aicodeman 1.8.3 → 1.9.1
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 +80 -103
- package/README.zh-CN.md +80 -103
- package/dist/types/common.d.ts +25 -0
- package/dist/types/common.d.ts.map +1 -1
- package/dist/types/common.js +1 -0
- package/dist/types/common.js.map +1 -1
- package/dist/web/middleware/auth.d.ts.map +1 -1
- package/dist/web/middleware/auth.js +39 -1
- package/dist/web/middleware/auth.js.map +1 -1
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/app.adc992cd.js.gz +0 -0
- package/dist/web/public/constants.e7175b95.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/i18n.4dcf1b87.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +10 -7
- 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.3f91acef.js → keyboard-accessory.9936227d.js} +361 -2
- package/dist/web/public/keyboard-accessory.9936227d.js.br +0 -0
- package/dist/web/public/keyboard-accessory.9936227d.js.gz +0 -0
- package/dist/web/public/mobile-handlers.bc2b8c1d.js.gz +0 -0
- package/dist/web/public/mobile.40e2d64e.css +1 -0
- package/dist/web/public/mobile.40e2d64e.css.br +0 -0
- package/dist/web/public/mobile.40e2d64e.css.gz +0 -0
- package/dist/web/public/notification-manager.fca811fd.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/panels-ui.3910e1e2.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/respawn-ui.2d249da9.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/{session-ui.fd39c53c.js → session-ui.ab6dd9aa.js} +15 -15
- package/dist/web/public/session-ui.ab6dd9aa.js.br +0 -0
- package/dist/web/public/session-ui.ab6dd9aa.js.gz +0 -0
- package/dist/web/public/settings-ui.b8def032.js.gz +0 -0
- package/dist/web/public/styles.30e64547.css +1 -0
- package/dist/web/public/styles.30e64547.css.br +0 -0
- package/dist/web/public/styles.30e64547.css.gz +0 -0
- package/dist/web/public/subagent-windows.69b60c35.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/{terminal-ui.feaf75c2.js → terminal-ui.f3a51974.js} +1 -1
- package/dist/web/public/terminal-ui.f3a51974.js.br +0 -0
- package/dist/web/public/terminal-ui.f3a51974.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-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.085e9e73.js.gz +0 -0
- package/dist/web/public/webview-tabs.js +43 -13
- package/dist/web/public/webview-tabs.js.br +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/routes/file-routes.d.ts.map +1 -1
- package/dist/web/routes/file-routes.js +313 -8
- package/dist/web/routes/file-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +173 -99
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +10 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +30 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +13 -6
- package/dist/web/server.js.map +1 -1
- package/dist/web/webview-proxy.d.ts +23 -6
- package/dist/web/webview-proxy.d.ts.map +1 -1
- package/dist/web/webview-proxy.js +154 -6
- package/dist/web/webview-proxy.js.map +1 -1
- package/package.json +17 -6
- package/dist/web/public/keyboard-accessory.3f91acef.js.br +0 -0
- package/dist/web/public/keyboard-accessory.3f91acef.js.gz +0 -0
- package/dist/web/public/mobile.6153d275.css +0 -1
- package/dist/web/public/mobile.6153d275.css.br +0 -0
- package/dist/web/public/mobile.6153d275.css.gz +0 -0
- package/dist/web/public/session-ui.fd39c53c.js.br +0 -0
- package/dist/web/public/session-ui.fd39c53c.js.gz +0 -0
- package/dist/web/public/styles.bd210c71.css +0 -1
- package/dist/web/public/styles.bd210c71.css.br +0 -0
- package/dist/web/public/styles.bd210c71.css.gz +0 -0
- package/dist/web/public/terminal-ui.feaf75c2.js.br +0 -0
- package/dist/web/public/terminal-ui.feaf75c2.js.gz +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.assign(CodemanApp.prototype,{buildEnvOverrides(e,t){const s={};return(e?.agentTeams||t?.agentTeamsEnabled)&&(s.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS="1"),s},getEffortSetting(e){const t=e?.thinkingEffort;return["low","medium","high","xhigh","max","ultracode"].includes(t)?t:void 0},formatCasePickerLabel(e){return e?.location==="remote"&&e.remote?.hostId?`${e.name} @ ${e.remote.hostId}`:e?.location==="docker"?`${e.name} (${this.dockerCaseTag(e.docker?.hostId)})`:e?.name||""},dockerCaseTag(e){return!e||e==="default"||e==="local"||/^q-/.test(e)?"docker":e},buildCasePickerOptions(e=[]){const t=[],s=new Set;for(const a of e)!a?.name||s.has(a.name)||(s.add(a.name),t.push(a));return s.has("testcase")||t.push({name:"testcase"}),t.map(a=>{const
|
|
1
|
+
"use strict";Object.assign(CodemanApp.prototype,{buildEnvOverrides(e,t){const s={};return(e?.agentTeams||t?.agentTeamsEnabled)&&(s.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS="1"),s},getEffortSetting(e){const t=e?.thinkingEffort;return["low","medium","high","xhigh","max","ultracode"].includes(t)?t:void 0},formatCasePickerLabel(e){return e?.location==="remote"&&e.remote?.hostId?`${e.name} @ ${e.remote.hostId}`:e?.location==="docker"?`${e.name} (${this.dockerCaseTag(e.docker?.hostId)})`:e?.name||""},dockerCaseTag(e){return!e||e==="default"||e==="local"||/^q-/.test(e)?"docker":e},buildCasePickerOptions(e=[]){const t=[],s=new Set;for(const a of e)!a?.name||s.has(a.name)||(s.add(a.name),t.push(a));return s.has("testcase")||t.push({name:"testcase"}),t.map(a=>{const n=this.formatCasePickerLabel(a),o=[a.name,n,a.path,a.location,a.remote?.hostId,a.remote?.label,a.remote?.path,a.docker?.container,a.docker?.image,a.docker?.path].filter(Boolean).join(" ").toLowerCase();return{name:a.name,label:n,case:a,searchText:o}}).sort((a,n)=>a.label.localeCompare(n.label,void 0,{sensitivity:"base",numeric:!0}))},filterCasePickerOptions(e,t){const s=String(t||"").trim().toLowerCase().split(/\s+/).filter(Boolean);return s.length===0?e:e.filter(a=>s.every(n=>a.searchText.includes(n)))},getCasePickerOptions(){return this.buildCasePickerOptions(this.cases||[])},updateCasePickerInput(e){const t=document.getElementById("quickStartCaseSearch");if(!t)return;const s=this.getCasePickerOptions().find(a=>a.name===e);t.value=s?.label||e||"testcase",t.title=s?.label||t.value},renderQuickStartCaseSelectOptions(e,t){e&&(e.innerHTML=t.map(s=>`<option value="${escapeHtml(s.name)}">${escapeHtml(s.label)}</option>`).join(""))},openCasePicker(e=""){const t=document.getElementById("quickStartCaseSearch"),s=document.getElementById("quickStartCaseList");!t||!s||(this._casePickerOpen=!0,this._casePickerFilter=e,this._casePickerActiveIndex=0,t.setAttribute("aria-expanded","true"),this.renderCasePickerList())},closeCasePicker(){const e=document.getElementById("quickStartCaseSearch"),t=document.getElementById("quickStartCaseList");this._casePickerOpen=!1,this._casePickerFilter="",e?.setAttribute("aria-expanded","false"),e?.removeAttribute("aria-activedescendant"),t?.classList.add("hidden")},renderCasePickerList(){const e=document.getElementById("quickStartCaseSearch"),t=document.getElementById("quickStartCaseList"),s=document.getElementById("quickStartCase");if(!e||!t||!s)return;const a=this.filterCasePickerOptions(this.getCasePickerOptions(),this._casePickerFilter||""),n=s.value||"testcase",o=Math.max(0,a.length-1);if(this._casePickerActiveIndex=Math.min(Math.max(this._casePickerActiveIndex||0,0),o),a.length===0){t.innerHTML='<div class="case-combobox-empty">No cases match</div>',t.classList.remove("hidden"),e.removeAttribute("aria-activedescendant");return}t.innerHTML=a.map((i,r)=>{const c=r===this._casePickerActiveIndex,m=i.name===n;return`
|
|
2
2
|
<button
|
|
3
3
|
type="button"
|
|
4
4
|
id="${`quickStartCaseOption-${r}`}"
|
|
@@ -10,25 +10,25 @@
|
|
|
10
10
|
<span class="case-combobox-check">${m?"\u2713":""}</span>
|
|
11
11
|
<span class="case-combobox-option-label">${escapeHtml(i.label)}</span>
|
|
12
12
|
</button>
|
|
13
|
-
`}).join(""),t.classList.remove("hidden"),e.setAttribute("aria-activedescendant",`quickStartCaseOption-${this._casePickerActiveIndex}`)},selectQuickStartCase(e,{save:t=!0}={}){const s=document.getElementById("quickStartCase");s&&(s.value=e||"testcase",this.updateCasePickerInput(s.value),this.closeCasePicker(),this.updateDirDisplayForCase(s.value),this.updateMobileCaseLabel(s.value),t&&this.saveLastUsedCase(s.value))},setupQuickStartCasePicker(){const e=document.getElementById("quickStartCase"),t=document.getElementById("quickStartCaseSearch"),s=document.getElementById("quickStartCaseList"),a=document.getElementById("quickStartCasePicker");!e||!t||!s||!a||t.dataset.listenerAdded||(t.addEventListener("focus",()=>{t.select?.(),this.openCasePicker("")}),t.addEventListener("click",()=>{t.select?.(),this.openCasePicker("")}),t.addEventListener("input",()=>{this.openCasePicker(t.value)}),t.addEventListener("keydown",
|
|
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 o=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,o.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 i=o[this._casePickerActiveIndex||0];i&&(n.preventDefault(),this.selectQuickStartCase(i.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 o=n.target.closest?.(".case-combobox-option");o?.dataset?.case&&this.selectQuickStartCase(o.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 o=document.getElementById("quickStartCase"),i=this.getCasePickerOptions();if(this.renderQuickStartCaseSelectOptions(o,i),console.log("[loadQuickStartCases] Set options:",o.innerHTML.substring(0,200)),e)o.value=e,this.updateDirDisplayForCase(e),this.updateMobileCaseLabel(e);else if(s&&n.some(r=>r.name===s))o.value=s,this.updateDirDisplayForCase(s),this.updateMobileCaseLabel(s);else if(n.length>0){const r=n.find(c=>c.name==="testcase")||n[0];o.value=r.name,this.updateDirDisplayForCase(r.name),this.updateMobileCaseLabel(r.name)}else o.value="testcase",document.getElementById("dirDisplay").textContent="~/codeman-cases/testcase",this.updateMobileCaseLabel("testcase");this.updateCasePickerInput(o.value),this.renderCasePickerList(),this.closeCasePicker(),o.dataset.listenerAdded||(o.addEventListener("change",()=>{this.updateDirDisplayForCase(o.value),this.saveLastUsedCase(o.value),this.updateMobileCaseLabel(o.value),this.updateCasePickerInput(o.value)}),o.dataset.listenerAdded="true"),this.setupQuickStartCasePicker()}catch(s){console.error("Failed to load cases:",s)}},async updateDirDisplayForCase(e){try{const s=(await(await fetch(`/api/cases/${e}`)).json()).data;s.path&&(document.getElementById("dirDisplay").textContent=s.path,document.getElementById("dirInput").value=s.path)}catch{document.getElementById("dirDisplay").textContent=e}},async saveLastUsedCase(e){try{await fetch("/api/settings",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({lastUsedCase:e})})}catch(t){console.error("Failed to save last used case:",t)}},async quickStart(){return this.run()},async _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==="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();const s=a=>{t.contains(a.target)||(t.classList.remove("active"),document.removeEventListener("click",s))};setTimeout(()=>document.addEventListener("click",s),0)}},async _loadRunModeHistory(){const e=document.getElementById("runModeHistory");if(e){e.innerHTML='<div class="run-mode-hist-empty">Loading...</div>';try{const t=await this._fetchHistorySessions(10);if(t.length===0){e.innerHTML='<div class="run-mode-hist-empty">No history</div>';return}e.replaceChildren();for(const s of t){const a=new Date(s.lastModified),n=a.toLocaleDateString("en",{month:"short",day:"numeric"})+" "+a.toLocaleTimeString("en",{hour:"2-digit",minute:"2-digit",hour12:!1}),o=s.workingDir.replace(/^\/home\/[^/]+\//,"~/"),i=document.createElement("button");i.className="run-mode-option",i.title=s.workingDir,i.dataset.sessionId=s.sessionId,i.dataset.workingDir=s.workingDir;const r=document.createElement("span");r.className="hist-dir",r.textContent=o;const c=document.createElement("span");c.className="hist-meta",c.textContent=n,i.append(r,c),i.addEventListener("click",m=>{m.stopPropagation(),this.resumeHistorySession(s.sessionId,s.workingDir,s.name)}),e.appendChild(i)}}catch{e.innerHTML='<div class="run-mode-hist-empty">Failed to load</div>'}}},_applyRunMode(){const e=this.runMode,t=document.getElementById("runBtn"),s=t?.nextElementSibling,a=document.getElementById("runBtnLabel");t&&(t.className=`btn-toolbar btn-run mode-${e}`),s&&(s.className=`btn-toolbar btn-run-gear mode-${e}`),a&&(a.textContent=e==="opencode"?"Run OC":e==="codex"?"Run CX":e==="gemini"?"Run GM":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 o=n.name&&n.name.match(s);if(o&&o[2]===e){const i=parseInt(o[1]);i>=a&&(a=i+1)}}return a},async runClaude(){const e=document.getElementById("quickStartCase").value||"testcase",t=Math.min(20,Math.max(1,parseInt(document.getElementById("tabCount").value)||1));this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting ${t} Claude session(s) in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{let a=(await(await fetch(`/api/cases/${e}`)).json())?.data??{};if(!a.path){const C=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!C.success)throw new Error(C.error||"Failed to create case");a=C.data.case}const n=a.path;if(!n)throw new Error("Case path not found");if(a.location==="remote"||a.location==="docker"){const f=this._nextCaseSessionStartNumber(e);let C;if(a.location==="docker"){const w=this.loadAppSettingsFromStorage(),S=this.getCaseSettings(e).opusContext1m||w.opusContext1mEnabled;C=w.claudeModel||(S?"opus[1m]":"")}const E=[];let I=!1;for(let w=0;w<t;w++){const x=JSON.stringify({caseName:e,mode:"claude",sessionName:`w${f+w}-${e}`,...C!==void 0?{modelOverride:C}:{}}),S=async()=>(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:x})).json();let k=await S();if(!k.success&&k.errorCode==="CONFLICT"&&a.location==="docker"&&!I&&(I=!0,confirm(`Container config for "${e}" changed since its container was created.
|
|
14
14
|
|
|
15
|
-
Recreate the container to apply the new config? Workspace files and the conversation survive (the conversation resumes on launch).`))){const T=await(await fetch(`/api/docker-cases/${encodeURIComponent(e)}/recreate`,{method:"POST"})).json();if(!T.success)throw new Error(T.error||"Failed to recreate container");b=await S()}if(!b.success)throw new Error(b.error||"Failed to start remote Claude session");await this._ensureCreatedSessionVisible(b.data.sessionId,b.data.session),E.push(b.data.sessionId)}this.terminal.writeln(`\x1B[90m All ${t} remote session(s) ready\x1B[0m`),E[0]&&(await this.selectSession(E[0]),this.loadQuickStartCases()),this.terminal.focus();return}let n=null;const i=this._nextCaseSessionStartNumber(e),r=this.isRalphTrackerEnabledByDefault(),c=[];for(let f=0;f<t;f++)c.push(`w${i+f}-${e}`);const m=this.getCaseSettings(e),l=this.loadAppSettingsFromStorage(),u=this.buildEnvOverrides(m,l),p=Object.keys(u).length>0,d=this.getEffortSetting(l),h=m.opusContext1m||l.opusContext1mEnabled,y=l.claudeModel||(h?"opus[1m]":"");this.terminal.writeln(`\x1B[90m Creating ${t} session(s)...\x1B[0m`);const g=c.map(f=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:o,name:f,...p?{envOverrides:u}:{},...d?{effort:d}:{},...y!==void 0?{modelOverride:y}:{},statusLineTelemetry:l.showPlanUsageLimits===!0})}).then(C=>C.json())),w=await Promise.all(g),v=[];for(const f of w){if(!f.success)throw new Error(f.error);await this._ensureCreatedSessionVisible(f.data.session.id,f.data.session),v.push(f.data.session.id)}n=v[0],await Promise.all(v.map(f=>fetch(`/api/sessions/${f}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:r,disableAutoEnable:!r})}))),this.terminal.writeln(`\x1B[90m Starting ${t} session(s) in parallel...\x1B[0m`),await Promise.all(v.map(f=>fetch(`/api/sessions/${f}/interactive`,{method:"POST"}))),this.terminal.writeln(`\x1B[90m All ${t} sessions ready\x1B[0m`),n&&(await this.selectSession(n),this.loadQuickStartCases()),this.terminal.focus()}catch(s){this.terminal.writeln(`\x1B[1;31m Error: ${s.message}\x1B[0m`)}},stopClaude(){if(!this.activeSessionId)return;const e=document.querySelector(".btn-toolbar.btn-stop");e&&(this._stopConfirmTimer?(clearTimeout(this._stopConfirmTimer),this._stopConfirmTimer=null,e.innerHTML=e.dataset.origHtml,delete e.dataset.origHtml,e.classList.remove("confirming"),fetch(`/api/sessions/${this.activeSessionId}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:""})})):(e.dataset.origHtml=e.innerHTML,e.textContent="Tap again",e.classList.add("confirming"),this._stopConfirmTimer=setTimeout(()=>{this._stopConfirmTimer=null,e.dataset.origHtml&&(e.innerHTML=e.dataset.origHtml,delete e.dataset.origHtml),e.classList.remove("confirming")},2e3)))},async runShell(){const e=document.getElementById("quickStartCase").value||"testcase",t=Math.min(20,Math.max(1,parseInt(document.getElementById("shellCount").value)||1));this.terminal.clear(),this.terminal.writeln(`\x1B[1;33m Starting ${t} Shell session(s) in ${e}...\x1B[0m`),this.terminal.writeln("");try{let a=(await(await fetch(`/api/cases/${e}`)).json())?.data??{};if(!a.path){const h=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!h.success)throw new Error(h.error||"Failed to create case");a=h.data.case}const o=(this.cases||[]).find(d=>d.name===e),n=a.location==="remote"||a.location==="docker"||o?.location==="remote"||o?.location==="docker",i=a.path;if(!i)throw new Error("Case path not found");if(a.location==="remote"||a.location==="docker"){const d=this._nextCaseSessionStartNumber(e,"s"),h=[];for(let y=0;y<t;y++){const w=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"shell",sessionName:`s${d+y}-${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),h.push(w.data.sessionId)}h[0]&&await this.selectSession(h[0]),this.terminal.focus();return}const r=this._nextCaseSessionStartNumber(e,"s"),c=[];for(let d=0;d<t;d++)c.push(`s${r+d}-${e}`);const m=c.map(d=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({...n?{caseName:e}:{workingDir:i},mode:"shell",name:d})}).then(h=>h.json())),l=await Promise.all(m),u=[];for(const d of l){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 p=this.getTerminalDimensions();p&&await Promise.all(u.map(d=>fetch(`/api/sessions/${d}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(p)}))),u.length>0&&await this.selectSession(u[0]),this.terminal.focus()}catch(s){this.terminal.writeln(`\x1B[1;31m Error: ${s.message}\x1B[0m`)}},async runOpenCode(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(a=>a.name===e)?.location,s=t==="remote"||t==="docker";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting OpenCode session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!s&&!(await(await fetch("/api/opencode/status")).json()).data.available){this.terminal.writeln("\x1B[1;31m OpenCode CLI not found.\x1B[0m"),this.terminal.writeln("\x1B[90m Install with: curl -fsSL https://opencode.ai/install | bash\x1B[0m");return}const a=this.buildEnvOverrides(this.getCaseSettings(e),this.loadAppSettingsFromStorage()),n=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"opencode",sessionName:`w${this._nextCaseSessionStartNumber(e)}-${e}`,...s?{}:{openCodeConfig:{autoAllowTools:!0},...Object.keys(a).length>0?{envOverrides:a}:{}}})})).json();if(!n.success)throw new Error(n.error||"Failed to start OpenCode");await this._ensureCreatedSessionVisible(n.data.sessionId,n.data.session),n.data.sessionId&&await this.selectSession(n.data.sessionId),this.terminal.focus()}catch(a){this.terminal.writeln(`\x1B[1;31m Error: ${a.message}\x1B[0m`)}},async runCodex(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(a=>a.name===e)?.location,s=t==="remote"||t==="docker";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting Codex session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!s&&!(await(await fetch("/api/codex/status")).json()).data.available){this.terminal.writeln("\x1B[1;31m Codex CLI not found.\x1B[0m"),this.terminal.writeln("\x1B[90m Install with: npm install -g @openai/codex\x1B[0m");return}const a=this.loadAppSettingsFromStorage(),o=this.buildEnvOverrides(this.getCaseSettings(e),a),i=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"codex",sessionName:`w${this._nextCaseSessionStartNumber(e)}-${e}`,...s?{}:{codexConfig:{dangerouslyBypassApprovals:a.codexDangerouslyBypassApprovals??!1,renderMode:"hybrid"},...Object.keys(o).length>0?{envOverrides:o}:{}}})})).json();if(!i.success)throw new Error(i.error||"Failed to start Codex");await this._ensureCreatedSessionVisible(i.data.sessionId,i.data.session),i.data.sessionId&&await this.selectSession(i.data.sessionId),this.terminal.focus()}catch(a){this.terminal.writeln(`\x1B[1;31m Error: ${a.message}\x1B[0m`)}},async runGemini(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(a=>a.name===e)?.location,s=t==="remote"||t==="docker";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting Gemini session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!s&&!(await(await fetch("/api/gemini/status")).json()).data.available){this.terminal.writeln("\x1B[1;31m Gemini CLI not found.\x1B[0m"),this.terminal.writeln("\x1B[90m Install with: npm install -g @google/gemini-cli\x1B[0m");return}const a=this.buildEnvOverrides(this.getCaseSettings(e),this.loadAppSettingsFromStorage()),n=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"gemini",sessionName:`w${this._nextCaseSessionStartNumber(e)}-${e}`,...s?{}:{geminiConfig:{approvalMode:"yolo"},...Object.keys(a).length>0?{envOverrides:a}:{}}})})).json();if(!n.success)throw new Error(n.error||"Failed to start Gemini");await this._ensureCreatedSessionVisible(n.data.sessionId,n.data.session),n.data.sessionId&&await this.selectSession(n.data.sessionId),this.terminal.focus()}catch(a){this.terminal.writeln(`\x1B[1;31m Error: ${a.message}\x1B[0m`)}},openSessionOptions(e){const t=this.sessions.get(e);if(!t)return;this.editingSessionId=e;const s=t.mode==="opencode"||t.mode==="codex"||t.mode==="gemini";this.switchOptionsTab(s?"summary":"respawn");const a=document.getElementById("sessionRespawnStatus"),o=document.getElementById("modalEnableRespawnBtn"),n=document.getElementById("modalStopRespawnBtn");this.respawnStatus[e]?(a.classList.add("active"),a.querySelector(".respawn-status-text").textContent=this.respawnStatus[e].state||"Active",o.style.display="none",n.style.display=""):(a.classList.remove("active"),a.querySelector(".respawn-status-text").textContent="Not active",o.style.display="",n.style.display="none");const i=document.getElementById("sessionRespawnSection");t.mode==="claude"&&t.pid?i.style.display="":i.style.display="none";const r=t.mode==="opencode"||t.mode==="codex"||t.mode==="gemini";document.querySelectorAll("[data-claude-only]").forEach(g=>{g.style.display=r?"none":""}),this.selectDurationPreset(""),this.loadSavedRespawnConfig(e),document.getElementById("modalAutoCompactEnabled").checked=t.autoCompactEnabled??!1,document.getElementById("modalAutoCompactThreshold").value=t.autoCompactThreshold??11e4,document.getElementById("modalAutoCompactPrompt").value=t.autoCompactPrompt??"",document.getElementById("modalAutoClearEnabled").checked=t.autoClearEnabled??!1,document.getElementById("modalAutoClearThreshold").value=t.autoClearThreshold??14e4,document.getElementById("modalAutoResumeEnabled").checked=t.autoResumeEnabled??!1,this.updateAutoResumeStatus(e),document.getElementById("modalImageWatcherEnabled").checked=t.imageWatcherEnabled??!0,document.getElementById("modalFlickerFilterEnabled").checked=t.flickerFilterEnabled??!1;const m=parseSessionPrefix(t.name),l=document.getElementById("modalSessionPrefix");m?(l.textContent=m.prefix+": ",l.style.display="",document.getElementById("modalSessionName").value=m.suffix,document.getElementById("modalSessionName").placeholder="Add description..."):(l.style.display="none",l.textContent="",document.getElementById("modalSessionName").value=t.name||"",document.getElementById("modalSessionName").placeholder="Auto (directory name)");const u=t.color||"default";document.getElementById("sessionColorPicker")?.querySelectorAll(".color-swatch").forEach(g=>{g.classList.toggle("selected",g.dataset.color===u)}),this.renderPresetDropdown(),document.getElementById("respawnPresetSelect").value="",document.getElementById("presetDescriptionHint").textContent="";const d=document.querySelector('#sessionOptionsModal .modal-tab-btn[data-tab="ralph"]'),h=document.querySelector('#sessionOptionsModal .modal-tab-btn[data-tab="respawn"]');if(r?(d&&(d.style.display="none"),h&&(h.style.display="none"),this.switchOptionsTab("context")):(d&&(d.style.display=""),h&&(h.style.display="")),!r){const g=this.ralphStates.get(e);this.populateRalphForm({enabled:g?.loop?.enabled??t.ralphLoop?.enabled??!1,completionPhrase:g?.loop?.completionPhrase||t.ralphLoop?.completionPhrase||"",maxIterations:g?.loop?.maxIterations||t.ralphLoop?.maxIterations||0,maxTodos:g?.loop?.maxTodos||t.ralphLoop?.maxTodos,todoExpirationMinutes:g?.loop?.todoExpirationMinutes||t.ralphLoop?.todoExpirationMinutes})}const y=document.getElementById("sessionOptionsModal");y.classList.add("active"),this.activeFocusTrap=new FocusTrap(y),this.activeFocusTrap.activate()},async saveSessionName(){if(!this.editingSessionId)return;const e=this.sessions.get(this.editingSessionId),t=e?parseSessionPrefix(e.name):null,s=document.getElementById("modalSessionName").value.trim();let a;t?a=t.prefix+(s?": "+s:""):a=s;try{await this._apiPut(`/api/sessions/${this.editingSessionId}/name`,{name:a})}catch(o){this.showToast("Failed to save session name: "+o.message,"error")}},async autoSaveAutoCompact(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-compact`,{enabled:document.getElementById("modalAutoCompactEnabled").checked,threshold:parseInt(document.getElementById("modalAutoCompactThreshold").value)||11e4,prompt:document.getElementById("modalAutoCompactPrompt").value.trim()||void 0})}catch{}},async autoSaveAutoClear(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-clear`,{enabled:document.getElementById("modalAutoClearEnabled").checked,threshold:parseInt(document.getElementById("modalAutoClearThreshold").value)||14e4})}catch{}},async autoSaveAutoResume(){if(!this.editingSessionId)return;const e=document.getElementById("modalAutoResumeEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-resume`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.autoResumeEnabled=e,e||(t.autoResumeAt=void 0)),this.updateAutoResumeStatus(this.editingSessionId),this.showToast(`Auto-resume on usage limit ${e?"enabled":"disabled"}`,"success")}catch(t){this.showToast("Failed to toggle auto-resume: "+t.message,"error")}},updateAutoResumeStatus(e){const t=document.getElementById("autoResumeStatus");if(!t||this.editingSessionId!==e)return;const s=this.sessions.get(e);if(s?.autoResumeAt&&s.autoResumeAt>Date.now()){const a=new Date(s.autoResumeAt).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});t.textContent=`Usage limit pause active \u2014 resumes at ${a}`,t.classList.add("active")}else t.textContent="",t.classList.remove("active")},async toggleSessionImageWatcher(){if(!this.editingSessionId)return;const e=document.getElementById("modalImageWatcherEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/image-watcher`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.imageWatcherEnabled=e),this.showToast(`Image watcher ${e?"enabled":"disabled"}`,"success")}catch{this.showToast("Failed to toggle image watcher","error")}},async toggleFlickerFilter(){if(!this.editingSessionId)return;const e=document.getElementById("modalFlickerFilterEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/flicker-filter`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.flickerFilterEnabled=e),this.showToast(`Flicker filter ${e?"enabled":"disabled"}`,"success")}catch{this.showToast("Failed to toggle flicker filter","error")}},async autoSaveRespawnConfig(){if(!this.editingSessionId)return;const e={updatePrompt:document.getElementById("modalRespawnPrompt").value,sendClear:document.getElementById("modalRespawnSendClear").checked,sendInit:document.getElementById("modalRespawnSendInit").checked,kickstartPrompt:document.getElementById("modalRespawnKickstart").value.trim()||void 0,autoAcceptPrompts:document.getElementById("modalRespawnAutoAccept").checked};try{await this._apiPut(`/api/sessions/${this.editingSessionId}/respawn/config`,e)}catch{}},async loadSavedRespawnConfig(e){try{const s=await(await fetch(`/api/sessions/${e}/respawn/config`)).json();if(s.success&&s.data&&s.data.config){const a=s.data.config;document.getElementById("modalRespawnPrompt").value=a.updatePrompt||"update all the docs and CLAUDE.md",document.getElementById("modalRespawnSendClear").checked=a.sendClear??!0,document.getElementById("modalRespawnSendInit").checked=a.sendInit??!0,document.getElementById("modalRespawnKickstart").value=a.kickstartPrompt||"",document.getElementById("modalRespawnAutoAccept").checked=a.autoAcceptPrompts??!0,a.durationMinutes&&(document.querySelector(`.duration-preset-btn[data-minutes="${a.durationMinutes}"]`)?this.selectDurationPreset(String(a.durationMinutes)):(this.selectDurationPreset("custom"),document.getElementById("modalRespawnDuration").value=a.durationMinutes))}}catch{}},selectDurationPreset(e){document.querySelectorAll(".duration-preset-btn").forEach(o=>o.classList.remove("active"));const t=document.querySelector(`.duration-preset-btn[data-minutes="${e}"]`);t&&t.classList.add("active");const s=document.querySelector(".duration-custom-input"),a=document.getElementById("modalRespawnDuration");e==="custom"?(s.classList.add("visible"),a.focus()):(s.classList.remove("visible"),a.value="")},getSelectedDuration(){const e=document.querySelector(".duration-custom-input"),t=document.getElementById("modalRespawnDuration");if(e.classList.contains("visible"))return t.value?parseInt(t.value):null;{const a=document.querySelector(".duration-preset-btn.active")?.dataset.minutes;return a?parseInt(a):null}},switchOptionsTab(e){document.querySelectorAll("#sessionOptionsModal .modal-tab-btn").forEach(t=>{t.classList.toggle("active",t.dataset.tab===e)}),document.getElementById("respawn-tab").classList.toggle("hidden",e!=="respawn"),document.getElementById("context-tab").classList.toggle("hidden",e!=="context"),document.getElementById("ralph-tab").classList.toggle("hidden",e!=="ralph"),document.getElementById("summary-tab").classList.toggle("hidden",e!=="summary"),e==="summary"&&this.editingSessionId&&this.loadRunSummary(this.editingSessionId)},getRalphConfig(){return{enabled:document.getElementById("modalRalphEnabled").checked,completionPhrase:document.getElementById("modalRalphPhrase").value.trim(),maxIterations:parseInt(document.getElementById("modalRalphMaxIterations").value)||0,maxTodos:parseInt(document.getElementById("modalRalphMaxTodos").value)||50,todoExpirationMinutes:parseInt(document.getElementById("modalRalphTodoExpiration").value)||60}},populateRalphForm(e){document.getElementById("modalRalphEnabled").checked=e?.enabled??!1,document.getElementById("modalRalphPhrase").value=e?.completionPhrase||"",document.getElementById("modalRalphMaxIterations").value=e?.maxIterations||0,document.getElementById("modalRalphMaxTodos").value=e?.maxTodos||50,document.getElementById("modalRalphTodoExpiration").value=e?.todoExpirationMinutes||60},async saveRalphConfig(){if(!this.editingSessionId){this.showToast("No session selected","warning");return}const e=this.getRalphConfig();e.enabled&&this.ralphClosedSessions.delete(this.editingSessionId);try{const s=await(await fetch(`/api/sessions/${this.editingSessionId}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).json();if(s.error)throw new Error(s.error);this.showToast("Ralph config saved","success")}catch(t){this.showToast("Failed to save Ralph config: "+t.message,"error")}},startInlineRename(e){const t=this.sessions.get(e);if(!t)return;const s=document.querySelector(`.tab-name[data-session-id="${e}"]`);if(!s)return;this._inlineRenameActive=!0;const a=this.getSessionName(t),o=parseSessionPrefix(t.name),n=s.textContent;for(s.textContent="";s.firstChild;)s.removeChild(s.firstChild);if(o){const c=document.createElement("span");c.textContent=o.prefix+": ",c.style.cssText="color: var(--text-muted); font-size: 0.75rem; white-space: nowrap;",s.appendChild(c)}const i=document.createElement("input");i.type="text",i.value=o?o.suffix:t.name||"",i.placeholder=o?"Add description...":a,i.className="tab-rename-input",i.style.cssText="width: 80px; font-size: 0.75rem; padding: 2px 4px; background: var(--bg-input); border: 1px solid var(--accent); border-radius: 3px; color: var(--text); outline: none;",s.appendChild(i),i.focus(),i.select();const r=async({commit:c})=>{if(!this._inlineRenameActive)return;if(this._inlineRenameActive=!1,this._activeRename=null,!c){this.renderSessionTabs();return}const m=i.value.trim(),l=o?o.prefix+(m?": "+m:""):m;if(s.textContent=l||n,this.sessions.has(e)&&l!==t.name)try{await fetch(`/api/sessions/${e}/name`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:l})})}catch{s.textContent=n,this.showToast("Failed to rename","error")}this.renderSessionTabs()};this._activeRename={sessionId:e,cancel:()=>r({commit:!1})},i.addEventListener("blur",()=>r({commit:!0})),i.addEventListener("keydown",c=>{c.isComposing||c.keyCode===229||(c.key==="Enter"?(c.preventDefault(),i.blur()):c.key==="Escape"&&(i.value="",i.blur()))})},toggleCaseSettings(){const e=document.getElementById("caseSettingsPopover");if(e.classList.contains("hidden")){const t=document.getElementById("quickStartCase").value||"testcase",s=this.getCaseSettings(t);document.getElementById("caseAgentTeams").checked=s.agentTeams,document.getElementById("caseOpusContext1m").checked=s.opusContext1m,e.classList.remove("hidden");const a=o=>{!e.contains(o.target)&&!o.target.classList.contains("btn-case-settings")&&(e.classList.add("hidden"),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),0)}else e.classList.add("hidden")},getCaseSettings(e){try{const t=localStorage.getItem("caseSettings_"+e);if(t)return JSON.parse(t)}catch{}return{agentTeams:!1,opusContext1m:!0}},saveCaseSettings(e,t){localStorage.setItem("caseSettings_"+e,JSON.stringify(t))},onCaseSettingChanged(){const e=document.getElementById("quickStartCase").value||"testcase",t=this.getCaseSettings(e);t.agentTeams=document.getElementById("caseAgentTeams").checked,t.opusContext1m=document.getElementById("caseOpusContext1m").checked,this.saveCaseSettings(e,t);const s=document.getElementById("caseAgentTeamsMobile");s&&(s.checked=t.agentTeams);const a=document.getElementById("caseOpusContext1mMobile");a&&(a.checked=t.opusContext1m)},toggleCaseSettingsMobile(){const e=document.getElementById("caseSettingsPopoverMobile");if(e.classList.contains("hidden")){const t=document.getElementById("quickStartCase").value||"testcase",s=this.getCaseSettings(t);document.getElementById("caseAgentTeamsMobile").checked=s.agentTeams,document.getElementById("caseOpusContext1mMobile").checked=s.opusContext1m,e.classList.remove("hidden");const a=o=>{!e.contains(o.target)&&!o.target.classList.contains("btn-case-settings-mobile")&&(e.classList.add("hidden"),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),0)}else e.classList.add("hidden")},onCaseSettingChangedMobile(){const e=document.getElementById("quickStartCase").value||"testcase",t=this.getCaseSettings(e);t.agentTeams=document.getElementById("caseAgentTeamsMobile").checked,t.opusContext1m=document.getElementById("caseOpusContext1mMobile").checked,this.saveCaseSettings(e,t);const s=document.getElementById("caseAgentTeams");s&&(s.checked=t.agentTeams);const a=document.getElementById("caseOpusContext1m");a&&(a.checked=t.opusContext1m)},showCreateCaseModal(){document.getElementById("newCaseName").value="",document.getElementById("newCaseDescription").value="",document.getElementById("linkCaseName").value="",document.getElementById("linkCasePath").value="",["remoteCaseName","remoteCasePath","remoteHostId","remoteHostAddress","remoteHostUsername","remoteHostPort","remoteHostCodexCommand","remoteHostIdentityFile","remoteHostSocksProxy","remoteHostJumpHost","remoteHostExtraSshOptions"].forEach(s=>{const a=document.getElementById(s);a&&(a.value="")}),this.caseModalTab="case-create",this.switchCaseModalTab("case-create");const t=document.getElementById("createCaseModal");t.querySelectorAll(".modal-tabs .modal-tab-btn").forEach(s=>{s.onclick=()=>this.switchCaseModalTab(s.dataset.tab)}),t.querySelectorAll('input[type="text"]').forEach(s=>{s._mobileScrollWired||(s._mobileScrollWired=!0,s.addEventListener("focus",()=>{window.innerWidth<=430&&setTimeout(()=>s.scrollIntoView({behavior:"smooth",block:"center"}),300)}))}),t.classList.add("active"),document.getElementById("newCaseName").focus()},switchCaseModalTab(e){this.caseModalTab=e;const t=document.getElementById("createCaseModal");t.querySelectorAll(".modal-tabs .modal-tab-btn").forEach(a=>{a.classList.toggle("active",a.dataset.tab===e)}),t.querySelectorAll(".modal-tab-content").forEach(a=>{a.classList.toggle("hidden",a.id!==e)});const s=document.getElementById("caseModalSubmit");e==="case-manage"?(s.style.display="none",this.renderCaseManageList(),this.refreshDockerExports()):(s.style.display="",s.textContent=e==="case-create"?"Create":e==="case-remote"?"Link Remote":e==="case-docker"?"Link Docker":"Link"),e==="case-create"?document.getElementById("newCaseName").focus():e==="case-link"?document.getElementById("linkCaseName").focus():e==="case-remote"?document.getElementById("remoteCaseName").focus():e==="case-docker"&&document.getElementById("dockerCaseName").focus()},closeCreateCaseModal(){document.getElementById("createCaseModal").classList.remove("active")},async submitCaseModal(){const e=document.getElementById("caseModalSubmit"),t=e.textContent;e.classList.add("loading"),e.textContent=this.caseModalTab==="case-create"?"Creating...":"Linking...";try{this.caseModalTab==="case-create"?await this.createCase():this.caseModalTab==="case-remote"?await this.linkRemoteCase():this.caseModalTab==="case-docker"?await this.linkDockerCase():await this.linkCase()}finally{e.classList.remove("loading"),e.textContent=t}},async createCase(){const e=document.getElementById("newCaseName").value.trim(),t=document.getElementById("newCaseDescription").value.trim();if(!e){this.showToast("Please enter a case name","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}const s=document.getElementById("newCaseDocker")?.checked,a=s?"/api/cases/docker-quickcreate":"/api/cases",o=s?{name:e,description:t,...this._collectDockerQuickSettings()}:{name:e,description:t};try{const i=await(await fetch(a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)})).json();if(i.success)if(this.closeCreateCaseModal(),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e),s){const r=i.data?.capsEnforced===!1?" (resource caps advisory on this engine)":"";this.showToast(`Docker case "${e}" created${r} \u2014 starting session\u2026`,"success"),await this.runClaude()}else this.showToast(`Case "${e}" created`,"success");else this.showToast(i.error||"Failed to create case","error")}catch(n){console.error("Failed to create case:",n),this.showToast("Failed to create case: "+n.message,"error")}},applyDockerTemplate(){const e=document.getElementById("quickDockerTemplate")?.value,s={small:{m:"2g",c:"1",g:""},medium:{m:"",c:"",g:""},large:{m:"8g",c:"4",g:""},gpu:{m:"8g",c:"4",g:"all"}}[e];if(!s)return;const a=(o,n)=>{const i=document.getElementById(o);i&&(i.value=n)};a("quickDockerMemory",s.m),a("quickDockerCpus",s.c),a("quickDockerGpus",s.g)},_collectDockerQuickSettings(){const e=c=>(document.getElementById(c)?.value||"").trim(),t={},s=e("quickDockerMemory");s&&(t.memory=s);const a=e("quickDockerCpus");a&&(t.cpus=a);const o=e("quickDockerGpus");o&&o.toLowerCase()!=="none"&&(t.gpus=o);const n=document.getElementById("quickDockerNetwork")?.value;n&&n!=="bridge"&&(t.network=n);const i=e("quickDockerImage");i&&(t.image=i);const r=document.getElementById("quickDockerMountCreds");return r&&!r.checked&&(t.mountCredentials=!1),t},async linkCase(){const e=document.getElementById("linkCaseName").value.trim(),t=document.getElementById("linkCasePath").value.trim();if(!e){this.showToast("Please enter a case name","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}if(!t){this.showToast("Please enter a folder path","error");return}try{const a=await(await fetch("/api/cases/link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,path:t})})).json();a.success?(this.closeCreateCaseModal(),this.showToast(`Case "${e}" linked to ${t}`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(a.error||"Failed to link case","error")}catch(s){console.error("Failed to link case:",s),this.showToast("Failed to link case: "+s.message,"error")}},async linkRemoteCase(){const e=document.getElementById("remoteCaseName").value.trim(),t=document.getElementById("remoteCasePath").value.trim(),s=document.getElementById("remoteHostId").value.trim(),a=document.getElementById("remoteHostAddress").value.trim(),o=document.getElementById("remoteHostUsername").value.trim(),n=document.getElementById("remoteHostCodexCommand").value.trim(),i=document.getElementById("remoteHostPort").value.trim(),r=document.getElementById("remoteHostIdentityFile").value.trim(),c=document.getElementById("remoteHostSocksProxy").value.trim(),m=document.getElementById("remoteHostJumpHost").value.trim(),l=document.getElementById("remoteHostExtraSshOptions").value.split(`
|
|
16
|
-
`).map(p=>p.trim()).filter(p=>p.length>0);if(!e||!t||!s||!a||!
|
|
17
|
-
<span style="overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="${i}">${i} <span class="form-hint">(${
|
|
15
|
+
Recreate the container to apply the new config? Workspace files and the conversation survive (the conversation resumes on launch).`))){const T=await(await fetch(`/api/docker-cases/${encodeURIComponent(e)}/recreate`,{method:"POST"})).json();if(!T.success)throw new Error(T.error||"Failed to recreate container");k=await S()}if(!k.success)throw new Error(k.error||"Failed to start remote Claude session");await this._ensureCreatedSessionVisible(k.data.sessionId,k.data.session),E.push(k.data.sessionId)}this.terminal.writeln(`\x1B[90m All ${t} remote session(s) ready\x1B[0m`),E[0]&&(await this.selectSession(E[0]),this.loadQuickStartCases()),this.terminal.focus();return}let o=null;const i=this._nextCaseSessionStartNumber(e),r=this.isRalphTrackerEnabledByDefault(),c=[];for(let f=0;f<t;f++)c.push(`w${i+f}-${e}`);const m=this.getCaseSettings(e),l=this.loadAppSettingsFromStorage(),u=this.buildEnvOverrides(m,l),p=Object.keys(u).length>0,d=this.getEffortSetting(l),h=m.opusContext1m||l.opusContext1mEnabled,y=l.claudeModel||(h?"opus[1m]":"");this.terminal.writeln(`\x1B[90m Creating ${t} session(s)...\x1B[0m`);const g=c.map(f=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:n,name:f,...p?{envOverrides:u}:{},...d?{effort:d}:{},...y!==void 0?{modelOverride:y}:{},statusLineTelemetry:l.showPlanUsageLimits===!0})}).then(C=>C.json())),b=await Promise.all(g),v=[];for(const f of b){if(!f.success)throw new Error(f.error);await this._ensureCreatedSessionVisible(f.data.session.id,f.data.session),v.push(f.data.session.id)}o=v[0],await Promise.all(v.map(f=>fetch(`/api/sessions/${f}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:r,disableAutoEnable:!r})}))),this.terminal.writeln(`\x1B[90m Starting ${t} session(s) in parallel...\x1B[0m`),await Promise.all(v.map(f=>fetch(`/api/sessions/${f}/interactive`,{method:"POST"}))),this.terminal.writeln(`\x1B[90m All ${t} sessions ready\x1B[0m`),o&&(await this.selectSession(o),this.loadQuickStartCases()),this.terminal.focus()}catch(s){this.terminal.writeln(`\x1B[1;31m Error: ${s.message}\x1B[0m`)}},stopClaude(){if(!this.activeSessionId)return;const e=document.querySelector(".btn-toolbar.btn-stop");e&&(this._stopConfirmTimer?(clearTimeout(this._stopConfirmTimer),this._stopConfirmTimer=null,e.innerHTML=e.dataset.origHtml,delete e.dataset.origHtml,e.classList.remove("confirming"),fetch(`/api/sessions/${this.activeSessionId}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:""})})):(e.dataset.origHtml=e.innerHTML,e.textContent="Tap again",e.classList.add("confirming"),this._stopConfirmTimer=setTimeout(()=>{this._stopConfirmTimer=null,e.dataset.origHtml&&(e.innerHTML=e.dataset.origHtml,delete e.dataset.origHtml),e.classList.remove("confirming")},2e3)))},async runShell(){const e=document.getElementById("quickStartCase").value||"testcase",t=Math.min(20,Math.max(1,parseInt(document.getElementById("shellCount").value)||1));this.terminal.clear(),this.terminal.writeln(`\x1B[1;33m Starting ${t} Shell session(s) in ${e}...\x1B[0m`),this.terminal.writeln("");try{let a=(await(await fetch(`/api/cases/${e}`)).json())?.data??{};if(!a.path){const h=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!h.success)throw new Error(h.error||"Failed to create case");a=h.data.case}const n=(this.cases||[]).find(d=>d.name===e),o=a.location==="remote"||a.location==="docker"||n?.location==="remote"||n?.location==="docker",i=a.path;if(!i)throw new Error("Case path not found");if(a.location==="remote"||a.location==="docker"){const d=this._nextCaseSessionStartNumber(e,"s"),h=[];for(let y=0;y<t;y++){const b=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"shell",sessionName:`s${d+y}-${e}`})})).json();if(!b.success)throw new Error(b.error||"Failed to start remote shell session");await this._ensureCreatedSessionVisible(b.data.sessionId,b.data.session),h.push(b.data.sessionId)}h[0]&&await this.selectSession(h[0]),this.terminal.focus();return}const r=this._nextCaseSessionStartNumber(e,"s"),c=[];for(let d=0;d<t;d++)c.push(`s${r+d}-${e}`);const m=c.map(d=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({...o?{caseName:e}:{workingDir:i},mode:"shell",name:d})}).then(h=>h.json())),l=await Promise.all(m),u=[];for(const d of l){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 p=this.getTerminalDimensions();p&&await Promise.all(u.map(d=>fetch(`/api/sessions/${d}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(p)}))),u.length>0&&await this.selectSession(u[0]),this.terminal.focus()}catch(s){this.terminal.writeln(`\x1B[1;31m Error: ${s.message}\x1B[0m`)}},async runOpenCode(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(a=>a.name===e)?.location,s=t==="remote"||t==="docker";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting OpenCode session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!s&&!(await(await fetch("/api/opencode/status")).json()).data.available){this.terminal.writeln("\x1B[1;31m OpenCode CLI not found.\x1B[0m"),this.terminal.writeln("\x1B[90m Install with: curl -fsSL https://opencode.ai/install | bash\x1B[0m");return}const a=this.buildEnvOverrides(this.getCaseSettings(e),this.loadAppSettingsFromStorage()),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(a).length>0?{envOverrides:a}:{}}})})).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(a){this.terminal.writeln(`\x1B[1;31m Error: ${a.message}\x1B[0m`)}},async runCodex(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(a=>a.name===e)?.location,s=t==="remote"||t==="docker";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting Codex session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!s&&!(await(await fetch("/api/codex/status")).json()).data.available){this.terminal.writeln("\x1B[1;31m Codex CLI not found.\x1B[0m"),this.terminal.writeln("\x1B[90m Install with: npm install -g @openai/codex\x1B[0m");return}const a=this.loadAppSettingsFromStorage(),n=this.buildEnvOverrides(this.getCaseSettings(e),a),i=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"codex",sessionName:`w${this._nextCaseSessionStartNumber(e)}-${e}`,...s?{}:{codexConfig:{dangerouslyBypassApprovals:a.codexDangerouslyBypassApprovals??!1,renderMode:"hybrid"},...Object.keys(n).length>0?{envOverrides:n}:{}}})})).json();if(!i.success)throw new Error(i.error||"Failed to start Codex");await this._ensureCreatedSessionVisible(i.data.sessionId,i.data.session),i.data.sessionId&&await this.selectSession(i.data.sessionId),this.terminal.focus()}catch(a){this.terminal.writeln(`\x1B[1;31m Error: ${a.message}\x1B[0m`)}},async runGemini(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(a=>a.name===e)?.location,s=t==="remote"||t==="docker";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting Gemini session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!s&&!(await(await fetch("/api/gemini/status")).json()).data.available){this.terminal.writeln("\x1B[1;31m Gemini CLI not found.\x1B[0m"),this.terminal.writeln("\x1B[90m Install with: npm install -g @google/gemini-cli\x1B[0m");return}const a=this.buildEnvOverrides(this.getCaseSettings(e),this.loadAppSettingsFromStorage()),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(a).length>0?{envOverrides:a}:{}}})})).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(a){this.terminal.writeln(`\x1B[1;31m Error: ${a.message}\x1B[0m`)}},openSessionOptions(e){const t=this.sessions.get(e);if(!t)return;this.editingSessionId=e;const s=t.mode==="opencode"||t.mode==="codex"||t.mode==="gemini";this.switchOptionsTab(s?"summary":"respawn");const a=document.getElementById("sessionRespawnStatus"),n=document.getElementById("modalEnableRespawnBtn"),o=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",o.style.display=""):(a.classList.remove("active"),a.querySelector(".respawn-status-text").textContent="Not active",n.style.display="",o.style.display="none");const i=document.getElementById("sessionRespawnSection");t.mode==="claude"&&t.pid?i.style.display="":i.style.display="none";const r=t.mode==="opencode"||t.mode==="codex"||t.mode==="gemini";document.querySelectorAll("[data-claude-only]").forEach(g=>{g.style.display=r?"none":""}),this.selectDurationPreset(""),this.loadSavedRespawnConfig(e),document.getElementById("modalAutoCompactEnabled").checked=t.autoCompactEnabled??!1,document.getElementById("modalAutoCompactThreshold").value=t.autoCompactThreshold??11e4,document.getElementById("modalAutoCompactPrompt").value=t.autoCompactPrompt??"",document.getElementById("modalAutoClearEnabled").checked=t.autoClearEnabled??!1,document.getElementById("modalAutoClearThreshold").value=t.autoClearThreshold??14e4,document.getElementById("modalAutoResumeEnabled").checked=t.autoResumeEnabled??!1,this.updateAutoResumeStatus(e),document.getElementById("modalImageWatcherEnabled").checked=t.imageWatcherEnabled??!0,document.getElementById("modalFlickerFilterEnabled").checked=t.flickerFilterEnabled??!1;const m=parseSessionPrefix(t.name),l=document.getElementById("modalSessionPrefix");m?(l.textContent=m.prefix+": ",l.style.display="",document.getElementById("modalSessionName").value=m.suffix,document.getElementById("modalSessionName").placeholder="Add description..."):(l.style.display="none",l.textContent="",document.getElementById("modalSessionName").value=t.name||"",document.getElementById("modalSessionName").placeholder="Auto (directory name)");const u=t.color||"default";document.getElementById("sessionColorPicker")?.querySelectorAll(".color-swatch").forEach(g=>{g.classList.toggle("selected",g.dataset.color===u)}),this.renderPresetDropdown(),document.getElementById("respawnPresetSelect").value="",document.getElementById("presetDescriptionHint").textContent="";const d=document.querySelector('#sessionOptionsModal .modal-tab-btn[data-tab="ralph"]'),h=document.querySelector('#sessionOptionsModal .modal-tab-btn[data-tab="respawn"]');if(r?(d&&(d.style.display="none"),h&&(h.style.display="none"),this.switchOptionsTab("context")):(d&&(d.style.display=""),h&&(h.style.display="")),!r){const g=this.ralphStates.get(e);this.populateRalphForm({enabled:g?.loop?.enabled??t.ralphLoop?.enabled??!1,completionPhrase:g?.loop?.completionPhrase||t.ralphLoop?.completionPhrase||"",maxIterations:g?.loop?.maxIterations||t.ralphLoop?.maxIterations||0,maxTodos:g?.loop?.maxTodos||t.ralphLoop?.maxTodos,todoExpirationMinutes:g?.loop?.todoExpirationMinutes||t.ralphLoop?.todoExpirationMinutes})}const y=document.getElementById("sessionOptionsModal");y.classList.add("active"),this.activeFocusTrap=new FocusTrap(y),this.activeFocusTrap.activate()},async saveSessionName(){if(!this.editingSessionId)return;const e=this.sessions.get(this.editingSessionId),t=e?parseSessionPrefix(e.name):null,s=document.getElementById("modalSessionName").value.trim();let a;t?a=t.prefix+(s?": "+s:""):a=s;try{await this._apiPut(`/api/sessions/${this.editingSessionId}/name`,{name:a})}catch(n){this.showToast("Failed to save session name: "+n.message,"error")}},async autoSaveAutoCompact(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-compact`,{enabled:document.getElementById("modalAutoCompactEnabled").checked,threshold:parseInt(document.getElementById("modalAutoCompactThreshold").value)||11e4,prompt:document.getElementById("modalAutoCompactPrompt").value.trim()||void 0})}catch{}},async autoSaveAutoClear(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-clear`,{enabled:document.getElementById("modalAutoClearEnabled").checked,threshold:parseInt(document.getElementById("modalAutoClearThreshold").value)||14e4})}catch{}},async autoSaveAutoResume(){if(!this.editingSessionId)return;const e=document.getElementById("modalAutoResumeEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-resume`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.autoResumeEnabled=e,e||(t.autoResumeAt=void 0)),this.updateAutoResumeStatus(this.editingSessionId),this.showToast(`Auto-resume on usage limit ${e?"enabled":"disabled"}`,"success")}catch(t){this.showToast("Failed to toggle auto-resume: "+t.message,"error")}},updateAutoResumeStatus(e){const t=document.getElementById("autoResumeStatus");if(!t||this.editingSessionId!==e)return;const s=this.sessions.get(e);if(s?.autoResumeAt&&s.autoResumeAt>Date.now()){const a=new Date(s.autoResumeAt).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});t.textContent=`Usage limit pause active \u2014 resumes at ${a}`,t.classList.add("active")}else t.textContent="",t.classList.remove("active")},async toggleSessionImageWatcher(){if(!this.editingSessionId)return;const e=document.getElementById("modalImageWatcherEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/image-watcher`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.imageWatcherEnabled=e),this.showToast(`Image watcher ${e?"enabled":"disabled"}`,"success")}catch{this.showToast("Failed to toggle image watcher","error")}},async toggleFlickerFilter(){if(!this.editingSessionId)return;const e=document.getElementById("modalFlickerFilterEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/flicker-filter`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.flickerFilterEnabled=e),this.showToast(`Flicker filter ${e?"enabled":"disabled"}`,"success")}catch{this.showToast("Failed to toggle flicker filter","error")}},async autoSaveRespawnConfig(){if(!this.editingSessionId)return;const e={updatePrompt:document.getElementById("modalRespawnPrompt").value,sendClear:document.getElementById("modalRespawnSendClear").checked,sendInit:document.getElementById("modalRespawnSendInit").checked,kickstartPrompt:document.getElementById("modalRespawnKickstart").value.trim()||void 0,autoAcceptPrompts:document.getElementById("modalRespawnAutoAccept").checked};try{await this._apiPut(`/api/sessions/${this.editingSessionId}/respawn/config`,e)}catch{}},async loadSavedRespawnConfig(e){try{const s=await(await fetch(`/api/sessions/${e}/respawn/config`)).json();if(s.success&&s.data&&s.data.config){const a=s.data.config;document.getElementById("modalRespawnPrompt").value=a.updatePrompt||"update all the docs and CLAUDE.md",document.getElementById("modalRespawnSendClear").checked=a.sendClear??!0,document.getElementById("modalRespawnSendInit").checked=a.sendInit??!0,document.getElementById("modalRespawnKickstart").value=a.kickstartPrompt||"",document.getElementById("modalRespawnAutoAccept").checked=a.autoAcceptPrompts??!0,a.durationMinutes&&(document.querySelector(`.duration-preset-btn[data-minutes="${a.durationMinutes}"]`)?this.selectDurationPreset(String(a.durationMinutes)):(this.selectDurationPreset("custom"),document.getElementById("modalRespawnDuration").value=a.durationMinutes))}}catch{}},selectDurationPreset(e){document.querySelectorAll(".duration-preset-btn").forEach(n=>n.classList.remove("active"));const t=document.querySelector(`.duration-preset-btn[data-minutes="${e}"]`);t&&t.classList.add("active");const s=document.querySelector(".duration-custom-input"),a=document.getElementById("modalRespawnDuration");e==="custom"?(s.classList.add("visible"),a.focus()):(s.classList.remove("visible"),a.value="")},getSelectedDuration(){const e=document.querySelector(".duration-custom-input"),t=document.getElementById("modalRespawnDuration");if(e.classList.contains("visible"))return t.value?parseInt(t.value):null;{const a=document.querySelector(".duration-preset-btn.active")?.dataset.minutes;return a?parseInt(a):null}},switchOptionsTab(e){document.querySelectorAll("#sessionOptionsModal .modal-tab-btn").forEach(t=>{t.classList.toggle("active",t.dataset.tab===e)}),document.getElementById("respawn-tab").classList.toggle("hidden",e!=="respawn"),document.getElementById("context-tab").classList.toggle("hidden",e!=="context"),document.getElementById("ralph-tab").classList.toggle("hidden",e!=="ralph"),document.getElementById("summary-tab").classList.toggle("hidden",e!=="summary"),e==="summary"&&this.editingSessionId&&this.loadRunSummary(this.editingSessionId)},getRalphConfig(){return{enabled:document.getElementById("modalRalphEnabled").checked,completionPhrase:document.getElementById("modalRalphPhrase").value.trim(),maxIterations:parseInt(document.getElementById("modalRalphMaxIterations").value)||0,maxTodos:parseInt(document.getElementById("modalRalphMaxTodos").value)||50,todoExpirationMinutes:parseInt(document.getElementById("modalRalphTodoExpiration").value)||60}},populateRalphForm(e){document.getElementById("modalRalphEnabled").checked=e?.enabled??!1,document.getElementById("modalRalphPhrase").value=e?.completionPhrase||"",document.getElementById("modalRalphMaxIterations").value=e?.maxIterations||0,document.getElementById("modalRalphMaxTodos").value=e?.maxTodos||50,document.getElementById("modalRalphTodoExpiration").value=e?.todoExpirationMinutes||60},async saveRalphConfig(){if(!this.editingSessionId){this.showToast("No session selected","warning");return}const e=this.getRalphConfig();e.enabled&&this.ralphClosedSessions.delete(this.editingSessionId);try{const s=await(await fetch(`/api/sessions/${this.editingSessionId}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).json();if(s.error)throw new Error(s.error);this.showToast("Ralph config saved","success")}catch(t){this.showToast("Failed to save Ralph config: "+t.message,"error")}},startInlineRename(e){const t=this.sessions.get(e);if(!t)return;const s=document.querySelector(`.tab-name[data-session-id="${e}"]`);if(!s)return;this._inlineRenameActive=!0;const a=this.getSessionName(t),n=parseSessionPrefix(t.name),o=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 i=document.createElement("input");i.type="text",i.value=n?n.suffix:t.name||"",i.placeholder=n?"Add description...":a,i.className="tab-rename-input",i.style.cssText="width: 80px; font-size: 0.75rem; padding: 2px 4px; background: var(--bg-input); border: 1px solid var(--accent); border-radius: 3px; color: var(--text); outline: none;",s.appendChild(i),i.focus(),i.select();const r=async({commit:c})=>{if(!this._inlineRenameActive)return;if(this._inlineRenameActive=!1,this._activeRename=null,!c){this.renderSessionTabs();return}const m=i.value.trim(),l=n?n.prefix+(m?": "+m:""):m;if(s.textContent=l||o,this.sessions.has(e)&&l!==t.name)try{await fetch(`/api/sessions/${e}/name`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:l})})}catch{s.textContent=o,this.showToast("Failed to rename","error")}this.renderSessionTabs()};this._activeRename={sessionId:e,cancel:()=>r({commit:!1})},i.addEventListener("blur",()=>r({commit:!0})),i.addEventListener("keydown",c=>{c.isComposing||c.keyCode===229||(c.key==="Enter"?(c.preventDefault(),i.blur()):c.key==="Escape"&&(i.value="",i.blur()))})},toggleCaseSettings(){const e=document.getElementById("caseSettingsPopover");if(e.classList.contains("hidden")){const t=document.getElementById("quickStartCase").value||"testcase",s=this.getCaseSettings(t);document.getElementById("caseAgentTeams").checked=s.agentTeams,document.getElementById("caseOpusContext1m").checked=s.opusContext1m,e.classList.remove("hidden");const a=n=>{!e.contains(n.target)&&!n.target.classList.contains("btn-case-settings")&&(e.classList.add("hidden"),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),0)}else e.classList.add("hidden")},getCaseSettings(e){try{const t=localStorage.getItem("caseSettings_"+e);if(t)return JSON.parse(t)}catch{}return{agentTeams:!1,opusContext1m:!0}},saveCaseSettings(e,t){localStorage.setItem("caseSettings_"+e,JSON.stringify(t))},onCaseSettingChanged(){const e=document.getElementById("quickStartCase").value||"testcase",t=this.getCaseSettings(e);t.agentTeams=document.getElementById("caseAgentTeams").checked,t.opusContext1m=document.getElementById("caseOpusContext1m").checked,this.saveCaseSettings(e,t);const s=document.getElementById("caseAgentTeamsMobile");s&&(s.checked=t.agentTeams);const a=document.getElementById("caseOpusContext1mMobile");a&&(a.checked=t.opusContext1m)},toggleCaseSettingsMobile(){const e=document.getElementById("caseSettingsPopoverMobile");if(e.classList.contains("hidden")){const t=document.getElementById("quickStartCase").value||"testcase",s=this.getCaseSettings(t);document.getElementById("caseAgentTeamsMobile").checked=s.agentTeams,document.getElementById("caseOpusContext1mMobile").checked=s.opusContext1m,e.classList.remove("hidden");const a=n=>{!e.contains(n.target)&&!n.target.classList.contains("btn-case-settings-mobile")&&(e.classList.add("hidden"),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),0)}else e.classList.add("hidden")},onCaseSettingChangedMobile(){const e=document.getElementById("quickStartCase").value||"testcase",t=this.getCaseSettings(e);t.agentTeams=document.getElementById("caseAgentTeamsMobile").checked,t.opusContext1m=document.getElementById("caseOpusContext1mMobile").checked,this.saveCaseSettings(e,t);const s=document.getElementById("caseAgentTeams");s&&(s.checked=t.agentTeams);const a=document.getElementById("caseOpusContext1m");a&&(a.checked=t.opusContext1m)},showCreateCaseModal(){document.getElementById("newCaseName").value="",document.getElementById("newCaseDescription").value="",document.getElementById("linkCaseName").value="",document.getElementById("linkCasePath").value="",["remoteCaseName","remoteCasePath","remoteHostId","remoteHostAddress","remoteHostUsername","remoteHostPort","remoteHostCodexCommand","remoteHostIdentityFile","remoteHostSocksProxy","remoteHostJumpHost","remoteHostExtraSshOptions"].forEach(s=>{const a=document.getElementById(s);a&&(a.value="")}),this.caseModalTab="case-create",this.switchCaseModalTab("case-create");const t=document.getElementById("createCaseModal");t.querySelectorAll(".modal-tabs .modal-tab-btn").forEach(s=>{s.onclick=()=>this.switchCaseModalTab(s.dataset.tab)}),t.querySelectorAll('input[type="text"]').forEach(s=>{s._mobileScrollWired||(s._mobileScrollWired=!0,s.addEventListener("focus",()=>{window.innerWidth<=430&&setTimeout(()=>s.scrollIntoView({behavior:"smooth",block:"center"}),300)}))}),t.classList.add("active"),document.getElementById("newCaseName").focus()},switchCaseModalTab(e){this.caseModalTab=e;const t=document.getElementById("createCaseModal");t.querySelectorAll(".modal-tabs .modal-tab-btn").forEach(a=>{a.classList.toggle("active",a.dataset.tab===e)}),t.querySelectorAll(".modal-tab-content").forEach(a=>{a.classList.toggle("hidden",a.id!==e)});const s=document.getElementById("caseModalSubmit");e==="case-manage"?(s.style.display="none",this.renderCaseManageList(),this.refreshDockerExports()):(s.style.display="",s.textContent=e==="case-create"?"Create":e==="case-remote"?"Link Remote":e==="case-docker"?"Link Docker":"Link"),e==="case-create"?document.getElementById("newCaseName").focus():e==="case-link"?document.getElementById("linkCaseName").focus():e==="case-remote"?document.getElementById("remoteCaseName").focus():e==="case-docker"&&document.getElementById("dockerCaseName").focus()},closeCreateCaseModal(){document.getElementById("createCaseModal").classList.remove("active")},async submitCaseModal(){const e=document.getElementById("caseModalSubmit"),t=e.textContent;e.classList.add("loading"),e.textContent=this.caseModalTab==="case-create"?"Creating...":"Linking...";try{this.caseModalTab==="case-create"?await this.createCase():this.caseModalTab==="case-remote"?await this.linkRemoteCase():this.caseModalTab==="case-docker"?await this.linkDockerCase():await this.linkCase()}finally{e.classList.remove("loading"),e.textContent=t}},async createCase(){const e=document.getElementById("newCaseName").value.trim(),t=document.getElementById("newCaseDescription").value.trim();if(!e){this.showToast("Please enter a case name","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}const s=document.getElementById("newCaseDocker")?.checked,a=s?"/api/cases/docker-quickcreate":"/api/cases",n=s?{name:e,description:t,...this._collectDockerQuickSettings()}:{name:e,description:t};try{const i=await(await fetch(a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json();if(i.success)if(this.closeCreateCaseModal(),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e),s){const r=i.data?.capsEnforced===!1?" (resource caps advisory on this engine)":"";this.showToast(`Docker case "${e}" created${r} \u2014 starting session\u2026`,"success"),await this.runClaude()}else this.showToast(`Case "${e}" created`,"success");else this.showToast(i.error||"Failed to create case","error")}catch(o){console.error("Failed to create case:",o),this.showToast("Failed to create case: "+o.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,o)=>{const i=document.getElementById(n);i&&(i.value=o)};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 o=document.getElementById("quickDockerNetwork")?.value;o&&o!=="bridge"&&(t.network=o);const i=e("quickDockerImage");i&&(t.image=i);const r=document.getElementById("quickDockerMountCreds");return r&&!r.checked&&(t.mountCredentials=!1),t},async linkCase(){const e=document.getElementById("linkCaseName").value.trim(),t=document.getElementById("linkCasePath").value.trim();if(!e){this.showToast("Please enter a case name","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}if(!t){this.showToast("Please enter a folder path","error");return}try{const a=await(await fetch("/api/cases/link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,path:t})})).json();a.success?(this.closeCreateCaseModal(),this.showToast(`Case "${e}" linked to ${t}`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(a.error||"Failed to link case","error")}catch(s){console.error("Failed to link case:",s),this.showToast("Failed to link case: "+s.message,"error")}},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(),o=document.getElementById("remoteHostCodexCommand").value.trim(),i=document.getElementById("remoteHostPort").value.trim(),r=document.getElementById("remoteHostIdentityFile").value.trim(),c=document.getElementById("remoteHostSocksProxy").value.trim(),m=document.getElementById("remoteHostJumpHost").value.trim(),l=document.getElementById("remoteHostExtraSshOptions").value.split(`
|
|
16
|
+
`).map(p=>p.trim()).filter(p=>p.length>0);if(!e||!t||!s||!a||!n){this.showToast("Please complete all required remote fields","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)||!/^[a-zA-Z0-9_-]+$/.test(s)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}if(!t.startsWith("/")){this.showToast("Remote path must be absolute","error");return}let u;if(i&&(u=Number(i),!Number.isInteger(u)||u<1||u>65535)){this.showToast("SSH port must be a number between 1 and 65535","error");return}try{const p={id:s,label:s,host:a,username:n,...u?{port:u}:{},...r?{identityFile:r}:{},...c?{socksProxy:c}:{},...m?{jumpHost:m}:{},...l.length?{extraSshOptions:l}:{},...o?{commands:{codex:o}}:{}},h=await(await fetch("/api/remote-hosts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(p)})).json();if(!h.success&&h.errorCode!=="ALREADY_EXISTS")throw new Error(h.error||"Failed to save remote host");const g=await(await fetch("/api/cases/remote-link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,hostId:s,remotePath:t})})).json();g.success?(this.closeCreateCaseModal(),this.showToast(`Remote case "${e}" linked`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(g.error||"Failed to link remote case","error")}catch(p){console.error("Failed to link remote case:",p),this.showToast("Failed to link remote case: "+p.message,"error")}},async linkDockerCase(){const e=document.getElementById("dockerCaseName").value.trim(),t=document.getElementById("dockerWorkspacePath").value.trim(),s=document.getElementById("dockerHostId").value.trim()||"local",a=document.getElementById("dockerImage").value.trim()||"codeman/agent:base",n=document.getElementById("dockerNetwork").value,o=document.getElementById("dockerMemory").value.trim(),i=document.getElementById("dockerCpus").value.trim(),r=document.getElementById("dockerMountCredentials").checked,c=document.getElementById("dockerResumeOnStart").checked,m=document.getElementById("dockerLinkStatus");if(!e||!t){this.showToast("Please enter a case name and workspace path","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)||!/^[a-zA-Z0-9_-]+$/.test(s)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}if(!t.startsWith("/")){this.showToast("Workspace path must be absolute","error");return}try{m&&(m.textContent="Checking docker daemon + base image...");const l={};o&&(l.memory=o),i&&(l.cpus=i);const u={id:s,label:s,image:a,network:n,mountCredentials:r,resumeOnStart:c,...Object.keys(l).length?{resources:l}:{}};let p=await fetch("/api/docker-hosts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(u)}),d=await p.json();if(!d.success&&d.errorCode==="ALREADY_EXISTS"&&(p=await fetch(`/api/docker-hosts/${encodeURIComponent(s)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(u)}),d=await p.json()),!d.success)throw new Error(d.error||"Failed to save docker host");const y=await(await fetch("/api/cases/docker-link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,hostId:s,hostWorkspacePath:t})})).json();if(y.success){this.closeCreateCaseModal();const g=y.data?.capsEnforced===!1?" (resource caps are advisory on this engine)":"";this.showToast(`Docker case "${e}" linked${g}`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)}else m&&(m.textContent=y.error||"Failed to link docker case"),this.showToast(y.error||"Failed to link docker case","error")}catch(l){console.error("Failed to link docker case:",l),m&&(m.textContent=l.message),this.showToast("Failed to link docker case: "+l.message,"error")}},async refreshDockerExports(){const e=document.getElementById("dockerExportsList");if(e)try{const a=(await(await fetch("/api/docker-exports")).json())?.data?.exports||[];if(a.length===0){e.innerHTML='<span class="form-hint">No exports yet. Export a docker case from its tab.</span>';return}e.innerHTML=a.map(n=>{const o=(n.sizeBytes/1e6).toFixed(1),i=escapeHtml(n.name);return`<div class="case-manage-item" style="display:flex; align-items:center; gap:8px; justify-content:space-between;">
|
|
17
|
+
<span style="overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="${i}">${i} <span class="form-hint">(${o} MB)</span></span>
|
|
18
18
|
<span style="flex-shrink:0;">
|
|
19
|
-
<a class="btn-toolbar" href="/api/docker-exports/${encodeURIComponent(
|
|
19
|
+
<a class="btn-toolbar" href="/api/docker-exports/${encodeURIComponent(n.name)}" download>Download</a>
|
|
20
20
|
<button class="btn-toolbar" onclick="app.importDockerBundle('${i.replace(/'/g,"\\'")}')">Import</button>
|
|
21
21
|
<button class="btn-toolbar" onclick="app.deleteDockerExport('${i.replace(/'/g,"\\'")}')">Delete</button>
|
|
22
22
|
</span>
|
|
23
|
-
</div>`}).join("")}catch(t){e.innerHTML=`<span class="form-hint">Failed to load exports: ${t.message}</span>`}},async exportDockerCaseBundle(e,t="full"){try{const a=await(await fetch(`/api/docker-cases/${encodeURIComponent(e)}/export`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({mode:t})})).json();a.success?this.showToast(`Exporting "${e}" (${t})... you'll be notified when the bundle is ready`,"info"):this.showToast(a.error||"Export failed","error")}catch(s){this.showToast("Export failed: "+s.message,"error")}},async importDockerBundle(e){const t=prompt("New case name for the imported bundle:",e.split("-")[0]+"-imported");if(!t)return;const s=prompt("Absolute host directory to restore the workspace into:","");if(s)try{const
|
|
24
|
-
`).map(l=>l.trim()).filter(l=>l.length>0);let m;if(a){const l=Number(a);Number.isInteger(l)&&l>=1&&l<=65535&&(m=l)}return{id:e,label:e,host:t,username:s,...m?{port:m}:{},...
|
|
23
|
+
</div>`}).join("")}catch(t){e.innerHTML=`<span class="form-hint">Failed to load exports: ${t.message}</span>`}},async exportDockerCaseBundle(e,t="full"){try{const a=await(await fetch(`/api/docker-cases/${encodeURIComponent(e)}/export`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({mode:t})})).json();a.success?this.showToast(`Exporting "${e}" (${t})... you'll be notified when the bundle is ready`,"info"):this.showToast(a.error||"Export failed","error")}catch(s){this.showToast("Export failed: "+s.message,"error")}},async importDockerBundle(e){const t=prompt("New case name for the imported bundle:",e.split("-")[0]+"-imported");if(!t)return;const s=prompt("Absolute host directory to restore the workspace into:","");if(s)try{const n=await(await fetch("/api/docker-cases/import",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({bundle:e,newCaseName:t,destWorkspacePath:s})})).json();n.success?(this.showToast(`Imported as "${t}"`,"success"),await this.loadQuickStartCases(t)):this.showToast(n.error||"Import failed","error")}catch(a){this.showToast("Import failed: "+a.message,"error")}},async deleteDockerExport(e){if(confirm(`Delete export bundle "${e}"?`))try{const s=await(await fetch(`/api/docker-exports/${encodeURIComponent(e)}`,{method:"DELETE"})).json();s.success?(this.showToast("Export deleted","success"),this.refreshDockerExports()):this.showToast(s.error||"Delete failed","error")}catch(t){this.showToast("Delete failed: "+t.message,"error")}},_readRemoteHostFromForm(){const e=document.getElementById("remoteHostId").value.trim(),t=document.getElementById("remoteHostAddress").value.trim(),s=document.getElementById("remoteHostUsername").value.trim(),a=document.getElementById("remoteHostPort").value.trim(),n=document.getElementById("remoteHostIdentityFile").value.trim(),o=document.getElementById("remoteHostSocksProxy").value.trim(),i=document.getElementById("remoteHostJumpHost").value.trim(),r=document.getElementById("remoteHostCodexCommand").value.trim(),c=document.getElementById("remoteHostExtraSshOptions").value.split(`
|
|
24
|
+
`).map(l=>l.trim()).filter(l=>l.length>0);let m;if(a){const l=Number(a);Number.isInteger(l)&&l>=1&&l<=65535&&(m=l)}return{id:e,label:e,host:t,username:s,...m?{port:m}:{},...n?{identityFile:n}:{},...o?{socksProxy:o}:{},...i?{jumpHost:i}:{},...c.length?{extraSshOptions:c}:{},...r?{commands:{codex:r}}:{}}},async discoverRemoteSessions(){const e=document.getElementById("remoteDiscoverResults"),t=document.getElementById("remoteDiscoverBtn"),s=this._readRemoteHostFromForm();if(!s.id||!s.host||!s.username){this.showToast("Fill in Host ID, address, and username first","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(s.id)){this.showToast("Invalid Host ID. Use letters, numbers, hyphens, underscores.","error");return}t&&(t.disabled=!0),e&&(e.innerHTML='<div class="form-hint">Discovering\u2026</div>');try{const n=await(await fetch("/api/remote-hosts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})).json();if(!n.success&&n.errorCode!=="ALREADY_EXISTS")throw new Error(n.error||"Failed to save remote host");const i=await(await fetch(`/api/remote-hosts/${encodeURIComponent(s.id)}/sessions`)).json();if(!i.success)throw new Error(i.error||"Discovery failed");this._renderDiscoveredSessions(s.id,i.data.sessions||[])}catch(a){console.error("Discover remote sessions failed:",a),e&&(e.innerHTML=`<div class="form-hint" style="color: var(--error, #e06c75);">${escapeHtml(a.message)}</div>`)}finally{t&&(t.disabled=!1)}},_renderDiscoveredSessions(e,t){const s=document.getElementById("remoteDiscoverResults");if(!s)return;if(!t.length){s.innerHTML='<div class="form-hint">No <code>codeman-*</code> sessions running on this host (or it is unreachable).</div>';return}const a=Math.floor(Date.now()/1e3),n=t.map(o=>{const i=Math.max(0,a-(o.created||0)),r=i<3600?`${Math.floor(i/60)}m`:i<86400?`${Math.floor(i/3600)}h`:`${Math.floor(i/86400)}d`,c=o.attachedClients!=null?o.attachedClients:o.attached?1:0,m=c>1?`<span class="case-location-badge" style="background: var(--warning, #e5c07b); color: #000;">shared \xB7 ${c} clients</span>`:c===1?'<span class="case-location-badge" style="background: var(--accent, #61afef);">attached</span>':"";return`
|
|
25
25
|
<div class="remote-discover-item">
|
|
26
26
|
<div class="remote-discover-info">
|
|
27
|
-
<span class="remote-discover-name">${escapeHtml(
|
|
28
|
-
<span class="form-hint">age ${r} \xB7 ${
|
|
27
|
+
<span class="remote-discover-name">${escapeHtml(o.name)} ${m}</span>
|
|
28
|
+
<span class="form-hint">age ${r} \xB7 ${o.windows||1} window(s)</span>
|
|
29
29
|
</div>
|
|
30
|
-
<button type="button" class="btn-toolbar" onclick="app.attachDiscoveredSession('${escapeHtml(e)}', '${escapeHtml(
|
|
31
|
-
</div>`}).join("");s.innerHTML=
|
|
30
|
+
<button type="button" class="btn-toolbar" onclick="app.attachDiscoveredSession('${escapeHtml(e)}', '${escapeHtml(o.name)}')">Attach</button>
|
|
31
|
+
</div>`}).join("");s.innerHTML=n},async attachDiscoveredSession(e,t){try{const a=await(await fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({mode:"shell",name:t,attachRemoteSession:{hostId:e,remoteSessionName:t}})})).json();if(!a.success)throw new Error(a.error||"Failed to create session");const n=a.data.session.id;await fetch(`/api/sessions/${n}/shell`,{method:"POST"});const o=this.getTerminalDimensions();o&&await fetch(`/api/sessions/${n}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)}),this.closeCreateCaseModal(),this.showToast(`Attached to ${t} (detach on close)`,"success"),this.activeSessionId=n,await this.selectSession(n),this.terminal&&typeof this.terminal.focus=="function"&&this.terminal.focus()}catch(s){console.error("Attach discovered session failed:",s),this.showToast("Failed to attach: "+s.message,"error")}},renderCaseManageList(){const e=document.getElementById("caseManageList"),t=this.cases||[];if(t.length===0){e.innerHTML='<div class="form-hint" style="text-align: center; padding: 2rem 0;">No cases yet</div>';return}let s="";t.forEach((a,n)=>{const o=n===0,i=n===t.length-1,r=a.path?a.path.replace(/^\/Users\/[^/]+/,"~"):"";s+=`
|
|
32
32
|
<div class="case-manage-item" data-case="${escapeHtml(a.name)}">
|
|
33
33
|
<div class="case-manage-info">
|
|
34
34
|
<span class="case-manage-name">${escapeHtml(a.name)}</span>
|
|
@@ -38,14 +38,14 @@ Recreate the container to apply the new config? Workspace files and the conversa
|
|
|
38
38
|
${a.location==="docker"?`<button class="case-manage-btn" onclick="app.exportDockerCaseBundle(${escapeHtml(JSON.stringify(a.name))}, 'full')"
|
|
39
39
|
title="Export container (full image + workspace) to move to another machine">📦</button>`:""}
|
|
40
40
|
<button class="case-manage-btn" onclick="app.moveCaseUp(${escapeHtml(JSON.stringify(a.name))})"
|
|
41
|
-
title="Move up" ${
|
|
41
|
+
title="Move up" ${o?"disabled":""}>▲</button>
|
|
42
42
|
<button class="case-manage-btn" onclick="app.moveCaseDown(${escapeHtml(JSON.stringify(a.name))})"
|
|
43
43
|
title="Move down" ${i?"disabled":""}>▼</button>
|
|
44
44
|
<button class="case-manage-btn case-manage-btn-delete" onclick="app.deleteCase(${escapeHtml(JSON.stringify(a.name))})"
|
|
45
45
|
title="Delete case">✕</button>
|
|
46
46
|
</div>
|
|
47
47
|
</div>
|
|
48
|
-
`}),e.innerHTML=s},async moveCaseUp(e){const t=this.cases||[],s=t.findIndex(
|
|
48
|
+
`}),e.innerHTML=s},async moveCaseUp(e){const t=this.cases||[],s=t.findIndex(n=>n.name===e);if(s<=0)return;const a=[...t];[a[s-1],a[s]]=[a[s],a[s-1]],this.cases=a,this.renderCaseManageList(),await this.saveCaseOrder(a.map(n=>n.name))},async moveCaseDown(e){const t=this.cases||[],s=t.findIndex(n=>n.name===e);if(s<0||s>=t.length-1)return;const a=[...t];[a[s],a[s+1]]=[a[s+1],a[s]],this.cases=a,this.renderCaseManageList(),await this.saveCaseOrder(a.map(n=>n.name))},async deleteCase(e){if(confirm(`Delete case "${e}"? Linked cases will only be unlinked (folder preserved). Created cases will be permanently deleted.`))try{const s=await(await fetch(`/api/cases/${encodeURIComponent(e)}`,{method:"DELETE"})).json();if(s.success){this.showToast(`Case "${e}" ${s.data?.type==="unlinked"?"unlinked":"deleted"}`,"success"),this.cases=(this.cases||[]).filter(o=>o.name!==e),this.renderCaseManageList();const a=document.getElementById("quickStartCase"),n=a.value;n===e&&a.blur?.(),await this.loadQuickStartCases(n===e?null:n),n===e&&await this.saveLastUsedCase(document.getElementById("quickStartCase")?.value||"testcase")}else this.showToast(s.error||"Failed to delete case","error")}catch(t){this.showToast("Failed to delete case: "+t.message,"error")}},async saveCaseOrder(e){try{await fetch("/api/cases/order",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({order:e})});const s=document.getElementById("quickStartCase").value;await this.loadQuickStartCases(s)}catch(t){this.showToast("Failed to save case order: "+t.message,"error")}},showMobileCasePicker(){const e=document.getElementById("mobileCasePickerModal"),t=document.getElementById("mobileCaseList"),a=document.getElementById("quickStartCase").value;let n="";const o=this.getCasePickerOptions();for(const i of o){const r=i.name===a;n+=`
|
|
49
49
|
<button class="mobile-case-item ${r?"selected":""}"
|
|
50
50
|
onclick="app.selectMobileCase(${escapeHtml(JSON.stringify(i.name))})">
|
|
51
51
|
<span class="mobile-case-item-icon">
|
|
@@ -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=
|
|
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==="claude"?e:"claude"}});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|