aicodeman 1.17.0 → 1.18.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 +9 -9
- package/README.zh-CN.md +7 -7
- package/dist/config/dependency-registry.d.ts +7 -0
- package/dist/config/dependency-registry.d.ts.map +1 -1
- package/dist/config/dependency-registry.js +25 -0
- package/dist/config/dependency-registry.js.map +1 -1
- package/dist/cron/cron-service.d.ts +20 -0
- package/dist/cron/cron-service.d.ts.map +1 -1
- package/dist/cron/cron-service.js +28 -6
- package/dist/cron/cron-service.js.map +1 -1
- package/dist/docker-hosts.d.ts.map +1 -1
- package/dist/docker-hosts.js +14 -0
- package/dist/docker-hosts.js.map +1 -1
- package/dist/mux-interface.d.ts +3 -1
- package/dist/mux-interface.d.ts.map +1 -1
- package/dist/remote-hosts.d.ts.map +1 -1
- package/dist/remote-hosts.js +2 -0
- package/dist/remote-hosts.js.map +1 -1
- package/dist/session.d.ts +21 -4
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +33 -6
- package/dist/session.js.map +1 -1
- package/dist/tmux-manager.d.ts +2 -1
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +71 -5
- package/dist/tmux-manager.js.map +1 -1
- package/dist/types/session.d.ts +37 -4
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js +2 -1
- package/dist/types/session.js.map +1 -1
- package/dist/utils/dependency-checker.d.ts.map +1 -1
- package/dist/utils/dependency-checker.js +7 -1
- package/dist/utils/dependency-checker.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/pi-cli-resolver.d.ts +50 -0
- package/dist/utils/pi-cli-resolver.d.ts.map +1 -0
- package/dist/utils/pi-cli-resolver.js +144 -0
- package/dist/utils/pi-cli-resolver.js.map +1 -0
- 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.fe48931f.js → app.a13ac13b.js} +6 -6
- package/dist/web/public/app.a13ac13b.js.br +0 -0
- package/dist/web/public/app.a13ac13b.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/{constants.34a17e06.js → constants.42b8bbc9.js} +42 -0
- package/dist/web/public/constants.42b8bbc9.js.br +0 -0
- package/dist/web/public/constants.42b8bbc9.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 +1 -0
- 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.86757605.js +1 -0
- package/dist/web/public/i18n.86757605.js.br +0 -0
- package/dist/web/public/i18n.86757605.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +20 -11
- 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.32fdd57f.js.gz +0 -0
- package/dist/web/public/mobile-overview.js +1 -0
- 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.1eef31fe.css +1 -0
- package/dist/web/public/mobile.1eef31fe.css.br +0 -0
- package/dist/web/public/mobile.1eef31fe.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.54b0d004.js → panels-ui.4255e1bc.js} +1 -1
- package/dist/web/public/panels-ui.4255e1bc.js.br +0 -0
- package/dist/web/public/panels-ui.4255e1bc.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.gz +0 -0
- package/dist/web/public/{session-ui.46a0993c.js → session-ui.20be58fa.js} +3 -3
- package/dist/web/public/session-ui.20be58fa.js.br +0 -0
- package/dist/web/public/session-ui.20be58fa.js.gz +0 -0
- package/dist/web/public/{settings-ui.a12588ae.js → settings-ui.eacb4f92.js} +1 -1
- 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.b6e8b4ae.css +1 -0
- package/dist/web/public/styles.b6e8b4ae.css.br +0 -0
- package/dist/web/public/styles.b6e8b4ae.css.gz +0 -0
- package/dist/web/public/subagent-windows.2a192c3e.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/terminal-ui.5046620b.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +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.gz +0 -0
- package/dist/web/routes/session-routes.d.ts +30 -0
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +62 -16
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/routes/system-routes.d.ts.map +1 -1
- package/dist/web/routes/system-routes.js +14 -1
- package/dist/web/routes/system-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +38 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +38 -5
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +4 -1
- package/dist/web/server.js.map +1 -1
- package/dist/web/sse-events.d.ts +16 -1
- package/dist/web/sse-events.d.ts.map +1 -1
- package/dist/web/sse-events.js +18 -1
- package/dist/web/sse-events.js.map +1 -1
- package/dist/web/sse-stream-manager.d.ts +8 -1
- package/dist/web/sse-stream-manager.d.ts.map +1 -1
- package/dist/web/sse-stream-manager.js +12 -6
- package/dist/web/sse-stream-manager.js.map +1 -1
- package/package.json +2 -1
- package/skills/codeman/SKILL.md +4 -3
- package/skills/codeman/reference/endpoints.md +15 -5
- package/skills/codeman/reference/messaging.md +2 -2
- package/skills/codeman/reference/recipes.md +1 -1
- package/dist/web/public/app.fe48931f.js.br +0 -0
- package/dist/web/public/app.fe48931f.js.gz +0 -0
- package/dist/web/public/constants.34a17e06.js.br +0 -0
- package/dist/web/public/constants.34a17e06.js.gz +0 -0
- package/dist/web/public/i18n.89097ff5.js +0 -1
- package/dist/web/public/i18n.89097ff5.js.br +0 -0
- package/dist/web/public/i18n.89097ff5.js.gz +0 -0
- package/dist/web/public/mobile.90287e03.css +0 -1
- package/dist/web/public/mobile.90287e03.css.br +0 -0
- package/dist/web/public/mobile.90287e03.css.gz +0 -0
- package/dist/web/public/panels-ui.54b0d004.js.br +0 -0
- package/dist/web/public/panels-ui.54b0d004.js.gz +0 -0
- package/dist/web/public/session-ui.46a0993c.js.br +0 -0
- package/dist/web/public/session-ui.46a0993c.js.gz +0 -0
- package/dist/web/public/settings-ui.a12588ae.js.br +0 -0
- package/dist/web/public/settings-ui.a12588ae.js.gz +0 -0
- package/dist/web/public/styles.4908bb9b.css +0 -1
- package/dist/web/public/styles.4908bb9b.css.br +0 -0
- package/dist/web/public/styles.4908bb9b.css.gz +0 -0
|
@@ -10,9 +10,9 @@
|
|
|
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==="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"]){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 f=document.createElement("span");f.className="hist-meta",f.textContent=n,h.push(f),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==="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 C=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!C.success)throw new Error(C.error||"Failed to create case");n=C.data.case}const i=n.path;if(!i)throw new Error("Case path not found");if(n.location==="remote"||n.location==="docker"){const y=this._nextCaseSessionStartNumber(e);let C;if(n.location==="docker"){const v=this.loadAppSettingsFromStorage(),b=this.getCaseSettings(e).opusContext1m||v.opusContext1mEnabled;C=v.claudeModel||(b?"opus[1m]":"")}const E=[];let T=!1;for(let v=0;v<t;v++){const B=JSON.stringify({caseName:e,mode:"claude",sessionName:`w${y+v}-${e}`,...C!==void 0?{modelOverride:C}:{}}),b=async()=>(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:B})).json();let S=await b();if(!S.success&&S.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 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 f=document.createElement("span");f.className="hist-meta",f.textContent=n,h.push(f),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 C=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!C.success)throw new Error(C.error||"Failed to create case");n=C.data.case}const i=n.path;if(!i)throw new Error("Case path not found");if(n.location==="remote"||n.location==="docker"){const y=this._nextCaseSessionStartNumber(e);let C;if(n.location==="docker"){const v=this.loadAppSettingsFromStorage(),b=this.getCaseSettings(e).opusContext1m||v.opusContext1mEnabled;C=v.claudeModel||(b?"opus[1m]":"")}const E=[];let T=!1;for(let v=0;v<t;v++){const B=JSON.stringify({caseName:e,mode:"claude",sessionName:`w${y+v}-${e}`,...C!==void 0?{modelOverride:C}:{}}),b=async()=>(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:B})).json();let S=await b();if(!S.success&&S.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");S=await b()}if(!S.success)throw new Error(S.error||"Failed to start remote Claude session");await this._ensureCreatedSessionVisible(S.data.sessionId,S.data.session),E.push(S.data.sessionId)}this._appendSessionLaunchStatus(s,`All ${t} remote session(s) ready`),E[0]&&(await this.selectSession(E[0]),this.loadQuickStartCases()),this.terminal.focus();return}let o=null;const r=this._nextCaseSessionStartNumber(e),c=this.isRalphTrackerEnabledByDefault(),l=[];for(let y=0;y<t;y++)l.push(`w${r+y}-${e}`);const m=this.getCaseSettings(e),h=this.loadAppSettingsFromStorage(),u=this.buildEnvOverrides(m,h),f=Object.keys(u).length>0,d=this.getEffortSetting(h),g=m.opusContext1m||h.opusContext1mEnabled,p=h.claudeModel||(g?"opus[1m]":"");this._appendSessionLaunchStatus(s,`Creating ${t} session(s)...`);const I=l.map(y=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:i,name:y,...f?{envOverrides:u}:{},...d?{effort:d}:{},...p!==void 0?{modelOverride:p}:{},statusLineTelemetry:this.planUsageChipEnabled(h)})}).then(C=>C.json())),w=await Promise.all(I),k=[];for(const y of w){if(!y.success)throw new Error(y.error);await this._ensureCreatedSessionVisible(y.data.session.id,y.data.session),k.push(y.data.session.id)}o=k[0],await Promise.all(k.map(y=>fetch(`/api/sessions/${y}/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(y=>fetch(`/api/sessions/${y}/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 g=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!g.success)throw new Error(g.error||"Failed to create case");n=g.data.case}const 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"),g=[];for(let p=0;p<t;p++){const w=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"shell",sessionName:`s${d+p}-${e}`})})).json();if(!w.success)throw new Error(w.error||"Failed to start remote shell session");await this._ensureCreatedSessionVisible(w.data.sessionId,w.data.session),g.push(w.data.sessionId)}g[0]&&await this.selectSession(g[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(g=>g.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 f=this.getTerminalDimensions();f&&await Promise.all(u.map(d=>fetch(`/api/sessions/${d}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(f)}))),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)}},openSessionOptions(e){const t=this.sessions.get(e);if(!t)return;this.editingSessionId=e;const s=t.mode==="opencode"||t.mode==="codex"||t.mode==="gemini"||t.mode==="antigravity";this.switchOptionsTab(s?"summary":"respawn");const a=document.getElementById("sessionRespawnStatus"),n=document.getElementById("modalEnableRespawnBtn"),i=document.getElementById("modalStopRespawnBtn");this.respawnStatus[e]?(a.classList.add("active"),a.querySelector(".respawn-status-text").textContent=this.respawnStatus[e].state||"Active",n.style.display="none",i.style.display=""):(a.classList.remove("active"),a.querySelector(".respawn-status-text").textContent="Not active",n.style.display="",i.style.display="none");const o=document.getElementById("sessionRespawnSection");t.mode==="claude"&&t.pid?o.style.display="":o.style.display="none";const r=t.mode==="opencode"||t.mode==="codex"||t.mode==="gemini"||t.mode==="antigravity";document.querySelectorAll("[data-claude-only]").forEach(p=>{p.style.display=r?"none":""}),this.selectDurationPreset(""),this.loadSavedRespawnConfig(e),document.getElementById("modalAutoCompactEnabled").checked=t.autoCompactEnabled??!1,document.getElementById("modalAutoCompactThreshold").value=t.autoCompactThreshold??11e4,document.getElementById("modalAutoCompactPrompt").value=t.autoCompactPrompt??"",document.getElementById("modalAutoClearEnabled").checked=t.autoClearEnabled??!1,document.getElementById("modalAutoClearThreshold").value=t.autoClearThreshold??14e4,document.getElementById("modalAutoResumeEnabled").checked=t.autoResumeEnabled??!1,this.updateAutoResumeStatus(e),document.getElementById("modalImageWatcherEnabled").checked=t.imageWatcherEnabled??!0,document.getElementById("modalFlickerFilterEnabled").checked=t.flickerFilterEnabled??!1;const l=parseSessionPrefix(t.name),m=document.getElementById("modalSessionPrefix");l?(m.textContent=l.prefix+": ",m.style.display="",document.getElementById("modalSessionName").value=l.suffix,document.getElementById("modalSessionName").placeholder="Add description..."):(m.style.display="none",m.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(p=>{p.classList.toggle("selected",p.dataset.color===h)}),this.renderPresetDropdown(),document.getElementById("respawnPresetSelect").value="",document.getElementById("presetDescriptionHint").textContent="";const f=document.querySelector('#sessionOptionsModal .set-rail-item[data-tab="ralph"]'),d=document.querySelector('#sessionOptionsModal .set-rail-item[data-tab="respawn"]');if(r?(f&&(f.style.display="none"),d&&(d.style.display="none"),this.switchOptionsTab("context")):(f&&(f.style.display=""),d&&(d.style.display="")),!r){const p=this.ralphStates.get(e);this.populateRalphForm({enabled:p?.loop?.enabled??t.ralphLoop?.enabled??!1,completionPhrase:p?.loop?.completionPhrase||t.ralphLoop?.completionPhrase||"",maxIterations:p?.loop?.maxIterations||t.ralphLoop?.maxIterations||0,maxTodos:p?.loop?.maxTodos||t.ralphLoop?.maxTodos,todoExpirationMinutes:p?.loop?.todoExpirationMinutes||t.ralphLoop?.todoExpirationMinutes})}const g=document.getElementById("sessionOptionsModal");g.dataset.chipsReady!=="1"&&(g.dataset.chipsReady="1",g.addEventListener("change",p=>{p.target?.closest?.(".set-chip")&&this._syncSettingsChips()})),this._syncSettingsChips(),g.classList.add("active"),this.activeFocusTrap=new FocusTrap(g),this.activeFocusTrap.activate()},async saveSessionName(){if(!this.editingSessionId)return;const e=this.sessions.get(this.editingSessionId),t=e?parseSessionPrefix(e.name):null,s=document.getElementById("modalSessionName").value.trim();let a;t?a=t.prefix+(s?": "+s:""):a=s;try{await this._apiPut(`/api/sessions/${this.editingSessionId}/name`,{name:a})}catch(n){this.showToast("Failed to save session name: "+n.message,"error")}},async autoSaveAutoCompact(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-compact`,{enabled:document.getElementById("modalAutoCompactEnabled").checked,threshold:parseInt(document.getElementById("modalAutoCompactThreshold").value)||11e4,prompt:document.getElementById("modalAutoCompactPrompt").value.trim()||void 0})}catch{}},async autoSaveAutoClear(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-clear`,{enabled:document.getElementById("modalAutoClearEnabled").checked,threshold:parseInt(document.getElementById("modalAutoClearThreshold").value)||14e4})}catch{}},async autoSaveAutoResume(){if(!this.editingSessionId)return;const e=document.getElementById("modalAutoResumeEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-resume`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.autoResumeEnabled=e,e||(t.autoResumeAt=void 0)),this.updateAutoResumeStatus(this.editingSessionId),this.showToast(`Auto-resume on usage limit ${e?"enabled":"disabled"}`,"success")}catch(t){this.showToast("Failed to toggle auto-resume: "+t.message,"error")}},updateAutoResumeStatus(e){const t=document.getElementById("autoResumeStatus");if(!t||this.editingSessionId!==e)return;const s=this.sessions.get(e);if(s?.autoResumeAt&&s.autoResumeAt>Date.now()){const a=new Date(s.autoResumeAt).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});t.textContent=`Usage limit pause active \u2014 resumes at ${a}`,t.classList.add("active")}else t.textContent="",t.classList.remove("active")},async toggleSessionImageWatcher(){if(!this.editingSessionId)return;const e=document.getElementById("modalImageWatcherEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/image-watcher`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.imageWatcherEnabled=e),this.showToast(`Image watcher ${e?"enabled":"disabled"}`,"success")}catch{this.showToast("Failed to toggle image watcher","error")}},async toggleFlickerFilter(){if(!this.editingSessionId)return;const e=document.getElementById("modalFlickerFilterEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/flicker-filter`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.flickerFilterEnabled=e),this.showToast(`Flicker filter ${e?"enabled":"disabled"}`,"success")}catch{this.showToast("Failed to toggle flicker filter","error")}},async autoSaveRespawnConfig(){if(!this.editingSessionId)return;const e={updatePrompt:document.getElementById("modalRespawnPrompt").value,sendClear:document.getElementById("modalRespawnSendClear").checked,sendInit:document.getElementById("modalRespawnSendInit").checked,kickstartPrompt:document.getElementById("modalRespawnKickstart").value.trim()||void 0,autoAcceptPrompts:document.getElementById("modalRespawnAutoAccept").checked};try{await this._apiPut(`/api/sessions/${this.editingSessionId}/respawn/config`,e)}catch{}},async loadSavedRespawnConfig(e){try{const s=await(await fetch(`/api/sessions/${e}/respawn/config`)).json();if(s.success&&s.data&&s.data.config){const a=s.data.config;document.getElementById("modalRespawnPrompt").value=a.updatePrompt||"update all the docs and CLAUDE.md",document.getElementById("modalRespawnSendClear").checked=a.sendClear??!0,document.getElementById("modalRespawnSendInit").checked=a.sendInit??!0,document.getElementById("modalRespawnKickstart").value=a.kickstartPrompt||"",document.getElementById("modalRespawnAutoAccept").checked=a.autoAcceptPrompts??!0,a.durationMinutes&&(document.querySelector(`.duration-preset-btn[data-minutes="${a.durationMinutes}"]`)?this.selectDurationPreset(String(a.durationMinutes)):(this.selectDurationPreset("custom"),document.getElementById("modalRespawnDuration").value=a.durationMinutes))}}catch{}},selectDurationPreset(e){document.querySelectorAll(".duration-preset-btn").forEach(n=>n.classList.remove("active"));const t=document.querySelector(`.duration-preset-btn[data-minutes="${e}"]`);t&&t.classList.add("active");const s=document.querySelector(".duration-custom-input"),a=document.getElementById("modalRespawnDuration");e==="custom"?(s.classList.add("visible"),a.focus()):(s.classList.remove("visible"),a.value="")},getSelectedDuration(){const e=document.querySelector(".duration-custom-input"),t=document.getElementById("modalRespawnDuration");if(e.classList.contains("visible"))return t.value?parseInt(t.value):null;{const a=document.querySelector(".duration-preset-btn.active")?.dataset.minutes;return a?parseInt(a):null}},switchOptionsTab(e){document.querySelectorAll("#sessionOptionsModal .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)try{await fetch(`/api/sessions/${e}/name`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:m})})}catch{s.textContent=i,this.showToast("Failed to rename","error")}this.renderSessionTabs()};this._activeRename={sessionId:e,cancel:()=>r({commit:!1})},o.addEventListener("blur",()=>r({commit:!0})),o.addEventListener("keydown",c=>{c.isComposing||c.keyCode===229||(c.key==="Enter"?(c.preventDefault(),o.blur()):c.key==="Escape"&&(o.value="",o.blur()))})},toggleCaseSettings(){const e=document.getElementById("caseSettingsPopover");if(e.classList.contains("hidden")){const t=document.getElementById("quickStartCase").value||"testcase",s=this.getCaseSettings(t);document.getElementById("caseAgentTeams").checked=s.agentTeams,document.getElementById("caseOpusContext1m").checked=s.opusContext1m,e.classList.remove("hidden");const a=n=>{!e.contains(n.target)&&!n.target.classList.contains("btn-case-settings")&&(e.classList.add("hidden"),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),0)}else e.classList.add("hidden")},getCaseSettings(e){try{const t=localStorage.getItem("caseSettings_"+e);if(t)return JSON.parse(t)}catch{}return{agentTeams:!1,opusContext1m:!0}},saveCaseSettings(e,t){localStorage.setItem("caseSettings_"+e,JSON.stringify(t))},onCaseSettingChanged(){const e=document.getElementById("quickStartCase").value||"testcase",t=this.getCaseSettings(e);t.agentTeams=document.getElementById("caseAgentTeams").checked,t.opusContext1m=document.getElementById("caseOpusContext1m").checked,this.saveCaseSettings(e,t);const s=document.getElementById("caseAgentTeamsMobile");s&&(s.checked=t.agentTeams);const a=document.getElementById("caseOpusContext1mMobile");a&&(a.checked=t.opusContext1m)},toggleCaseSettingsMobile(){const e=document.getElementById("caseSettingsPopoverMobile");if(e.classList.contains("hidden")){const t=document.getElementById("quickStartCase").value||"testcase",s=this.getCaseSettings(t);document.getElementById("caseAgentTeamsMobile").checked=s.agentTeams,document.getElementById("caseOpusContext1mMobile").checked=s.opusContext1m,e.classList.remove("hidden");const a=n=>{!e.contains(n.target)&&!n.target.classList.contains("btn-case-settings-mobile")&&(e.classList.add("hidden"),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),0)}else e.classList.add("hidden")},onCaseSettingChangedMobile(){const e=document.getElementById("quickStartCase").value||"testcase",t=this.getCaseSettings(e);t.agentTeams=document.getElementById("caseAgentTeamsMobile").checked,t.opusContext1m=document.getElementById("caseOpusContext1mMobile").checked,this.saveCaseSettings(e,t);const s=document.getElementById("caseAgentTeams");s&&(s.checked=t.agentTeams);const a=document.getElementById("caseOpusContext1m");a&&(a.checked=t.opusContext1m)},showCreateCaseModal(){document.getElementById("newCaseName").value="",document.getElementById("newCaseDescription").value="",document.getElementById("linkCaseName").value="",document.getElementById("linkCasePath").value="",["remoteCaseName","remoteCasePath","remoteHostId","remoteHostAddress","remoteHostUsername","remoteHostPort","remoteHostCodexCommand","remoteHostIdentityFile","remoteHostSocksProxy","remoteHostJumpHost","remoteHostExtraSshOptions"].forEach(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(`
|
|
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");S=await b()}if(!S.success)throw new Error(S.error||"Failed to start remote Claude session");await this._ensureCreatedSessionVisible(S.data.sessionId,S.data.session),E.push(S.data.sessionId)}this._appendSessionLaunchStatus(s,`All ${t} remote session(s) ready`),E[0]&&(await this.selectSession(E[0]),this.loadQuickStartCases()),this.terminal.focus();return}let o=null;const r=this._nextCaseSessionStartNumber(e),c=this.isRalphTrackerEnabledByDefault(),l=[];for(let y=0;y<t;y++)l.push(`w${r+y}-${e}`);const m=this.getCaseSettings(e),h=this.loadAppSettingsFromStorage(),u=this.buildEnvOverrides(m,h),f=Object.keys(u).length>0,d=this.getEffortSetting(h),g=m.opusContext1m||h.opusContext1mEnabled,p=h.claudeModel||(g?"opus[1m]":"");this._appendSessionLaunchStatus(s,`Creating ${t} session(s)...`);const I=l.map(y=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:i,name:y,...f?{envOverrides:u}:{},...d?{effort:d}:{},...p!==void 0?{modelOverride:p}:{},statusLineTelemetry:this.planUsageChipEnabled(h)})}).then(C=>C.json())),w=await Promise.all(I),k=[];for(const y of w){if(!y.success)throw new Error(y.error);await this._ensureCreatedSessionVisible(y.data.session.id,y.data.session),k.push(y.data.session.id)}o=k[0],await Promise.all(k.map(y=>fetch(`/api/sessions/${y}/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(y=>fetch(`/api/sessions/${y}/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 g=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!g.success)throw new Error(g.error||"Failed to create case");n=g.data.case}const 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"),g=[];for(let p=0;p<t;p++){const w=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"shell",sessionName:`s${d+p}-${e}`})})).json();if(!w.success)throw new Error(w.error||"Failed to start remote shell session");await this._ensureCreatedSessionVisible(w.data.sessionId,w.data.session),g.push(w.data.sessionId)}g[0]&&await this.selectSession(g[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(g=>g.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 f=this.getTerminalDimensions();f&&await Promise.all(u.map(d=>fetch(`/api/sessions/${d}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(f)}))),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)}},openSessionOptions(e){const t=this.sessions.get(e);if(!t)return;this.editingSessionId=e;const s=t.mode==="opencode"||t.mode==="codex"||t.mode==="gemini"||t.mode==="antigravity"||t.mode==="pi";this.switchOptionsTab(s?"summary":"respawn");const a=document.getElementById("sessionRespawnStatus"),n=document.getElementById("modalEnableRespawnBtn"),i=document.getElementById("modalStopRespawnBtn");this.respawnStatus[e]?(a.classList.add("active"),a.querySelector(".respawn-status-text").textContent=this.respawnStatus[e].state||"Active",n.style.display="none",i.style.display=""):(a.classList.remove("active"),a.querySelector(".respawn-status-text").textContent="Not active",n.style.display="",i.style.display="none");const o=document.getElementById("sessionRespawnSection");t.mode==="claude"&&t.pid?o.style.display="":o.style.display="none";const r=t.mode==="opencode"||t.mode==="codex"||t.mode==="gemini"||t.mode==="antigravity"||t.mode==="pi";document.querySelectorAll("[data-claude-only]").forEach(p=>{p.style.display=r?"none":""}),this.selectDurationPreset(""),this.loadSavedRespawnConfig(e),document.getElementById("modalAutoCompactEnabled").checked=t.autoCompactEnabled??!1,document.getElementById("modalAutoCompactThreshold").value=t.autoCompactThreshold??11e4,document.getElementById("modalAutoCompactPrompt").value=t.autoCompactPrompt??"",document.getElementById("modalAutoClearEnabled").checked=t.autoClearEnabled??!1,document.getElementById("modalAutoClearThreshold").value=t.autoClearThreshold??14e4,document.getElementById("modalAutoResumeEnabled").checked=t.autoResumeEnabled??!1,this.updateAutoResumeStatus(e),document.getElementById("modalImageWatcherEnabled").checked=t.imageWatcherEnabled??!0,document.getElementById("modalFlickerFilterEnabled").checked=t.flickerFilterEnabled??!1;const l=parseSessionPrefix(t.name),m=document.getElementById("modalSessionPrefix");l?(m.textContent=l.prefix+": ",m.style.display="",document.getElementById("modalSessionName").value=l.suffix,document.getElementById("modalSessionName").placeholder="Add description..."):(m.style.display="none",m.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(p=>{p.classList.toggle("selected",p.dataset.color===h)}),this.renderPresetDropdown(),document.getElementById("respawnPresetSelect").value="",document.getElementById("presetDescriptionHint").textContent="";const f=document.querySelector('#sessionOptionsModal .set-rail-item[data-tab="ralph"]'),d=document.querySelector('#sessionOptionsModal .set-rail-item[data-tab="respawn"]');if(r?(f&&(f.style.display="none"),d&&(d.style.display="none"),this.switchOptionsTab("context")):(f&&(f.style.display=""),d&&(d.style.display="")),!r){const p=this.ralphStates.get(e);this.populateRalphForm({enabled:p?.loop?.enabled??t.ralphLoop?.enabled??!1,completionPhrase:p?.loop?.completionPhrase||t.ralphLoop?.completionPhrase||"",maxIterations:p?.loop?.maxIterations||t.ralphLoop?.maxIterations||0,maxTodos:p?.loop?.maxTodos||t.ralphLoop?.maxTodos,todoExpirationMinutes:p?.loop?.todoExpirationMinutes||t.ralphLoop?.todoExpirationMinutes})}const g=document.getElementById("sessionOptionsModal");g.dataset.chipsReady!=="1"&&(g.dataset.chipsReady="1",g.addEventListener("change",p=>{p.target?.closest?.(".set-chip")&&this._syncSettingsChips()})),this._syncSettingsChips(),g.classList.add("active"),this.activeFocusTrap=new FocusTrap(g),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
16
|
`).map(u=>u.trim()).filter(u=>u.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 h;if(o&&(h=Number(o),!Number.isInteger(h)||h<1||h>65535)){this.showToast("SSH port must be a number between 1 and 65535","error");return}try{const u={id:s,label:s,host:a,username:n,...h?{port:h}:{},...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(u)})).json();if(!d.success&&d.errorCode!=="ALREADY_EXISTS")throw new Error(d.error||"Failed to save remote host");const p=await(await fetch("/api/cases/remote-link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,hostId:s,remotePath:t})})).json();p.success?(this.closeCreateCaseModal(),this.showToast(`Remote case "${e}" linked`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(p.error||"Failed to link remote case","error")}catch(u){console.error("Failed to link remote case:",u),this.showToast("Failed to link remote case: "+u.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 h={id:s,label:s,image:a,network:n,mountCredentials:r,resumeOnStart:c,...Object.keys(m).length?{resources:m}:{}};let u=await fetch("/api/docker-hosts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(h)}),f=await u.json();if(!f.success&&f.errorCode==="ALREADY_EXISTS"&&(u=await fetch(`/api/docker-hosts/${encodeURIComponent(s)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(h)}),f=await u.json()),!f.success)throw new Error(f.error||"Failed to save docker host");const g=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(g.success){this.closeCreateCaseModal();const p=g.data?.capsEnforced===!1?" (resource caps are advisory on this engine)":"";this.showToast(`Docker case "${e}" linked${p}`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)}else l&&(l.textContent=g.error||"Failed to link docker case"),this.showToast(g.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;">
|
|
@@ -65,4 +65,4 @@ Recreate the container to apply the new config? Workspace files and the conversa
|
|
|
65
65
|
</svg>
|
|
66
66
|
</span>
|
|
67
67
|
</button>
|
|
68
|
-
`}t.innerHTML=n,e.classList.add("active")},closeMobileCasePicker(){document.getElementById("mobileCasePickerModal").classList.remove("active")},selectMobileCase(e){const t=document.getElementById("quickStartCase");t.value=e,this.updateMobileCaseLabel(e),this.updateDirDisplayForCase(e),this.saveLastUsedCase(e),this.closeMobileCasePicker(),this.showToast(`Selected: ${e}`,"success")},updateMobileCaseLabel(e){const t=document.getElementById("mobileCaseName");t&&(t.textContent=e)},async deleteCaseMobile(e){if(confirm(`Delete case "${e}"?`))try{const s=await(await fetch(`/api/cases/${encodeURIComponent(e)}`,{method:"DELETE"})).json();s.success?(this.showToast(`Case "${e}" ${s.data?.type==="unlinked"?"unlinked":"deleted"}`,"success"),this.cases=(this.cases||[]).filter(a=>a.name!==e),this.closeMobileCasePicker(),await this.loadQuickStartCases()):this.showToast(s.error||"Failed to delete case","error")}catch(t){this.showToast("Failed to delete case: "+t.message,"error")}},showCreateCaseFromMobile(){this.closeMobileCasePicker(),this.showCreateCaseModal();const e=document.getElementById("createCaseModal");e.classList.add("from-mobile"),setTimeout(()=>e.classList.remove("from-mobile"),300)}}),Object.defineProperty(CodemanApp.prototype,"runMode",{configurable:!0,enumerable:!0,get(){return this._runMode||"claude"},set(e){this._runMode=e==="opencode"||e==="codex"||e==="gemini"||e==="antigravity"||e==="claude"?e:"claude"}});
|
|
68
|
+
`}t.innerHTML=n,e.classList.add("active")},closeMobileCasePicker(){document.getElementById("mobileCasePickerModal").classList.remove("active")},selectMobileCase(e){const t=document.getElementById("quickStartCase");t.value=e,this.updateMobileCaseLabel(e),this.updateDirDisplayForCase(e),this.saveLastUsedCase(e),this.closeMobileCasePicker(),this.showToast(`Selected: ${e}`,"success")},updateMobileCaseLabel(e){const t=document.getElementById("mobileCaseName");t&&(t.textContent=e)},async deleteCaseMobile(e){if(confirm(`Delete case "${e}"?`))try{const s=await(await fetch(`/api/cases/${encodeURIComponent(e)}`,{method:"DELETE"})).json();s.success?(this.showToast(`Case "${e}" ${s.data?.type==="unlinked"?"unlinked":"deleted"}`,"success"),this.cases=(this.cases||[]).filter(a=>a.name!==e),this.closeMobileCasePicker(),await this.loadQuickStartCases()):this.showToast(s.error||"Failed to delete case","error")}catch(t){this.showToast("Failed to delete case: "+t.message,"error")}},showCreateCaseFromMobile(){this.closeMobileCasePicker(),this.showCreateCaseModal();const e=document.getElementById("createCaseModal");e.classList.add("from-mobile"),setTimeout(()=>e.classList.remove("from-mobile"),300)}}),Object.defineProperty(CodemanApp.prototype,"runMode",{configurable:!0,enumerable:!0,get(){return this._runMode||"claude"},set(e){this._runMode=e==="opencode"||e==="codex"||e==="gemini"||e==="antigravity"||e==="pi"||e==="claude"?e:"claude"}});
|
|
Binary file
|
|
Binary file
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.assign(CodemanApp.prototype,{_onHookIdlePrompt(e){e.sessionId&&this.setPendingHook(e.sessionId,"idle_prompt"),this._notifySession(e.sessionId,"warning","hook-idle","Waiting for Input",e.message||"Claude is idle and waiting for a prompt")},_onHookPermissionPrompt(e){e.sessionId&&this.setPendingHook(e.sessionId,"permission_prompt");const t=e.tool?`${e.tool}${e.command?": "+e.command:e.file?": "+e.file:""}`:"";this._notifySession(e.sessionId,"critical","hook-permission","Permission Required",t||"Claude needs tool approval to continue")},_onHookElicitationDialog(e){e.sessionId&&this.setPendingHook(e.sessionId,"elicitation_dialog"),this._notifySession(e.sessionId,"critical","hook-elicitation","Question Asked",e.question||"Claude is asking a question and waiting for your answer")},_onHookElicitationComplete(e){e.sessionId&&this.clearPendingHooks(e.sessionId,"elicitation_dialog")},_onHookElicitationResponse(e){this._onHookElicitationComplete(e)},_onHookStop(e){e.sessionId&&this.clearPendingHooks(e.sessionId),this._notifySession(e.sessionId,"info","hook-stop","Response Complete",e.reason||"Claude has finished responding")},_onHookTeammateIdle(e){const t=this.sessions.get(e.sessionId);this._notifySession(e.sessionId,"warning","hook-teammate-idle","Teammate Idle",`A teammate is idle in ${t?.name||e.sessionId}`)},_onHookTaskCompleted(e){const t=this.sessions.get(e.sessionId);this._notifySession(e.sessionId,"info","hook-task-completed","Task Completed",`A team task completed in ${t?.name||e.sessionId}`)},_onTunnelStarted(e){console.log("[Tunnel] Started:",e.url),this._tunnelUrl=e.url,this._dismissTunnelConnecting(),this._updateTunnelUrlDisplay(e.url),this._updateTunnelIndicator(!0),document.getElementById("welcomeOverlay")?.classList.contains("visible")?(this._updateWelcomeTunnelBtn(!0,e.url,!0),this.showToast("Tunnel active","success")):(this._updateWelcomeTunnelBtn(!0,e.url),this.showToast(`Tunnel active: ${e.url}`,"success"),this.showTunnelQR())},_onTunnelStopped(){console.log("[Tunnel] Stopped"),this._tunnelUrl=null,this._dismissTunnelConnecting(),this._updateTunnelUrlDisplay(null),this._updateWelcomeTunnelBtn(!1),this._updateTunnelIndicator(!1),this.closeTunnelPanel(),this.closeTunnelQR()},_onTunnelProgress(e){console.log("[Tunnel] Progress:",e.message);const t=document.getElementById("tunnelConnectingToast");t&&(t.innerHTML=`<span class="tunnel-spinner"></span> ${e.message}`);const n=document.getElementById("welcomeTunnelBtn");n?.classList.contains("connecting")&&(n.innerHTML=`<span class="tunnel-spinner"></span> ${e.message}`)},_onTunnelError(e){console.warn("[Tunnel] Error:",e.message),this._dismissTunnelConnecting(),this.showToast(`Tunnel error: ${e.message}`,"error");const t=document.getElementById("welcomeTunnelBtn");t&&(t.disabled=!1,t.classList.remove("connecting"))},_onTunnelQrRotated(e){if(e.svg){const t=document.getElementById("tunnelQrContainer");t&&(t.innerHTML=e.svg);const n=document.getElementById("welcomeQrInner");n&&(n.innerHTML=e.svg)}else this._refreshTunnelQrFromApi();this._resetQrCountdown()},_onTunnelQrRegenerated(e){if(e.svg){const t=document.getElementById("tunnelQrContainer");t&&(t.innerHTML=e.svg);const n=document.getElementById("welcomeQrInner");n&&(n.innerHTML=e.svg)}else this._refreshTunnelQrFromApi();this._resetQrCountdown()},_onTunnelQrAuthUsed(e){const n=(e.ua||"Unknown device").match(/Chrome|Firefox|Safari|Edge|Mobile/)?.[0]||"Browser";this.showToast(`Device authenticated via QR (${n}, ${e.ip}). Not you?`,"warning",{duration:1e4,action:{label:"Revoke All",onClick:()=>{fetch("/api/auth/revoke",{method:"POST",headers:{"Content-Type":"application/json"},body:"{}"}).then(()=>this.showToast("All sessions revoked","success")).catch(()=>this.showToast("Failed to revoke sessions","error"))}}})},registerServiceWorker(){"serviceWorker"in navigator&&navigator.serviceWorker.register("/sw.js").then(e=>{this._swRegistration=e,navigator.serviceWorker.addEventListener("message",t=>{if(t.data?.type==="notification-click"){const{sessionId:n,action:s,approvalId:o}=t.data;s?this.handleNotificationAction?.(s,o,n):n&&this.sessions.has(n)&&this.selectSession(n),window.focus()}}),e.pushManager.getSubscription().then(t=>{t&&(this._pushSubscription=t,this._updatePushUI(!0))})}).catch(()=>{})},async subscribeToPush(){if(!this._swRegistration){this.showToast("Service worker not available. HTTPS or localhost required.","error");return}try{const e=await this._apiJson("/api/push/vapid-key");if(!e)throw new Error("Failed to get VAPID key");const t=urlBase64ToUint8Array(e.publicKey),n=await this._swRegistration.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:t}),s=n.toJSON(),o=await this._apiJson("/api/push/subscribe",{method:"POST",body:{endpoint:s.endpoint,keys:s.keys,userAgent:navigator.userAgent,pushPreferences:this._buildPushPreferences()}});if(!o)throw new Error("Failed to register subscription");this._pushSubscription=n,this._pushSubscriptionId=o.id,localStorage.setItem("codeman-push-subscription-id",o.id),this._updatePushUI(!0),this.showToast("Push notifications enabled","success")}catch(e){this.showToast("Push subscription failed: "+(e.message||e),"error")}},async unsubscribeFromPush(){try{this._pushSubscription&&await this._pushSubscription.unsubscribe();const e=this._pushSubscriptionId||localStorage.getItem("codeman-push-subscription-id");e&&await fetch(`/api/push/subscribe/${e}`,{method:"DELETE"}).catch(()=>{}),this._pushSubscription=null,this._pushSubscriptionId=null,localStorage.removeItem("codeman-push-subscription-id"),this._updatePushUI(!1),this.showToast("Push notifications disabled","success")}catch(e){this.showToast("Failed to unsubscribe: "+(e.message||e),"error")}},async togglePushSubscription(){this._pushSubscription?await this.unsubscribeFromPush():await this.subscribeToPush()},async _syncPushPreferences(){const e=this._pushSubscriptionId||localStorage.getItem("codeman-push-subscription-id");if(e)try{await fetch(`/api/push/subscribe/${e}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({pushPreferences:this._buildPushPreferences()})})}catch{}},_buildPushPreferences(){const e={},t={"hook:permission_prompt":"eventPermissionPush","hook:elicitation_dialog":"eventQuestionPush","hook:idle_prompt":"eventIdlePush","hook:stop":"eventStopPush","respawn:blocked":"eventRespawnPush","session:ralphCompletionDetected":"eventRalphPush"};for(const[n,s]of Object.entries(t)){const o=document.getElementById(s);e[n]=o?o.checked:!0}return e["session:error"]=!0,e},_updatePushUI(e){const t=document.getElementById("pushSubscribeBtn"),n=document.getElementById("pushSubscriptionStatus");t&&(t.textContent=e?"Unsubscribe":"Subscribe"),n&&(n.textContent=e?"active":"off",n.classList.remove("granted","denied"),e&&n.classList.add("granted"))},openAppSettings(){const e=this.loadAppSettingsFromStorage();document.getElementById("appSettingsDisplayName").value=e.displayName||"Codeman",document.getElementById("appSettingsLanguage").value=e.language==="zh-CN"?"zh-CN":"en",document.getElementById("appSettingsClaudeMdPath").value=e.defaultClaudeMdPath||"",document.getElementById("appSettingsDefaultDir").value=e.defaultWorkingDir||"";const t=this.getDefaultSettings();document.getElementById("appSettingsRalphEnabled").checked=e.ralphTrackerEnabled??t.ralphTrackerEnabled??!1,document.getElementById("appSettingsShowFontControls").checked=e.showFontControls??t.showFontControls??!1,document.getElementById("appSettingsShowSystemStats").checked=e.showSystemStats??t.showSystemStats??!0,document.getElementById("appSettingsShowLifecycleLog").checked=e.showLifecycleLog??t.showLifecycleLog??!1,document.getElementById("appSettingsShowResponseViewer").checked=e.showResponseViewer??t.showResponseViewer??!1,document.getElementById("appSettingsShowFileViewerButton").checked=e.showFileViewerButton??t.showFileViewerButton??!0,document.getElementById("appSettingsShowAttachmentsButton").checked=e.showAttachmentsButton??t.showAttachmentsButton??!1,document.getElementById("appSettingsSkin").value=e.skin??t.skin??"daylight-blue",this._syncEntranceAnimSetting?.(),document.getElementById("appSettingsWebglRenderer").checked=e.webglRendererEnabled??t.webglRendererEnabled??!0;const n=document.getElementById("appSettingsWebglRendererItem");n&&(n.style.display=MobileDetection.getDeviceType()==="desktop"?"":"none"),document.getElementById("appSettingsShowMonitor").checked=e.showMonitor??t.showMonitor??!1,document.getElementById("appSettingsShowProjectInsights").checked=e.showProjectInsights??t.showProjectInsights??!1,document.getElementById("appSettingsShowFileBrowser").checked=e.showFileBrowser??t.showFileBrowser??!1,document.getElementById("appSettingsShowSubagents").checked=e.showSubagents??t.showSubagents??!1,document.getElementById("appSettingsShowUltracodeAgents").checked=e.showUltracodeAgents??t.showUltracodeAgents??!1,document.getElementById("appSettingsApprovalsInbox").checked=e.approvalsInboxEnabled===!0,document.getElementById("appSettingsReadMyMind").checked=e.readMyMindEnabled===!0,document.getElementById("appSettingsUltracodeFloatingWindows").checked=e.ultracodeFloatingWindows??t.ultracodeFloatingWindows??!1,document.getElementById("appSettingsShowMultiMonitorButton").checked=e.showMultiMonitorButton??t.showMultiMonitorButton??!1,document.getElementById("appSettingsShowPlanUsageLimits").checked=this.planUsageChipEnabled(e),document.getElementById("appSettingsShowRedrawButton").checked=e.showRedrawButton??t.showRedrawButton??!1,document.getElementById("appSettingsLineageLines").checked=e.sessionLineageLines??t.sessionLineageLines??!0;const s=document.getElementById("appSettingsLineageLinesItem");s&&(s.style.display=MobileDetection.getDeviceType()==="desktop"?"":"none"),document.getElementById("appSettingsMobileOverview").checked=e.mobileOverviewEnabled??t.mobileOverviewEnabled??!1;const o=document.getElementById("appSettingsMobileOverviewItem");o&&(o.style.display=MobileDetection.getDeviceType()==="mobile"?"":"none"),document.getElementById("appSettingsShowSessionButton").checked=e.showSessionButton??t.showSessionButton??!1,document.getElementById("appSettingsShowAwayDigestButton").checked=e.showAwayDigestButton??t.showAwayDigestButton??!1,document.getElementById("appSettingsShowCronButton").checked=e.showCronButton??t.showCronButton??!1;const i=document.getElementById("appSettingsGestureControlItem");i&&(i.style.display=window.__codemanGestureAvailable?"":"none"),document.getElementById("appSettingsGestureControl").checked=e.gestureControlEnabled??t.gestureControlEnabled??!1,document.getElementById("appSettingsSubagentTracking").checked=e.subagentTrackingEnabled??t.subagentTrackingEnabled??!0,document.getElementById("appSettingsSubagentActiveTabOnly").checked=e.subagentActiveTabOnly??t.subagentActiveTabOnly??!0,document.getElementById("appSettingsImageWatcherEnabled").checked=e.imageWatcherEnabled??t.imageWatcherEnabled??!1,document.getElementById("appSettingsTunnelEnabled").checked=e.tunnelEnabled??!1,this.loadTunnelStatus(),document.getElementById("appSettingsLocalEcho").checked=e.localEchoEnabled??MobileDetection.isTouchDevice(),document.getElementById("appSettingsTerminalWheelLocal").checked=e.terminalWheelLocalScrollback??t.terminalWheelLocalScrollback??!1,document.getElementById("appSettingsCjkInput").checked=e.cjkInputEnabled??t.cjkInputEnabled??!1,document.getElementById("appSettingsExtendedKeyboardBar").checked=e.extendedKeyboardBar??!1,document.getElementById("appSettingsTabTwoRows").checked=e.tabTwoRows??t.tabTwoRows??!1,document.getElementById("appSettingsShowTabDetachButton").checked=e.showTabDetachButton??t.showTabDetachButton??!1;const a=document.getElementById("appSettingsClaudeMode"),l=document.getElementById("allowedToolsRow");a.value=e.claudeMode||"dangerously-skip-permissions",document.getElementById("appSettingsAllowedTools").value=e.allowedTools||"",l.style.display=a.value==="allowedTools"?"":"none",a.onchange=()=>{l.style.display=a.value==="allowedTools"?"":"none"},document.getElementById("appSettingsCodexDangerouslyBypassApprovals").checked=e.codexDangerouslyBypassApprovals??!1,document.getElementById("appSettingsCodexAnimations").checked=e.codexAnimationsEnabled??!1,this._applyCodexSettingsVisibility(),document.getElementById("appSettingsAgentTeams").checked=e.agentTeamsEnabled??!1,document.getElementById("appSettingsAgentSkill").checked=e.agentSkillEnabled??!1,document.getElementById("appSettingsClaudeModel").value=e.claudeModel??"",document.getElementById("appSettingsOpusContext1m").checked=e.opusContext1mEnabled??!1,document.getElementById("appSettingsRemoteAutoReconnect").checked=e.remoteAutoReconnect??!0,document.getElementById("appSettingsThinkingEffort").value=e.thinkingEffort??"";const c=e.nice||{};document.getElementById("appSettingsNiceEnabled").checked=c.enabled??!1,document.getElementById("appSettingsNiceValue").value=c.niceValue??10,this.loadModelConfigForSettings();const d=this.notificationManager?.preferences||{};document.getElementById("appSettingsNotifEnabled").checked=d.enabled??!0,document.getElementById("appSettingsNotifBrowser").checked=d.browserNotifications??!1,document.getElementById("appSettingsNotifAudio").checked=d.audioAlerts??!1,document.getElementById("appSettingsNotifStuckMins").value=Math.round((d.stuckThresholdMs||6e5)/6e4),document.getElementById("appSettingsNotifCritical").checked=!d.muteCritical,document.getElementById("appSettingsNotifWarning").checked=!d.muteWarning,document.getElementById("appSettingsNotifInfo").checked=!d.muteInfo,document.getElementById("appSettingsPushEnabled").checked=!!this._pushSubscription,this._updatePushUI(!!this._pushSubscription);const r=d.eventTypes||{},u=r.permission_prompt||{};document.getElementById("eventPermissionEnabled").checked=u.enabled??!0,document.getElementById("eventPermissionBrowser").checked=u.browser??!0,document.getElementById("eventPermissionPush").checked=u.push??!1,document.getElementById("eventPermissionAudio").checked=u.audio??!0;const g=r.elicitation_dialog||{};document.getElementById("eventQuestionEnabled").checked=g.enabled??!0,document.getElementById("eventQuestionBrowser").checked=g.browser??!0,document.getElementById("eventQuestionPush").checked=g.push??!1,document.getElementById("eventQuestionAudio").checked=g.audio??!0;const p=r.idle_prompt||{};document.getElementById("eventIdleEnabled").checked=p.enabled??!0,document.getElementById("eventIdleBrowser").checked=p.browser??!0,document.getElementById("eventIdlePush").checked=p.push??!1,document.getElementById("eventIdleAudio").checked=p.audio??!1;const S=r.stop||{};document.getElementById("eventStopEnabled").checked=S.enabled??!1,document.getElementById("eventStopBrowser").checked=S.browser??!1,document.getElementById("eventStopPush").checked=S.push??!1,document.getElementById("eventStopAudio").checked=S.audio??!1;const y=r.respawn_cycle||{};document.getElementById("eventRespawnEnabled").checked=y.enabled??!0,document.getElementById("eventRespawnBrowser").checked=y.browser??!1,document.getElementById("eventRespawnPush").checked=y.push??!1,document.getElementById("eventRespawnAudio").checked=y.audio??!1;const b=r.ralph_complete||{};document.getElementById("eventRalphEnabled").checked=b.enabled??!0,document.getElementById("eventRalphBrowser").checked=b.browser??!0,document.getElementById("eventRalphPush").checked=b.push??!1,document.getElementById("eventRalphAudio").checked=b.audio??!0;const f=r.subagent_spawn||{};document.getElementById("eventSubagentEnabled").checked=f.enabled??!1,document.getElementById("eventSubagentBrowser").checked=f.browser??!1,document.getElementById("eventSubagentPush").checked=f.push??!1,document.getElementById("eventSubagentAudio").checked=f.audio??!1;const w=document.getElementById("notifPermissionStatus");if(w&&typeof Notification<"u"){const m=Notification.permission;w.textContent=m==="granted"?"\u2713":m==="denied"?"\u2717":"?",w.classList.remove("granted","denied"),m==="granted"?w.classList.add("granted"):m==="denied"&&w.classList.add("denied")}const h=VoiceInput._getDeepgramConfig();document.getElementById("voiceDeepgramKey").value=h.apiKey||"",document.getElementById("voiceLanguage").value=h.language||"en-US",document.getElementById("voiceKeyterms").value=h.keyterms||DEFAULT_VOICE_KEYTERMS,document.getElementById("voiceInsertMode").value=h.insertMode||"direct",document.getElementById("voiceProvider").value=h.provider||"auto",document.getElementById("appSettingsClaudeVoice").checked=e.claudeVoiceEnabled??!1;const B=document.getElementById("voiceDeepgramKey");B.type="password",document.getElementById("voiceKeyToggleBtn").textContent="Show",this._renderVoiceProviderStatus(),VoiceInput.refreshClaudeStatus().then(()=>this._renderVoiceProviderStatus()),this._initUpdatesSection(),this._initSettingsNav(),this._syncSettingsChips(),this._syncModelCards(),this._syncEffortSegment(),this.switchSettingsTab("settings-updates");const v=document.getElementById("appSettingsModal");v.classList.add("active"),this.activeFocusTrap=new FocusTrap(v),this.activeFocusTrap.activate()},_applyCodexSettingsVisibility(){const e=document.getElementById("appSettingsCodexGroup");e&&(e.style.display=window.__codemanCliAvailable?.codex===!0?"":"none")},switchSettingsTab(e){e==="settings-shortcuts"&&this.renderShortcutSettingsList?.();const t=document.getElementById("appSettingsDoc"),n=document.getElementById(e);if(t&&n&&typeof n.offsetTop=="number"){const s=document.getElementById("appSettingsJump"),o=s&&s.offsetParent?s.offsetHeight+16:6;t.scrollTop=Math.max(0,n.offsetTop-o)}this._setActiveSettingsSection(e)},_setActiveSettingsSection(e){const t=document.getElementById("appSettingsModal");if(!t||typeof t.querySelectorAll!="function")return;let n=null;t.querySelectorAll(".set-rail-item").forEach(a=>{const l=a.dataset.section===e;a.classList.toggle("active",l),l&&(n=a)}),t.querySelectorAll(".set-jump-row").forEach(a=>{a.classList.toggle("active",a.dataset.section===e)});const s=document.getElementById("appSettingsJump")?.querySelector(".set-jump-label");s&&n&&(s.textContent=n.textContent.trim());const o=document.getElementById("appSettingsJump")?.querySelector(".set-jump-ico"),i=n?.querySelector("svg");o&&i&&(o.innerHTML=i.innerHTML)},_initSettingsNav(){const e=document.getElementById("appSettingsModal"),t=document.getElementById("appSettingsDoc");if(!e||!t||typeof e.querySelectorAll!="function"||(this._buildModelCards(),this._buildEffortSegment(),this._buildSettingsJumpMenu(),e.dataset.navReady==="1"))return;e.dataset.navReady="1",e.querySelector(".set-rail-items")?.addEventListener("click",i=>{const a=i.target.closest?.(".set-rail-item");a?.dataset.section&&this.switchSettingsTab(a.dataset.section)}),document.getElementById("appSettingsJumpMenu")?.addEventListener("click",i=>{const a=i.target.closest?.(".set-jump-row");a?.dataset.section&&(this._toggleSettingsJump(!1),this.switchSettingsTab(a.dataset.section))}),document.getElementById("appSettingsJump")?.addEventListener("click",()=>this._toggleSettingsJump()),document.getElementById("appSettingsJumpVeil")?.addEventListener("click",()=>this._toggleSettingsJump(!1)),t.addEventListener("scroll",()=>{this._settingsSpyQueued||(this._settingsSpyQueued=!0,requestAnimationFrame(()=>{this._settingsSpyQueued=!1;const i=[...t.querySelectorAll(".set-section")].filter(l=>l.offsetParent!==null);if(!i.length)return;let a=i[0].id;for(const l of i)l.offsetTop-t.scrollTop<=140&&(a=l.id);this._setActiveSettingsSection(a)}))});const n=document.getElementById("appSettingsSearch");n?.addEventListener("input",()=>this._filterSettings(n.value)),e.querySelectorAll(".set-chip input").forEach(i=>{i.addEventListener("change",()=>this._syncSettingsChips())});const s=e.querySelector(".set-group-head-toggle"),o=s?.closest(".set-group-advanced");if(s&&o){const i=()=>{const a=o.classList.toggle("open");s.setAttribute("aria-expanded",a?"true":"false")};s.addEventListener("click",i),s.addEventListener("keydown",a=>{(a.key==="Enter"||a.key===" ")&&(a.preventDefault(),i())})}document.getElementById("appSettingsOpusContext1m")?.addEventListener("change",()=>this._applyModelSelection())},_buildSettingsJumpMenu(){const e=document.getElementById("appSettingsModal"),t=document.getElementById("appSettingsJumpMenu");!e||!t||(t.innerHTML="",e.querySelectorAll(".set-rail-item").forEach(n=>{const s=document.createElement("button");s.type="button",s.className="set-jump-row",s.dataset.section=n.dataset.section,s.innerHTML=n.innerHTML;const o=document.getElementById(n.dataset.section),i=o?o.querySelectorAll("input, select").length:0;if(i){const a=document.createElement("span");a.className="set-jump-count",a.textContent=String(i),s.appendChild(a)}t.appendChild(s)}))},_toggleSettingsJump(e){const t=document.getElementById("appSettingsModal");if(!t)return;const n=e===void 0?!t.classList.contains("jump-open"):e;t.classList.toggle("jump-open",n),document.getElementById("appSettingsJump")?.setAttribute("aria-expanded",n?"true":"false")},_syncSettingsChips(){document.querySelectorAll("#appSettingsModal .set-chip, #sessionOptionsModal .set-chip").forEach(e=>{e.classList.toggle("is-on",!!e.querySelector("input")?.checked)}),this._syncLayoutPreview()},_syncLayoutPreview(){const e=document.getElementById("appSettingsModal");if(!e||typeof e.querySelectorAll!="function")return;const t={header:document.getElementById("appSettingsPreviewHeader"),panel:document.getElementById("appSettingsPreviewPanels"),toolbar:document.getElementById("appSettingsPreviewToolbar"),float:document.getElementById("appSettingsPreviewFloats")};if(!t.header)return;Object.values(t).forEach(i=>{i&&(i.innerHTML="")});const n=[...e.querySelectorAll(".set-chip[data-preview]")].filter(i=>i.querySelector("input")?.checked).sort((i,a)=>(Number(i.dataset.previewOrder)||0)-(Number(a.dataset.previewOrder)||0));let s=0;for(const i of n){const a=i.dataset.preview,l=t[a];if(!l)continue;const c=i.querySelectorAll("span"),d=(c[c.length-1]?.textContent||"").trim(),r=document.createElement("span");if(r.title=d,a==="header"){const u=i.dataset.previewText;r.className=u?"set-preview-chip":"set-preview-btn",u?r.textContent=u:this._appendPreviewIcon(r,i)}else{r.className=`set-preview-${a}`,this._appendPreviewIcon(r,i);const u=document.createElement("span");u.textContent=d,r.appendChild(u)}l.appendChild(r),s++}const o=document.getElementById("appSettingsPreviewEmpty");o&&(o.hidden=s>0)},_appendPreviewIcon(e,t){const n=t.querySelector(".set-chip-ico");if(!n)return;const s=n.cloneNode(!0);s.classList.remove("set-chip-ico"),s.classList.add("set-preview-ico"),e.appendChild(s)},_buildModelCards(){const e=document.getElementById("appSettingsClaudeModel"),t=document.getElementById("appSettingsModelCards");!e||!t||t.dataset.built==="1"||!e.options||(t.innerHTML="",[...e.options].filter(n=>n.dataset.variant!=="1m").forEach(n=>{const s=document.createElement("button");s.type="button",s.className="set-modelcard",s.setAttribute("role","radio"),s.dataset.value=n.value,n.dataset.ctx==="1"&&(s.dataset.ctx="1");const o=document.createElement("span");o.className="set-mc-top";const i=document.createElement("span");i.className="set-mc-name",i.textContent=n.textContent,o.appendChild(i);const a=document.createElement("span");a.className="set-mc-dot",o.appendChild(a),s.appendChild(o);const l=document.createElement("span");if(l.className="set-mc-meta",l.textContent=n.dataset.meta||"",s.appendChild(l),n.dataset.ctx==="1"){const c=document.createElement("span");c.className="set-mc-ctx",c.textContent="1M capable",s.appendChild(c)}s.addEventListener("click",()=>{this._settingsModelBase=n.value,this._applyModelSelection()}),t.appendChild(s)}),t.dataset.built="1")},_syncModelCards(){const e=document.getElementById("appSettingsClaudeModel");if(!e)return;const t=e.value||"";if(this._settingsModelBase=t.endsWith("[1m]")?t.slice(0,-4):t,t.endsWith("[1m]")){const n=document.getElementById("appSettingsOpusContext1m");n&&(n.checked=!0)}this._applyModelSelection()},_applyModelSelection(){const e=document.getElementById("appSettingsClaudeModel"),t=document.getElementById("appSettingsModelCards");if(!e||!t)return;const n=this._settingsModelBase||"";let s=!1;t.querySelectorAll(".set-modelcard").forEach(c=>{const d=c.dataset.value===n;c.classList.toggle("selected",d),c.setAttribute("aria-checked",d?"true":"false"),d&&(s=c.dataset.ctx==="1")});const o=!!document.getElementById("appSettingsOpusContext1m")?.checked;e.value=n&&s&&o?`${n}[1m]`:n;const i=document.getElementById("appSettingsContextRow"),a=document.getElementById("appSettingsContextDesc"),l=!!n&&!s;i?.classList.toggle("set-row-disabled",l),a&&(a.textContent=l?"The selected model has no 1M variant.":n?"Available for Fable 5, Opus and Opus 4.6.":"With no model pinned, this starts new sessions on Opus with a 1M window.")},_buildEffortSegment(){const e=document.getElementById("appSettingsThinkingEffort"),t=document.getElementById("appSettingsEffortSegment");!e||!t||t.dataset.built==="1"||!e.options||(t.innerHTML="",[...e.options].forEach(n=>{const s=document.createElement("button");s.type="button",s.setAttribute("role","radio"),s.dataset.value=n.value,s.textContent=n.textContent,s.addEventListener("click",()=>{e.value=n.value,this._syncEffortSegment()}),t.appendChild(s)}),t.dataset.built="1")},_syncEffortSegment(){const e=document.getElementById("appSettingsThinkingEffort"),t=document.getElementById("appSettingsEffortSegment");!e||!t||t.querySelectorAll("button").forEach(n=>{const s=n.dataset.value===(e.value||"");n.classList.toggle("selected",s),n.setAttribute("aria-checked",s?"true":"false")})},_filterSettings(e){const t=document.getElementById("appSettingsDoc");if(!t)return;const n=(e||"").trim().toLowerCase(),s=".set-row, .set-chip, .set-modelgrid, .set-minigrid, .event-type-grid, #appSettingsShortcutsList",o=[...t.querySelectorAll(s)];let i=!1;o.forEach(l=>{if(!n){l.classList.remove("set-hit-hidden");return}const d=`${l.dataset?.search||""} ${l.textContent||""}`.toLowerCase().includes(n);l.classList.toggle("set-hit-hidden",!d),d&&(i=!0)}),t.querySelectorAll(".set-chips").forEach(l=>{const c=[...l.querySelectorAll(".set-chip")].some(d=>!d.classList.contains("set-hit-hidden"));l.classList.toggle("set-hit-hidden",!!n&&!c)}),t.querySelectorAll(".set-group").forEach(l=>{const c=[...l.querySelectorAll(s)].some(d=>!d.classList.contains("set-hit-hidden"));l.classList.toggle("set-hit-hidden",!!n&&!c),n&&c&&l.classList.add("open")}),t.querySelectorAll(".set-section").forEach(l=>{const c=[...l.querySelectorAll(".set-group")].some(d=>!d.classList.contains("set-hit-hidden"));l.classList.toggle("set-hit-hidden",!!n&&!c)}),t.querySelectorAll(".set-preview").forEach(l=>l.classList.toggle("set-hit-hidden",!!n));const a=document.getElementById("appSettingsSearchEmpty");a&&(a.hidden=!n||i),n||t.querySelectorAll(".set-group-advanced").forEach(l=>l.classList.remove("open"))},closeAppSettings(){this._toggleSettingsJump(!1),document.getElementById("appSettingsModal").classList.remove("active"),this.activeFocusTrap&&(this.activeFocusTrap.deactivate(),this.activeFocusTrap=null)},_updatePhaseText(e){return{queued:"Queued\u2026",preparing:"Preparing\u2026",stashing:"Stashing local changes\u2026",fetching:"Fetching release\u2026",checkout:"Checking out release\u2026",installing:"Installing dependencies\u2026",building:"Building\u2026",restarting:"Restarting Codeman\u2026"}[e]||e},_initUpdatesSection(){const e=this.$("updateCurrentVersion");e&&(e.textContent=(this.$("versionDisplay")?.textContent||"").trim()||"\u2014");for(const t of["updateResult","updateActionRow","updateNotes","updateProgress"]){const n=this.$(t);n&&(n.style.display="none")}this._updateCheck=null},_setUpdateResult(e){const t=this.$("updateResult");t&&(t.style.display="block",t.innerHTML=e)},_setUpdateProgress(e){const t=this.$("updateProgress");t&&(t.style.display="block",t.innerHTML=e)},async checkForUpdate(){const e=this.$("updateCheckBtn");e&&(e.disabled=!0,e.textContent="Checking\u2026");const t=await this._apiJson("/api/system/update/check");e&&(e.disabled=!1,e.textContent="Check now");const n=this.$("updateActionRow"),s=this.$("updateNotes");if(n&&(n.style.display="none"),s&&(s.style.display="none"),!t){this._setUpdateResult("Could not check for updates. Try again later.");return}this._updateCheck=t;const o=this.$("updateCurrentVersion");if(o&&t.currentVersion&&(o.textContent=`v${t.currentVersion}`),t.installKind&&t.installKind!=="git"){this._setUpdateResult(`This install can't update itself (${escapeHtml(t.installKind)}). Update with <code>npm i -g aicodeman@latest</code>.`);return}if(t.selfUpdateEnabled===!1){this._setUpdateResult("In-app updates are disabled on this server (CODEMAN_DISABLE_SELF_UPDATE=1).");return}if(t.error&&!t.updateAvailable){this._setUpdateResult(escapeHtml(t.error));return}if(t.updateAvailable&&t.latestVersion){this._setUpdateResult(`Update available: <strong>v${escapeHtml(t.latestVersion)}</strong> (current v${escapeHtml(t.currentVersion||"")})`);const i=this.$("updateActionLabel");i&&(i.textContent=`Update to v${t.latestVersion}`),n&&(n.style.display="flex");const a=this.$("updateNowBtn");a&&(a.disabled=!1,a.textContent="Update now"),s&&t.notes&&(s.style.display="block",s.textContent=t.notes)}else this._setUpdateResult(`You're up to date (v${escapeHtml(t.currentVersion||"")}).`)},async startSelfUpdate(){const e=this._updateCheck?.latestVersion?`v${this._updateCheck.latestVersion}`:"the latest release";if(!confirm(`Update Codeman to ${e}? The server will restart and this page will reload.`))return;const t=this.$("updateNowBtn");t&&(t.disabled=!0,t.textContent="Starting\u2026");const n=await this._apiPost("/api/system/update",{});if(!n||!n.ok){let i="Failed to start the update.";try{const a=await n.json();typeof a?.error=="string"&&a.error&&(i=a.error)}catch{}this._setUpdateProgress(`<span style="color:var(--danger,#e5534b)">${escapeHtml(i)}</span>`),t&&(t.disabled=!1,t.textContent="Update now");return}const s=this.$("updateActionRow");s&&(s.style.display="none");const o=this.$("updateNotes");o&&(o.style.display="none"),this._setUpdateProgress("Starting update\u2026"),this._pollUpdateStatus()},_stopUpdatePolling(){this._updatePollTimer&&(clearInterval(this._updatePollTimer),this._updatePollTimer=null)},_pollUpdateStatus(){this._stopUpdatePolling();const e=new Set(["completed","completed-needs-manual-restart","failed","idle"]),t=async()=>{let n=null;try{const s=await fetch("/api/system/update/status");if(s.ok){const o=await s.json();n=o&&o.success===!0?o.data:o}}catch{}if(!n){this._setUpdateProgress("\u21BB Restarting Codeman\u2026");return}if(!e.has(n.phase)){const s=n.message&&n.message.trim()?n.message.trim():this._updatePhaseText(n.phase);let o="";n.startedAt&&(o=` <span style="color:var(--text-secondary)">\xB7 ${Math.max(0,Math.round((Date.now()-n.startedAt)/1e3))}s</span>`),this._setUpdateProgress(`<span class="tunnel-spinner"></span> ${escapeHtml(s)}${o}`);return}if(this._stopUpdatePolling(),n.phase==="completed"){let s=`<span style="color:var(--success,#3fb950)">\u2713 Updated to v${escapeHtml(n.toVersion||"")}. Reloading\u2026</span>`;n.stashRef&&(s+=`<br><span style="color:var(--text-secondary)">Local changes stashed as <code>${escapeHtml(n.stashRef)}</code> \u2014 run <code>git stash pop</code> to restore.</span>`),this._setUpdateProgress(s),setTimeout(()=>location.reload(),2500)}else if(n.phase==="completed-needs-manual-restart")this._setUpdateProgress(`Update staged. Restart Codeman to apply:<br><code>${escapeHtml(n.manualRestartCommand||"restart codeman web")}</code>`);else if(n.phase==="failed"){let s=`<span style="color:var(--danger,#e5534b)">\u2717 ${escapeHtml(n.message||"Update failed")}.</span>`;n.error&&(s+=`<br><span style="color:var(--text-secondary)">${escapeHtml(n.error)}</span>`),s+='<br><span style="color:var(--text-secondary)">The previous version is still running.</span>',n.stashRef&&(s+=`<br><span style="color:var(--text-secondary)">Local changes stashed as <code>${escapeHtml(n.stashRef)}</code>.</span>`),this._setUpdateProgress(s);const o=this.$("updateNowBtn"),i=this.$("updateActionRow");o&&(o.disabled=!1,o.textContent="Try again"),i&&(i.style.display="flex")}};t(),this._updatePollTimer=setInterval(t,1500)},isCliAvailable(e){const t=window.__codemanCliAvailable;return!t||typeof t!="object"?!0:t[e]!==!1},applyWelcomeCliVisibility(){const e=[["welcomeClaudeBtn","claude"],["welcomeOpencodeBtn","opencode"],["welcomeAntigravityBtn","antigravity"],["welcomeGeminiBtn","gemini"],["welcomeTunnelBtn","cloudflared"]];for(const[t,n]of e){const s=document.getElementById(t);s&&(s.style.display=this.isCliAvailable(n)?"flex":"none")}},async loadTunnelStatus(){try{const t=await(await fetch("/api/tunnel/status")).json(),n=t?.success===!0?t.data:t,s=n.running&&n.url;this._tunnelUrl=s?n.url:null,this._updateTunnelUrlDisplay(this._tunnelUrl),this._updateWelcomeTunnelBtn(!!s,this._tunnelUrl),this._updateTunnelIndicator(!!s)}catch{this._tunnelUrl=null,this._updateTunnelUrlDisplay(null),this._updateWelcomeTunnelBtn(!1),this._updateTunnelIndicator(!1)}},_updateTunnelUrlRow(e,t,n,s=""){const o=document.getElementById(e),i=document.getElementById(t);if(!(!o||!i))if(n){const a=n+s;o.style.display="",i.textContent=a,i.onclick=()=>{navigator.clipboard.writeText(a).then(()=>{this.showToast(`${s?"Upload":"Tunnel"} URL copied`,"success")})}}else o.style.display="none",i.textContent="",i.onclick=null},_updateTunnelUrlDisplay(e){this._updateTunnelUrlRow("tunnelUrlRow","tunnelUrlDisplay",e),this._updateTunnelUrlRow("tunnelUploadUrlRow","tunnelUploadUrlDisplay",e,"/upload.html")},showTunnelQR(){this.closeTunnelQR();const e=document.createElement("div");e.id="tunnelQrOverlay",e.style.cssText="position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:5000;display:flex;align-items:center;justify-content:center;cursor:pointer",e.onclick=n=>{n.target===e&&this.closeTunnelQR()};const t=document.createElement("div");t.style.cssText="background:var(--bg-card);border:1px solid var(--border);border-radius:12px;padding:24px;text-align:center;max-width:340px;width:90vw;box-shadow:var(--shadow-lg);cursor:default",t.innerHTML=`
|
|
1
|
+
"use strict";Object.assign(CodemanApp.prototype,{_onHookIdlePrompt(e){e.sessionId&&this.setPendingHook(e.sessionId,"idle_prompt"),this._notifySession(e.sessionId,"warning","hook-idle","Waiting for Input",e.message||"Claude is idle and waiting for a prompt")},_onHookPermissionPrompt(e){e.sessionId&&this.setPendingHook(e.sessionId,"permission_prompt");const t=e.tool?`${e.tool}${e.command?": "+e.command:e.file?": "+e.file:""}`:"";this._notifySession(e.sessionId,"critical","hook-permission","Permission Required",t||"Claude needs tool approval to continue")},_onHookElicitationDialog(e){e.sessionId&&this.setPendingHook(e.sessionId,"elicitation_dialog"),this._notifySession(e.sessionId,"critical","hook-elicitation","Question Asked",e.question||"Claude is asking a question and waiting for your answer")},_onHookElicitationComplete(e){e.sessionId&&this.clearPendingHooks(e.sessionId,"elicitation_dialog")},_onHookElicitationResponse(e){this._onHookElicitationComplete(e)},_onHookStop(e){e.sessionId&&this.clearPendingHooks(e.sessionId),this._notifySession(e.sessionId,"info","hook-stop","Response Complete",e.reason||"Claude has finished responding")},_onHookTeammateIdle(e){const t=this.sessions.get(e.sessionId);this._notifySession(e.sessionId,"warning","hook-teammate-idle","Teammate Idle",`A teammate is idle in ${t?.name||e.sessionId}`)},_onHookTaskCompleted(e){const t=this.sessions.get(e.sessionId);this._notifySession(e.sessionId,"info","hook-task-completed","Task Completed",`A team task completed in ${t?.name||e.sessionId}`)},_onTunnelStarted(e){console.log("[Tunnel] Started:",e.url),this._tunnelUrl=e.url,this._dismissTunnelConnecting(),this._updateTunnelUrlDisplay(e.url),this._updateTunnelIndicator(!0),document.getElementById("welcomeOverlay")?.classList.contains("visible")?(this._updateWelcomeTunnelBtn(!0,e.url,!0),this.showToast("Tunnel active","success")):(this._updateWelcomeTunnelBtn(!0,e.url),this.showToast(`Tunnel active: ${e.url}`,"success"),this.showTunnelQR())},_onTunnelStopped(){console.log("[Tunnel] Stopped"),this._tunnelUrl=null,this._dismissTunnelConnecting(),this._updateTunnelUrlDisplay(null),this._updateWelcomeTunnelBtn(!1),this._updateTunnelIndicator(!1),this.closeTunnelPanel(),this.closeTunnelQR()},_onTunnelProgress(e){console.log("[Tunnel] Progress:",e.message);const t=document.getElementById("tunnelConnectingToast");t&&(t.innerHTML=`<span class="tunnel-spinner"></span> ${e.message}`);const n=document.getElementById("welcomeTunnelBtn");n?.classList.contains("connecting")&&(n.innerHTML=`<span class="tunnel-spinner"></span> ${e.message}`)},_onTunnelError(e){console.warn("[Tunnel] Error:",e.message),this._dismissTunnelConnecting(),this.showToast(`Tunnel error: ${e.message}`,"error");const t=document.getElementById("welcomeTunnelBtn");t&&(t.disabled=!1,t.classList.remove("connecting"))},_onTunnelQrRotated(e){if(e.svg){const t=document.getElementById("tunnelQrContainer");t&&(t.innerHTML=e.svg);const n=document.getElementById("welcomeQrInner");n&&(n.innerHTML=e.svg)}else this._refreshTunnelQrFromApi();this._resetQrCountdown()},_onTunnelQrRegenerated(e){if(e.svg){const t=document.getElementById("tunnelQrContainer");t&&(t.innerHTML=e.svg);const n=document.getElementById("welcomeQrInner");n&&(n.innerHTML=e.svg)}else this._refreshTunnelQrFromApi();this._resetQrCountdown()},_onTunnelQrAuthUsed(e){const n=(e.ua||"Unknown device").match(/Chrome|Firefox|Safari|Edge|Mobile/)?.[0]||"Browser";this.showToast(`Device authenticated via QR (${n}, ${e.ip}). Not you?`,"warning",{duration:1e4,action:{label:"Revoke All",onClick:()=>{fetch("/api/auth/revoke",{method:"POST",headers:{"Content-Type":"application/json"},body:"{}"}).then(()=>this.showToast("All sessions revoked","success")).catch(()=>this.showToast("Failed to revoke sessions","error"))}}})},registerServiceWorker(){"serviceWorker"in navigator&&navigator.serviceWorker.register("/sw.js").then(e=>{this._swRegistration=e,navigator.serviceWorker.addEventListener("message",t=>{if(t.data?.type==="notification-click"){const{sessionId:n,action:s,approvalId:o}=t.data;s?this.handleNotificationAction?.(s,o,n):n&&this.sessions.has(n)&&this.selectSession(n),window.focus()}}),e.pushManager.getSubscription().then(t=>{t&&(this._pushSubscription=t,this._updatePushUI(!0))})}).catch(()=>{})},async subscribeToPush(){if(!this._swRegistration){this.showToast("Service worker not available. HTTPS or localhost required.","error");return}try{const e=await this._apiJson("/api/push/vapid-key");if(!e)throw new Error("Failed to get VAPID key");const t=urlBase64ToUint8Array(e.publicKey),n=await this._swRegistration.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:t}),s=n.toJSON(),o=await this._apiJson("/api/push/subscribe",{method:"POST",body:{endpoint:s.endpoint,keys:s.keys,userAgent:navigator.userAgent,pushPreferences:this._buildPushPreferences()}});if(!o)throw new Error("Failed to register subscription");this._pushSubscription=n,this._pushSubscriptionId=o.id,localStorage.setItem("codeman-push-subscription-id",o.id),this._updatePushUI(!0),this.showToast("Push notifications enabled","success")}catch(e){this.showToast("Push subscription failed: "+(e.message||e),"error")}},async unsubscribeFromPush(){try{this._pushSubscription&&await this._pushSubscription.unsubscribe();const e=this._pushSubscriptionId||localStorage.getItem("codeman-push-subscription-id");e&&await fetch(`/api/push/subscribe/${e}`,{method:"DELETE"}).catch(()=>{}),this._pushSubscription=null,this._pushSubscriptionId=null,localStorage.removeItem("codeman-push-subscription-id"),this._updatePushUI(!1),this.showToast("Push notifications disabled","success")}catch(e){this.showToast("Failed to unsubscribe: "+(e.message||e),"error")}},async togglePushSubscription(){this._pushSubscription?await this.unsubscribeFromPush():await this.subscribeToPush()},async _syncPushPreferences(){const e=this._pushSubscriptionId||localStorage.getItem("codeman-push-subscription-id");if(e)try{await fetch(`/api/push/subscribe/${e}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({pushPreferences:this._buildPushPreferences()})})}catch{}},_buildPushPreferences(){const e={},t={"hook:permission_prompt":"eventPermissionPush","hook:elicitation_dialog":"eventQuestionPush","hook:idle_prompt":"eventIdlePush","hook:stop":"eventStopPush","respawn:blocked":"eventRespawnPush","session:ralphCompletionDetected":"eventRalphPush"};for(const[n,s]of Object.entries(t)){const o=document.getElementById(s);e[n]=o?o.checked:!0}return e["session:error"]=!0,e},_updatePushUI(e){const t=document.getElementById("pushSubscribeBtn"),n=document.getElementById("pushSubscriptionStatus");t&&(t.textContent=e?"Unsubscribe":"Subscribe"),n&&(n.textContent=e?"active":"off",n.classList.remove("granted","denied"),e&&n.classList.add("granted"))},openAppSettings(){const e=this.loadAppSettingsFromStorage();document.getElementById("appSettingsDisplayName").value=e.displayName||"Codeman",document.getElementById("appSettingsLanguage").value=e.language==="zh-CN"?"zh-CN":"en",document.getElementById("appSettingsClaudeMdPath").value=e.defaultClaudeMdPath||"",document.getElementById("appSettingsDefaultDir").value=e.defaultWorkingDir||"";const t=this.getDefaultSettings();document.getElementById("appSettingsRalphEnabled").checked=e.ralphTrackerEnabled??t.ralphTrackerEnabled??!1,document.getElementById("appSettingsShowFontControls").checked=e.showFontControls??t.showFontControls??!1,document.getElementById("appSettingsShowSystemStats").checked=e.showSystemStats??t.showSystemStats??!0,document.getElementById("appSettingsShowLifecycleLog").checked=e.showLifecycleLog??t.showLifecycleLog??!1,document.getElementById("appSettingsShowResponseViewer").checked=e.showResponseViewer??t.showResponseViewer??!1,document.getElementById("appSettingsShowFileViewerButton").checked=e.showFileViewerButton??t.showFileViewerButton??!0,document.getElementById("appSettingsShowAttachmentsButton").checked=e.showAttachmentsButton??t.showAttachmentsButton??!1,document.getElementById("appSettingsSkin").value=e.skin??t.skin??"daylight-blue",this._syncEntranceAnimSetting?.(),document.getElementById("appSettingsWebglRenderer").checked=e.webglRendererEnabled??t.webglRendererEnabled??!0;const n=document.getElementById("appSettingsWebglRendererItem");n&&(n.style.display=MobileDetection.getDeviceType()==="desktop"?"":"none"),document.getElementById("appSettingsShowMonitor").checked=e.showMonitor??t.showMonitor??!1,document.getElementById("appSettingsShowProjectInsights").checked=e.showProjectInsights??t.showProjectInsights??!1,document.getElementById("appSettingsShowFileBrowser").checked=e.showFileBrowser??t.showFileBrowser??!1,document.getElementById("appSettingsShowSubagents").checked=e.showSubagents??t.showSubagents??!1,document.getElementById("appSettingsShowUltracodeAgents").checked=e.showUltracodeAgents??t.showUltracodeAgents??!1,document.getElementById("appSettingsApprovalsInbox").checked=e.approvalsInboxEnabled===!0,document.getElementById("appSettingsReadMyMind").checked=e.readMyMindEnabled===!0,document.getElementById("appSettingsUltracodeFloatingWindows").checked=e.ultracodeFloatingWindows??t.ultracodeFloatingWindows??!1,document.getElementById("appSettingsShowMultiMonitorButton").checked=e.showMultiMonitorButton??t.showMultiMonitorButton??!1,document.getElementById("appSettingsShowPlanUsageLimits").checked=this.planUsageChipEnabled(e),document.getElementById("appSettingsShowRedrawButton").checked=e.showRedrawButton??t.showRedrawButton??!1,document.getElementById("appSettingsLineageLines").checked=e.sessionLineageLines??t.sessionLineageLines??!0;const s=document.getElementById("appSettingsLineageLinesItem");s&&(s.style.display=MobileDetection.getDeviceType()==="desktop"?"":"none"),document.getElementById("appSettingsMobileOverview").checked=e.mobileOverviewEnabled??t.mobileOverviewEnabled??!1;const o=document.getElementById("appSettingsMobileOverviewItem");o&&(o.style.display=MobileDetection.getDeviceType()==="mobile"?"":"none"),document.getElementById("appSettingsShowSessionButton").checked=e.showSessionButton??t.showSessionButton??!1,document.getElementById("appSettingsShowAwayDigestButton").checked=e.showAwayDigestButton??t.showAwayDigestButton??!1,document.getElementById("appSettingsShowCronButton").checked=e.showCronButton??t.showCronButton??!1;const i=document.getElementById("appSettingsGestureControlItem");i&&(i.style.display=window.__codemanGestureAvailable?"":"none"),document.getElementById("appSettingsGestureControl").checked=e.gestureControlEnabled??t.gestureControlEnabled??!1,document.getElementById("appSettingsSubagentTracking").checked=e.subagentTrackingEnabled??t.subagentTrackingEnabled??!0,document.getElementById("appSettingsSubagentActiveTabOnly").checked=e.subagentActiveTabOnly??t.subagentActiveTabOnly??!0,document.getElementById("appSettingsImageWatcherEnabled").checked=e.imageWatcherEnabled??t.imageWatcherEnabled??!1,document.getElementById("appSettingsTunnelEnabled").checked=e.tunnelEnabled??!1,this.loadTunnelStatus(),document.getElementById("appSettingsLocalEcho").checked=e.localEchoEnabled??MobileDetection.isTouchDevice(),document.getElementById("appSettingsTerminalWheelLocal").checked=e.terminalWheelLocalScrollback??t.terminalWheelLocalScrollback??!1,document.getElementById("appSettingsCjkInput").checked=e.cjkInputEnabled??t.cjkInputEnabled??!1,document.getElementById("appSettingsExtendedKeyboardBar").checked=e.extendedKeyboardBar??!1,document.getElementById("appSettingsTabTwoRows").checked=e.tabTwoRows??t.tabTwoRows??!1,document.getElementById("appSettingsShowTabDetachButton").checked=e.showTabDetachButton??t.showTabDetachButton??!1;const a=document.getElementById("appSettingsClaudeMode"),l=document.getElementById("allowedToolsRow");a.value=e.claudeMode||"dangerously-skip-permissions",document.getElementById("appSettingsAllowedTools").value=e.allowedTools||"",l.style.display=a.value==="allowedTools"?"":"none",a.onchange=()=>{l.style.display=a.value==="allowedTools"?"":"none"},document.getElementById("appSettingsCodexDangerouslyBypassApprovals").checked=e.codexDangerouslyBypassApprovals??!1,document.getElementById("appSettingsCodexAnimations").checked=e.codexAnimationsEnabled??!1,this._applyCodexSettingsVisibility(),document.getElementById("appSettingsAgentTeams").checked=e.agentTeamsEnabled??!1,document.getElementById("appSettingsAgentSkill").checked=e.agentSkillEnabled??!1,document.getElementById("appSettingsClaudeModel").value=e.claudeModel??"",document.getElementById("appSettingsOpusContext1m").checked=e.opusContext1mEnabled??!1,document.getElementById("appSettingsRemoteAutoReconnect").checked=e.remoteAutoReconnect??!0,document.getElementById("appSettingsThinkingEffort").value=e.thinkingEffort??"";const c=e.nice||{};document.getElementById("appSettingsNiceEnabled").checked=c.enabled??!1,document.getElementById("appSettingsNiceValue").value=c.niceValue??10,this.loadModelConfigForSettings();const d=this.notificationManager?.preferences||{};document.getElementById("appSettingsNotifEnabled").checked=d.enabled??!0,document.getElementById("appSettingsNotifBrowser").checked=d.browserNotifications??!1,document.getElementById("appSettingsNotifAudio").checked=d.audioAlerts??!1,document.getElementById("appSettingsNotifStuckMins").value=Math.round((d.stuckThresholdMs||6e5)/6e4),document.getElementById("appSettingsNotifCritical").checked=!d.muteCritical,document.getElementById("appSettingsNotifWarning").checked=!d.muteWarning,document.getElementById("appSettingsNotifInfo").checked=!d.muteInfo,document.getElementById("appSettingsPushEnabled").checked=!!this._pushSubscription,this._updatePushUI(!!this._pushSubscription);const r=d.eventTypes||{},u=r.permission_prompt||{};document.getElementById("eventPermissionEnabled").checked=u.enabled??!0,document.getElementById("eventPermissionBrowser").checked=u.browser??!0,document.getElementById("eventPermissionPush").checked=u.push??!1,document.getElementById("eventPermissionAudio").checked=u.audio??!0;const g=r.elicitation_dialog||{};document.getElementById("eventQuestionEnabled").checked=g.enabled??!0,document.getElementById("eventQuestionBrowser").checked=g.browser??!0,document.getElementById("eventQuestionPush").checked=g.push??!1,document.getElementById("eventQuestionAudio").checked=g.audio??!0;const p=r.idle_prompt||{};document.getElementById("eventIdleEnabled").checked=p.enabled??!0,document.getElementById("eventIdleBrowser").checked=p.browser??!0,document.getElementById("eventIdlePush").checked=p.push??!1,document.getElementById("eventIdleAudio").checked=p.audio??!1;const S=r.stop||{};document.getElementById("eventStopEnabled").checked=S.enabled??!1,document.getElementById("eventStopBrowser").checked=S.browser??!1,document.getElementById("eventStopPush").checked=S.push??!1,document.getElementById("eventStopAudio").checked=S.audio??!1;const y=r.respawn_cycle||{};document.getElementById("eventRespawnEnabled").checked=y.enabled??!0,document.getElementById("eventRespawnBrowser").checked=y.browser??!1,document.getElementById("eventRespawnPush").checked=y.push??!1,document.getElementById("eventRespawnAudio").checked=y.audio??!1;const b=r.ralph_complete||{};document.getElementById("eventRalphEnabled").checked=b.enabled??!0,document.getElementById("eventRalphBrowser").checked=b.browser??!0,document.getElementById("eventRalphPush").checked=b.push??!1,document.getElementById("eventRalphAudio").checked=b.audio??!0;const f=r.subagent_spawn||{};document.getElementById("eventSubagentEnabled").checked=f.enabled??!1,document.getElementById("eventSubagentBrowser").checked=f.browser??!1,document.getElementById("eventSubagentPush").checked=f.push??!1,document.getElementById("eventSubagentAudio").checked=f.audio??!1;const w=document.getElementById("notifPermissionStatus");if(w&&typeof Notification<"u"){const m=Notification.permission;w.textContent=m==="granted"?"\u2713":m==="denied"?"\u2717":"?",w.classList.remove("granted","denied"),m==="granted"?w.classList.add("granted"):m==="denied"&&w.classList.add("denied")}const h=VoiceInput._getDeepgramConfig();document.getElementById("voiceDeepgramKey").value=h.apiKey||"",document.getElementById("voiceLanguage").value=h.language||"en-US",document.getElementById("voiceKeyterms").value=h.keyterms||DEFAULT_VOICE_KEYTERMS,document.getElementById("voiceInsertMode").value=h.insertMode||"direct",document.getElementById("voiceProvider").value=h.provider||"auto",document.getElementById("appSettingsClaudeVoice").checked=e.claudeVoiceEnabled??!1;const B=document.getElementById("voiceDeepgramKey");B.type="password",document.getElementById("voiceKeyToggleBtn").textContent="Show",this._renderVoiceProviderStatus(),VoiceInput.refreshClaudeStatus().then(()=>this._renderVoiceProviderStatus()),this._initUpdatesSection(),this._initSettingsNav(),this._syncSettingsChips(),this._syncModelCards(),this._syncEffortSegment(),this.switchSettingsTab("settings-updates");const v=document.getElementById("appSettingsModal");v.classList.add("active"),this.activeFocusTrap=new FocusTrap(v),this.activeFocusTrap.activate()},_applyCodexSettingsVisibility(){const e=document.getElementById("appSettingsCodexGroup");e&&(e.style.display=window.__codemanCliAvailable?.codex===!0?"":"none")},switchSettingsTab(e){e==="settings-shortcuts"&&this.renderShortcutSettingsList?.();const t=document.getElementById("appSettingsDoc"),n=document.getElementById(e);if(t&&n&&typeof n.offsetTop=="number"){const s=document.getElementById("appSettingsJump"),o=s&&s.offsetParent?s.offsetHeight+16:6;t.scrollTop=Math.max(0,n.offsetTop-o)}this._setActiveSettingsSection(e)},_setActiveSettingsSection(e){const t=document.getElementById("appSettingsModal");if(!t||typeof t.querySelectorAll!="function")return;let n=null;t.querySelectorAll(".set-rail-item").forEach(a=>{const l=a.dataset.section===e;a.classList.toggle("active",l),l&&(n=a)}),t.querySelectorAll(".set-jump-row").forEach(a=>{a.classList.toggle("active",a.dataset.section===e)});const s=document.getElementById("appSettingsJump")?.querySelector(".set-jump-label");s&&n&&(s.textContent=n.textContent.trim());const o=document.getElementById("appSettingsJump")?.querySelector(".set-jump-ico"),i=n?.querySelector("svg");o&&i&&(o.innerHTML=i.innerHTML)},_initSettingsNav(){const e=document.getElementById("appSettingsModal"),t=document.getElementById("appSettingsDoc");if(!e||!t||typeof e.querySelectorAll!="function"||(this._buildModelCards(),this._buildEffortSegment(),this._buildSettingsJumpMenu(),e.dataset.navReady==="1"))return;e.dataset.navReady="1",e.querySelector(".set-rail-items")?.addEventListener("click",i=>{const a=i.target.closest?.(".set-rail-item");a?.dataset.section&&this.switchSettingsTab(a.dataset.section)}),document.getElementById("appSettingsJumpMenu")?.addEventListener("click",i=>{const a=i.target.closest?.(".set-jump-row");a?.dataset.section&&(this._toggleSettingsJump(!1),this.switchSettingsTab(a.dataset.section))}),document.getElementById("appSettingsJump")?.addEventListener("click",()=>this._toggleSettingsJump()),document.getElementById("appSettingsJumpVeil")?.addEventListener("click",()=>this._toggleSettingsJump(!1)),t.addEventListener("scroll",()=>{this._settingsSpyQueued||(this._settingsSpyQueued=!0,requestAnimationFrame(()=>{this._settingsSpyQueued=!1;const i=[...t.querySelectorAll(".set-section")].filter(l=>l.offsetParent!==null);if(!i.length)return;let a=i[0].id;for(const l of i)l.offsetTop-t.scrollTop<=140&&(a=l.id);this._setActiveSettingsSection(a)}))});const n=document.getElementById("appSettingsSearch");n?.addEventListener("input",()=>this._filterSettings(n.value)),e.querySelectorAll(".set-chip input").forEach(i=>{i.addEventListener("change",()=>this._syncSettingsChips())});const s=e.querySelector(".set-group-head-toggle"),o=s?.closest(".set-group-advanced");if(s&&o){const i=()=>{const a=o.classList.toggle("open");s.setAttribute("aria-expanded",a?"true":"false")};s.addEventListener("click",i),s.addEventListener("keydown",a=>{(a.key==="Enter"||a.key===" ")&&(a.preventDefault(),i())})}document.getElementById("appSettingsOpusContext1m")?.addEventListener("change",()=>this._applyModelSelection())},_buildSettingsJumpMenu(){const e=document.getElementById("appSettingsModal"),t=document.getElementById("appSettingsJumpMenu");!e||!t||(t.innerHTML="",e.querySelectorAll(".set-rail-item").forEach(n=>{const s=document.createElement("button");s.type="button",s.className="set-jump-row",s.dataset.section=n.dataset.section,s.innerHTML=n.innerHTML;const o=document.getElementById(n.dataset.section),i=o?o.querySelectorAll("input, select").length:0;if(i){const a=document.createElement("span");a.className="set-jump-count",a.textContent=String(i),s.appendChild(a)}t.appendChild(s)}))},_toggleSettingsJump(e){const t=document.getElementById("appSettingsModal");if(!t)return;const n=e===void 0?!t.classList.contains("jump-open"):e;t.classList.toggle("jump-open",n),document.getElementById("appSettingsJump")?.setAttribute("aria-expanded",n?"true":"false")},_syncSettingsChips(){document.querySelectorAll("#appSettingsModal .set-chip, #sessionOptionsModal .set-chip").forEach(e=>{e.classList.toggle("is-on",!!e.querySelector("input")?.checked)}),this._syncLayoutPreview()},_syncLayoutPreview(){const e=document.getElementById("appSettingsModal");if(!e||typeof e.querySelectorAll!="function")return;const t={header:document.getElementById("appSettingsPreviewHeader"),panel:document.getElementById("appSettingsPreviewPanels"),toolbar:document.getElementById("appSettingsPreviewToolbar"),float:document.getElementById("appSettingsPreviewFloats")};if(!t.header)return;Object.values(t).forEach(i=>{i&&(i.innerHTML="")});const n=[...e.querySelectorAll(".set-chip[data-preview]")].filter(i=>i.querySelector("input")?.checked).sort((i,a)=>(Number(i.dataset.previewOrder)||0)-(Number(a.dataset.previewOrder)||0));let s=0;for(const i of n){const a=i.dataset.preview,l=t[a];if(!l)continue;const c=i.querySelectorAll("span"),d=(c[c.length-1]?.textContent||"").trim(),r=document.createElement("span");if(r.title=d,a==="header"){const u=i.dataset.previewText;r.className=u?"set-preview-chip":"set-preview-btn",u?r.textContent=u:this._appendPreviewIcon(r,i)}else{r.className=`set-preview-${a}`,this._appendPreviewIcon(r,i);const u=document.createElement("span");u.textContent=d,r.appendChild(u)}l.appendChild(r),s++}const o=document.getElementById("appSettingsPreviewEmpty");o&&(o.hidden=s>0)},_appendPreviewIcon(e,t){const n=t.querySelector(".set-chip-ico");if(!n)return;const s=n.cloneNode(!0);s.classList.remove("set-chip-ico"),s.classList.add("set-preview-ico"),e.appendChild(s)},_buildModelCards(){const e=document.getElementById("appSettingsClaudeModel"),t=document.getElementById("appSettingsModelCards");!e||!t||t.dataset.built==="1"||!e.options||(t.innerHTML="",[...e.options].filter(n=>n.dataset.variant!=="1m").forEach(n=>{const s=document.createElement("button");s.type="button",s.className="set-modelcard",s.setAttribute("role","radio"),s.dataset.value=n.value,n.dataset.ctx==="1"&&(s.dataset.ctx="1");const o=document.createElement("span");o.className="set-mc-top";const i=document.createElement("span");i.className="set-mc-name",i.textContent=n.textContent,o.appendChild(i);const a=document.createElement("span");a.className="set-mc-dot",o.appendChild(a),s.appendChild(o);const l=document.createElement("span");if(l.className="set-mc-meta",l.textContent=n.dataset.meta||"",s.appendChild(l),n.dataset.ctx==="1"){const c=document.createElement("span");c.className="set-mc-ctx",c.textContent="1M capable",s.appendChild(c)}s.addEventListener("click",()=>{this._settingsModelBase=n.value,this._applyModelSelection()}),t.appendChild(s)}),t.dataset.built="1")},_syncModelCards(){const e=document.getElementById("appSettingsClaudeModel");if(!e)return;const t=e.value||"";if(this._settingsModelBase=t.endsWith("[1m]")?t.slice(0,-4):t,t.endsWith("[1m]")){const n=document.getElementById("appSettingsOpusContext1m");n&&(n.checked=!0)}this._applyModelSelection()},_applyModelSelection(){const e=document.getElementById("appSettingsClaudeModel"),t=document.getElementById("appSettingsModelCards");if(!e||!t)return;const n=this._settingsModelBase||"";let s=!1;t.querySelectorAll(".set-modelcard").forEach(c=>{const d=c.dataset.value===n;c.classList.toggle("selected",d),c.setAttribute("aria-checked",d?"true":"false"),d&&(s=c.dataset.ctx==="1")});const o=!!document.getElementById("appSettingsOpusContext1m")?.checked;e.value=n&&s&&o?`${n}[1m]`:n;const i=document.getElementById("appSettingsContextRow"),a=document.getElementById("appSettingsContextDesc"),l=!!n&&!s;i?.classList.toggle("set-row-disabled",l),a&&(a.textContent=l?"The selected model has no 1M variant.":n?"Available for Fable 5, Opus and Opus 4.6.":"With no model pinned, this starts new sessions on Opus with a 1M window.")},_buildEffortSegment(){const e=document.getElementById("appSettingsThinkingEffort"),t=document.getElementById("appSettingsEffortSegment");!e||!t||t.dataset.built==="1"||!e.options||(t.innerHTML="",[...e.options].forEach(n=>{const s=document.createElement("button");s.type="button",s.setAttribute("role","radio"),s.dataset.value=n.value,s.textContent=n.textContent,s.addEventListener("click",()=>{e.value=n.value,this._syncEffortSegment()}),t.appendChild(s)}),t.dataset.built="1")},_syncEffortSegment(){const e=document.getElementById("appSettingsThinkingEffort"),t=document.getElementById("appSettingsEffortSegment");!e||!t||t.querySelectorAll("button").forEach(n=>{const s=n.dataset.value===(e.value||"");n.classList.toggle("selected",s),n.setAttribute("aria-checked",s?"true":"false")})},_filterSettings(e){const t=document.getElementById("appSettingsDoc");if(!t)return;const n=(e||"").trim().toLowerCase(),s=".set-row, .set-chip, .set-modelgrid, .set-minigrid, .event-type-grid, #appSettingsShortcutsList",o=[...t.querySelectorAll(s)];let i=!1;o.forEach(l=>{if(!n){l.classList.remove("set-hit-hidden");return}const d=`${l.dataset?.search||""} ${l.textContent||""}`.toLowerCase().includes(n);l.classList.toggle("set-hit-hidden",!d),d&&(i=!0)}),t.querySelectorAll(".set-chips").forEach(l=>{const c=[...l.querySelectorAll(".set-chip")].some(d=>!d.classList.contains("set-hit-hidden"));l.classList.toggle("set-hit-hidden",!!n&&!c)}),t.querySelectorAll(".set-group").forEach(l=>{const c=[...l.querySelectorAll(s)].some(d=>!d.classList.contains("set-hit-hidden"));l.classList.toggle("set-hit-hidden",!!n&&!c),n&&c&&l.classList.add("open")}),t.querySelectorAll(".set-section").forEach(l=>{const c=[...l.querySelectorAll(".set-group")].some(d=>!d.classList.contains("set-hit-hidden"));l.classList.toggle("set-hit-hidden",!!n&&!c)}),t.querySelectorAll(".set-preview").forEach(l=>l.classList.toggle("set-hit-hidden",!!n));const a=document.getElementById("appSettingsSearchEmpty");a&&(a.hidden=!n||i),n||t.querySelectorAll(".set-group-advanced").forEach(l=>l.classList.remove("open"))},closeAppSettings(){this._toggleSettingsJump(!1),document.getElementById("appSettingsModal").classList.remove("active"),this.activeFocusTrap&&(this.activeFocusTrap.deactivate(),this.activeFocusTrap=null)},_updatePhaseText(e){return{queued:"Queued\u2026",preparing:"Preparing\u2026",stashing:"Stashing local changes\u2026",fetching:"Fetching release\u2026",checkout:"Checking out release\u2026",installing:"Installing dependencies\u2026",building:"Building\u2026",restarting:"Restarting Codeman\u2026"}[e]||e},_initUpdatesSection(){const e=this.$("updateCurrentVersion");e&&(e.textContent=(this.$("versionDisplay")?.textContent||"").trim()||"\u2014");for(const t of["updateResult","updateActionRow","updateNotes","updateProgress"]){const n=this.$(t);n&&(n.style.display="none")}this._updateCheck=null},_setUpdateResult(e){const t=this.$("updateResult");t&&(t.style.display="block",t.innerHTML=e)},_setUpdateProgress(e){const t=this.$("updateProgress");t&&(t.style.display="block",t.innerHTML=e)},async checkForUpdate(){const e=this.$("updateCheckBtn");e&&(e.disabled=!0,e.textContent="Checking\u2026");const t=await this._apiJson("/api/system/update/check");e&&(e.disabled=!1,e.textContent="Check now");const n=this.$("updateActionRow"),s=this.$("updateNotes");if(n&&(n.style.display="none"),s&&(s.style.display="none"),!t){this._setUpdateResult("Could not check for updates. Try again later.");return}this._updateCheck=t;const o=this.$("updateCurrentVersion");if(o&&t.currentVersion&&(o.textContent=`v${t.currentVersion}`),t.installKind&&t.installKind!=="git"){this._setUpdateResult(`This install can't update itself (${escapeHtml(t.installKind)}). Update with <code>npm i -g aicodeman@latest</code>.`);return}if(t.selfUpdateEnabled===!1){this._setUpdateResult("In-app updates are disabled on this server (CODEMAN_DISABLE_SELF_UPDATE=1).");return}if(t.error&&!t.updateAvailable){this._setUpdateResult(escapeHtml(t.error));return}if(t.updateAvailable&&t.latestVersion){this._setUpdateResult(`Update available: <strong>v${escapeHtml(t.latestVersion)}</strong> (current v${escapeHtml(t.currentVersion||"")})`);const i=this.$("updateActionLabel");i&&(i.textContent=`Update to v${t.latestVersion}`),n&&(n.style.display="flex");const a=this.$("updateNowBtn");a&&(a.disabled=!1,a.textContent="Update now"),s&&t.notes&&(s.style.display="block",s.textContent=t.notes)}else this._setUpdateResult(`You're up to date (v${escapeHtml(t.currentVersion||"")}).`)},async startSelfUpdate(){const e=this._updateCheck?.latestVersion?`v${this._updateCheck.latestVersion}`:"the latest release";if(!confirm(`Update Codeman to ${e}? The server will restart and this page will reload.`))return;const t=this.$("updateNowBtn");t&&(t.disabled=!0,t.textContent="Starting\u2026");const n=await this._apiPost("/api/system/update",{});if(!n||!n.ok){let i="Failed to start the update.";try{const a=await n.json();typeof a?.error=="string"&&a.error&&(i=a.error)}catch{}this._setUpdateProgress(`<span style="color:var(--danger,#e5534b)">${escapeHtml(i)}</span>`),t&&(t.disabled=!1,t.textContent="Update now");return}const s=this.$("updateActionRow");s&&(s.style.display="none");const o=this.$("updateNotes");o&&(o.style.display="none"),this._setUpdateProgress("Starting update\u2026"),this._pollUpdateStatus()},_stopUpdatePolling(){this._updatePollTimer&&(clearInterval(this._updatePollTimer),this._updatePollTimer=null)},_pollUpdateStatus(){this._stopUpdatePolling();const e=new Set(["completed","completed-needs-manual-restart","failed","idle"]),t=async()=>{let n=null;try{const s=await fetch("/api/system/update/status");if(s.ok){const o=await s.json();n=o&&o.success===!0?o.data:o}}catch{}if(!n){this._setUpdateProgress("\u21BB Restarting Codeman\u2026");return}if(!e.has(n.phase)){const s=n.message&&n.message.trim()?n.message.trim():this._updatePhaseText(n.phase);let o="";n.startedAt&&(o=` <span style="color:var(--text-secondary)">\xB7 ${Math.max(0,Math.round((Date.now()-n.startedAt)/1e3))}s</span>`),this._setUpdateProgress(`<span class="tunnel-spinner"></span> ${escapeHtml(s)}${o}`);return}if(this._stopUpdatePolling(),n.phase==="completed"){let s=`<span style="color:var(--success,#3fb950)">\u2713 Updated to v${escapeHtml(n.toVersion||"")}. Reloading\u2026</span>`;n.stashRef&&(s+=`<br><span style="color:var(--text-secondary)">Local changes stashed as <code>${escapeHtml(n.stashRef)}</code> \u2014 run <code>git stash pop</code> to restore.</span>`),this._setUpdateProgress(s),setTimeout(()=>location.reload(),2500)}else if(n.phase==="completed-needs-manual-restart")this._setUpdateProgress(`Update staged. Restart Codeman to apply:<br><code>${escapeHtml(n.manualRestartCommand||"restart codeman web")}</code>`);else if(n.phase==="failed"){let s=`<span style="color:var(--danger,#e5534b)">\u2717 ${escapeHtml(n.message||"Update failed")}.</span>`;n.error&&(s+=`<br><span style="color:var(--text-secondary)">${escapeHtml(n.error)}</span>`),s+='<br><span style="color:var(--text-secondary)">The previous version is still running.</span>',n.stashRef&&(s+=`<br><span style="color:var(--text-secondary)">Local changes stashed as <code>${escapeHtml(n.stashRef)}</code>.</span>`),this._setUpdateProgress(s);const o=this.$("updateNowBtn"),i=this.$("updateActionRow");o&&(o.disabled=!1,o.textContent="Try again"),i&&(i.style.display="flex")}};t(),this._updatePollTimer=setInterval(t,1500)},isCliAvailable(e){const t=window.__codemanCliAvailable;return!t||typeof t!="object"?!0:t[e]!==!1},applyWelcomeCliVisibility(){const e=[["welcomeClaudeBtn","claude"],["welcomeOpencodeBtn","opencode"],["welcomeAntigravityBtn","antigravity"],["welcomeGeminiBtn","gemini"],["welcomePiBtn","pi"],["welcomeTunnelBtn","cloudflared"]];for(const[t,n]of e){const s=document.getElementById(t);s&&(s.style.display=this.isCliAvailable(n)?"flex":"none")}},async loadTunnelStatus(){try{const t=await(await fetch("/api/tunnel/status")).json(),n=t?.success===!0?t.data:t,s=n.running&&n.url;this._tunnelUrl=s?n.url:null,this._updateTunnelUrlDisplay(this._tunnelUrl),this._updateWelcomeTunnelBtn(!!s,this._tunnelUrl),this._updateTunnelIndicator(!!s)}catch{this._tunnelUrl=null,this._updateTunnelUrlDisplay(null),this._updateWelcomeTunnelBtn(!1),this._updateTunnelIndicator(!1)}},_updateTunnelUrlRow(e,t,n,s=""){const o=document.getElementById(e),i=document.getElementById(t);if(!(!o||!i))if(n){const a=n+s;o.style.display="",i.textContent=a,i.onclick=()=>{navigator.clipboard.writeText(a).then(()=>{this.showToast(`${s?"Upload":"Tunnel"} URL copied`,"success")})}}else o.style.display="none",i.textContent="",i.onclick=null},_updateTunnelUrlDisplay(e){this._updateTunnelUrlRow("tunnelUrlRow","tunnelUrlDisplay",e),this._updateTunnelUrlRow("tunnelUploadUrlRow","tunnelUploadUrlDisplay",e,"/upload.html")},showTunnelQR(){this.closeTunnelQR();const e=document.createElement("div");e.id="tunnelQrOverlay",e.style.cssText="position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:5000;display:flex;align-items:center;justify-content:center;cursor:pointer",e.onclick=n=>{n.target===e&&this.closeTunnelQR()};const t=document.createElement("div");t.style.cssText="background:var(--bg-card);border:1px solid var(--border);border-radius:12px;padding:24px;text-align:center;max-width:340px;width:90vw;box-shadow:var(--shadow-lg);cursor:default",t.innerHTML=`
|
|
2
2
|
<div style="font-size:14px;font-weight:600;color:var(--text-primary);margin-bottom:16px">Scan to connect</div>
|
|
3
3
|
<div id="tunnelQrContainer" style="background:#fff;border-radius:8px;padding:16px;display:inline-block">
|
|
4
4
|
<div style="color:#666;font-size:12px">Loading...</div>
|
|
Binary file
|
|
Binary file
|