aicodeman 1.1.12 → 1.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  2. package/dist/web/public/{app.a24e7b65.js → app.160e8c7d.js} +10 -9
  3. package/dist/web/public/app.160e8c7d.js.br +0 -0
  4. package/dist/web/public/app.160e8c7d.js.gz +0 -0
  5. package/dist/web/public/constants.1c779517.js.gz +0 -0
  6. package/dist/web/public/image-input.0ea86695.js.gz +0 -0
  7. package/dist/web/public/index.html +5 -5
  8. package/dist/web/public/index.html.br +0 -0
  9. package/dist/web/public/index.html.gz +0 -0
  10. package/dist/web/public/input-cjk.4e950e0a.js.gz +0 -0
  11. package/dist/web/public/{keyboard-accessory.3370a23c.js → keyboard-accessory.3f91acef.js} +1 -0
  12. package/dist/web/public/keyboard-accessory.3f91acef.js.br +0 -0
  13. package/dist/web/public/keyboard-accessory.3f91acef.js.gz +0 -0
  14. package/dist/web/public/mobile-handlers.b5a667ef.js.gz +0 -0
  15. package/dist/web/public/mobile.38346326.css.gz +0 -0
  16. package/dist/web/public/notification-manager.9c984ac2.js.gz +0 -0
  17. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  18. package/dist/web/public/panels-ui.f3f08e26.js.gz +0 -0
  19. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  20. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  21. package/dist/web/public/respawn-ui.2d249da9.js.gz +0 -0
  22. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  23. package/dist/web/public/session-ui.1463b824.js.gz +0 -0
  24. package/dist/web/public/settings-ui.38feb956.js.gz +0 -0
  25. package/dist/web/public/{styles.e0388c26.css → styles.4d2984bd.css} +1 -1
  26. package/dist/web/public/styles.4d2984bd.css.br +0 -0
  27. package/dist/web/public/styles.4d2984bd.css.gz +0 -0
  28. package/dist/web/public/{subagent-windows.07e139f2.js → subagent-windows.e3207234.js} +4 -0
  29. package/dist/web/public/subagent-windows.e3207234.js.br +0 -0
  30. package/dist/web/public/subagent-windows.e3207234.js.gz +0 -0
  31. package/dist/web/public/sw.js.gz +0 -0
  32. package/dist/web/public/terminal-ui.70596bbd.js.gz +0 -0
  33. package/dist/web/public/ultracode-panel.js +12 -22
  34. package/dist/web/public/ultracode-panel.js.br +0 -0
  35. package/dist/web/public/ultracode-panel.js.gz +0 -0
  36. package/dist/web/public/ultracode-windows.js +449 -12
  37. package/dist/web/public/ultracode-windows.js.br +0 -0
  38. package/dist/web/public/ultracode-windows.js.gz +0 -0
  39. package/dist/web/public/upload.html.gz +0 -0
  40. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  41. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  42. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  43. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  44. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  45. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  46. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  47. package/dist/web/public/vendor/xterm.css.gz +0 -0
  48. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  49. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  50. package/package.json +1 -1
  51. package/dist/web/public/app.a24e7b65.js.br +0 -0
  52. package/dist/web/public/app.a24e7b65.js.gz +0 -0
  53. package/dist/web/public/keyboard-accessory.3370a23c.js.br +0 -0
  54. package/dist/web/public/keyboard-accessory.3370a23c.js.gz +0 -0
  55. package/dist/web/public/styles.e0388c26.css.br +0 -0
  56. package/dist/web/public/styles.e0388c26.css.gz +0 -0
  57. package/dist/web/public/subagent-windows.07e139f2.js.br +0 -0
  58. package/dist/web/public/subagent-windows.07e139f2.js.gz +0 -0
@@ -11,27 +11,28 @@
11
11
  `).replace(/[ \t]+$/gm,"").replace(/\n{4,}/g,`
12
12
 
13
13
 
14
- `).trim()}_preprocessAsciiArt(e){const t=/[─-╿▀-▟]/,s=/```[\s\S]*?```/g,i=[];return e.replace(s,a=>(i.push(a),`__CODEMAN_FENCE_${i.length-1}__`)).split(/(\n{2,})/).map(a=>/^\n{2,}$/.test(a)||!a.trim()||a.includes("__CODEMAN_FENCE_")?a:t.test(a)?"\n```\n"+a+"\n```\n":a).join("").replace(/__CODEMAN_FENCE_(\d+)__/g,(a,r)=>i[Number(r)])}_renderMarkdown(e){const t=e||"";if(typeof marked<"u"&&marked.parse)try{const i=this._preprocessAsciiArt(t);let n=this._sanitizeHtml(marked.parse(i,{breaks:!0,gfm:!0}));n=n.replace(/<table>/g,'<div class="rv-table-wrap"><table>').replace(/<\/table>/g,"</table></div>");const o=/[─-╿▀-▟]/,a=document.createElement("template");return a.innerHTML=n,a.content.querySelectorAll("pre > code").forEach(r=>{const l=r.parentElement,u=o.test(r.textContent||""),c=document.createElement("div");c.className=u?"rv-code-wrap rv-diagram-wrap":"rv-code-wrap";const p=document.createElement("div");p.className="rv-code-actions";const _=document.createElement("button");if(_.className="rv-copy-btn",_.type="button",_.setAttribute("aria-label","Copy code"),_.setAttribute("title","Copy code"),p.appendChild(_),u){l.classList.add("rv-diagram");const m=document.createElement("button");m.className="rv-wrap-toggle",m.type="button",m.setAttribute("aria-label","Toggle line wrapping"),m.setAttribute("title","Toggle line wrapping"),p.appendChild(m)}l.parentNode.insertBefore(c,l),c.appendChild(p),c.appendChild(l)}),a.innerHTML}catch{}return`<pre style="white-space:pre-wrap;word-break:break-word">${t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}</pre>`}_bindResponseViewerInteractions(e){!e||e.dataset.rvBound==="1"||(e.dataset.rvBound="1",e.addEventListener("click",async t=>{const s=t.target.closest(".rv-copy-btn");if(s){t.preventDefault(),t.stopPropagation();const r=s.closest(".rv-code-wrap")?.querySelector("pre code"),l=r?await this._copyText(r.textContent||""):!1;s.classList.remove("rv-copied","rv-copy-failed"),s.classList.add(l?"rv-copied":"rv-copy-failed"),clearTimeout(s._resetTimer),s._resetTimer=setTimeout(()=>{s.classList.remove("rv-copied","rv-copy-failed")},1500);return}const i=t.target.closest(".rv-wrap-toggle");if(!i)return;t.preventDefault(),t.stopPropagation();const n=i.closest(".rv-diagram-wrap"),o=n?.querySelector("pre.rv-diagram");if(!o||!n)return;const a=o.classList.toggle("rv-nowrap");n.classList.toggle("rv-wrap-nowrap",a)}))}async _copyText(e){if(!e)return!1;try{if(navigator.clipboard?.writeText)return await navigator.clipboard.writeText(e),!0}catch{}try{const t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.cssText="position:fixed;top:0;left:0;opacity:0;pointer-events:none",document.body.appendChild(t),t.select();const s=document.execCommand("copy");return document.body.removeChild(t),s}catch{return!1}}async toggleResponseViewer(){const e=document.getElementById("responseViewer"),t=document.getElementById("responseViewerBackdrop");if(!e)return;if(e.classList.contains("visible")){e.classList.remove("visible"),t.classList.remove("visible");return}if(this.activeSessionId)try{let o=((await(await fetch(`/api/sessions/${this.activeSessionId}/last-response`)).json())?.data??{}).text||"";if(!o){const c=(await(await fetch(`/api/sessions/${this.activeSessionId}/terminal`)).json())?.data??{};c.terminalBuffer&&(o=this._cleanTerminalBuffer(c.terminalBuffer))}const a=document.getElementById("responseViewerBody");a.innerHTML=this._renderMarkdown(o),this._bindResponseViewerInteractions(a);const r=document.getElementById("responseViewerTitle"),l=document.getElementById("responseViewerMore");r&&(r.textContent="Last Response"),l&&(l.style.display="",l.textContent="More"),e.classList.add("visible"),t.classList.add("visible"),a.scrollTop=0}catch(i){console.error("Failed to load response:",i)}}async loadFullContext(){if(!this.activeSessionId)return;const e=document.getElementById("responseViewerMore");e&&(e.textContent="...");try{const i=((await(await fetch(`/api/sessions/${this.activeSessionId}/last-response?context=full`)).json())?.data??{}).messages||[],n=document.getElementById("responseViewerBody"),o=document.getElementById("responseViewerTitle");if(!n)return;if(i.length===0){n.textContent="No conversation history available";return}n.innerHTML="";for(const a of i){const r=document.createElement("div"),l=a.role==="user";r.className="rv-message "+(l?"rv-msg-user":"rv-msg-assistant");const u=document.createElement("div");u.className="rv-role "+(l?"rv-role-user":"rv-role-assistant"),u.textContent=l?"You":"Claude",r.appendChild(u);const c=document.createElement("div");c.className="rv-text",c.innerHTML=this._renderMarkdown(a.text),r.appendChild(c),n.appendChild(r)}this._bindResponseViewerInteractions(n),o&&(o.textContent=`Conversation (${i.length} messages)`),e&&(e.style.display="none"),n.scrollTop=n.scrollHeight}catch(t){console.error("Failed to load context:",t)}finally{e&&(e.textContent="More")}}async _onSessionNeedsRefresh(){if(!(!this.activeSessionId||!this.terminal)&&!this._isLoadingBuffer)try{const t=(await(await fetch(`/api/sessions/${this.activeSessionId}/terminal?tail=${TERMINAL_TAIL_SIZE}`)).json())?.data??{};t.terminalBuffer&&(this.terminal.clear(),this.terminal.reset(),await this.chunkedTerminalWrite(t.terminalBuffer),this.terminal.scrollToBottom(),this._localEchoOverlay?.rerender(),this.activeSessionId&&this.sendResize(this.activeSessionId))}catch(e){console.error("needsRefresh reload failed:",e)}}async _onSessionClearTerminal(e){if(e.id===this.activeSessionId){if(this._isLoadingBuffer)return;try{const s=(await(await fetch(`/api/sessions/${e.id}/terminal`)).json())?.data??{};if(this.terminal.clear(),this.terminal.reset(),s.terminalBuffer){const i=s.terminalBuffer.replace(DEC_SYNC_STRIP_RE,"");await this.chunkedTerminalWrite(i)}this.sendResize(e.id),this._localEchoOverlay?.rerender()}catch(t){console.error("clearTerminal refresh failed:",t)}}}_onSessionCompletion(e){this.totalCost+=e.cost||0,this.updateCost(),e.id===this.activeSessionId&&(this.terminal.writeln(""),this.terminal.writeln(`\x1B[1;32m Done (Cost: $${(e.cost||0).toFixed(4)})\x1B[0m`))}_onSessionError(e){e.id===this.activeSessionId&&this.terminal.writeln(`\x1B[1;31m Error: ${e.error}\x1B[0m`),this._notifySession(e.id,"critical","session-error","Session Error",e.error||"Unknown error")}_onSessionExit(e){this._wsSessionId===e.id&&this._disconnectWs();const t=this.sessions.get(e.id);t&&(t.status="stopped",this.renderSessionTabs(),e.id===this.activeSessionId&&this._updateLocalEchoState()),e.code&&e.code!==0&&this._notifySession(e.id,"critical","session-crash","Session Crashed",`Exited with code ${e.code}`)}_onSessionIdle(e){const t=this.sessions.get(e.id);if(t&&(t.status="idle",this.renderSessionTabs(),this.sendPendingCtrlL(e.id),e.id===this.activeSessionId&&this._updateLocalEchoState()),!this.respawnStatus[e.id]?.enabled){const s=this.notificationManager?.preferences?.stuckThresholdMs||6e5;clearTimeout(this.idleTimers.get(e.id)),this.idleTimers.set(e.id,setTimeout(()=>{this._notifySession(e.id,"warning","session-stuck","Session Idle",`Idle for ${Math.round(s/6e4)}+ minutes`),this.idleTimers.delete(e.id)},s))}}_onSessionWorking(e){const t=this.sessions.get(e.id);t&&(t.status="busy",this.pendingHooks.has(e.id)||this.tabAlerts.delete(e.id),this.renderSessionTabs(),this.sendPendingCtrlL(e.id),e.id===this.activeSessionId&&this._updateLocalEchoState());const s=this.idleTimers.get(e.id);s&&(clearTimeout(s),this.idleTimers.delete(e.id))}_onSessionAutoClear(e){e.sessionId===this.activeSessionId&&(this.showToast(`Auto-cleared at ${e.tokens.toLocaleString()} tokens`,"info"),this.updateRespawnTokens(0)),this._notifySession(e.sessionId,"info","auto-clear","Auto-Cleared",`Context reset at ${(e.tokens||0).toLocaleString()} tokens`)}_onSessionLimitPauseScheduled(e){const t=this.sessions.get(e.sessionId);t&&(t.autoResumeAt=e.resumeAt);const s=new Date(e.resumeAt).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});e.sessionId===this.activeSessionId&&this.showToast(`Usage limit reached \u2014 auto-resume at ${s}`,"warning"),this._notifySession(e.sessionId,"warning","limit-pause","Usage Limit Reached",`Auto-resume scheduled for ${s}`),this.updateAutoResumeStatus(e.sessionId)}_onSessionLimitResume(e){const t=this.sessions.get(e.sessionId);t&&(t.autoResumeAt=void 0),e.sessionId===this.activeSessionId&&this.showToast("Usage limit reset \u2014 work resumed automatically","success"),this._notifySession(e.sessionId,"info","limit-resume","Auto-Resumed","Usage limit reset \u2014 continuing work"),this.updateAutoResumeStatus(e.sessionId)}_onSessionLimitResumeCancelled(e){const t=this.sessions.get(e.sessionId);t&&(t.autoResumeAt=void 0),this.updateAutoResumeStatus(e.sessionId)}_onSessionCliInfo(e){const t=this.sessions.get(e.sessionId);t&&(e.version&&(t.cliVersion=e.version),e.model&&(t.cliModel=e.model),e.accountType&&(t.cliAccountType=e.accountType),e.latestVersion&&(t.cliLatestVersion=e.latestVersion)),e.sessionId===this.activeSessionId&&this.updateCliInfoDisplay()}_onSessionStatusTelemetry(e){this.updatePlanUsageChip(e);try{localStorage.setItem("codeman:planUsage",JSON.stringify({t:Date.now(),data:e}))}catch{}}restorePlanUsageChip(){try{const e=localStorage.getItem("codeman:planUsage");if(!e)return;const t=JSON.parse(e);t?.data&&Date.now()-(t.t||0)<12*3600*1e3&&this.updatePlanUsageChip(t.data)}catch{}}updatePlanUsageChip(e){const t=document.getElementById("planUsageChip");if(!t||!e)return;const s=l=>l&&typeof l.usedPercentage=="number"?Math.round(l.usedPercentage):null,i=s(e.fiveHour),n=s(e.sevenDay);if(i===null&&n===null)return;const o=l=>l>=85?"pu-red":l>=60?"pu-yellow":"pu-green",a=(l,u)=>{if(u===null)return"";const c=Math.round(Number(u));return Number.isFinite(c)?`<span class="pu-win"><span class="pu-label">${l}</span><span class="pu-val ${o(c)}">${c}%</span></span>`:""};t.innerHTML=[a("5h",i),a("7d",n)].filter(Boolean).join('<span class="pu-sep">\xB7</span>');const r=l=>l&&l.resetAt?new Date(l.resetAt).toLocaleString():"\u2014";t.title=`Claude plan usage
14
+ `).trim()}_preprocessAsciiArt(e){const t=/[─-╿▀-▟]/,s=/```[\s\S]*?```/g,i=[];return e.replace(s,a=>(i.push(a),`__CODEMAN_FENCE_${i.length-1}__`)).split(/(\n{2,})/).map(a=>/^\n{2,}$/.test(a)||!a.trim()||a.includes("__CODEMAN_FENCE_")?a:t.test(a)?"\n```\n"+a+"\n```\n":a).join("").replace(/__CODEMAN_FENCE_(\d+)__/g,(a,r)=>i[Number(r)])}_renderMarkdown(e){const t=e||"";if(typeof marked<"u"&&marked.parse)try{const i=this._preprocessAsciiArt(t);let n=this._sanitizeHtml(marked.parse(i,{breaks:!0,gfm:!0}));n=n.replace(/<table>/g,'<div class="rv-table-wrap"><table>').replace(/<\/table>/g,"</table></div>");const o=/[─-╿▀-▟]/,a=document.createElement("template");return a.innerHTML=n,a.content.querySelectorAll("pre > code").forEach(r=>{const l=r.parentElement,d=o.test(r.textContent||""),c=document.createElement("div");c.className=d?"rv-code-wrap rv-diagram-wrap":"rv-code-wrap";const p=document.createElement("div");p.className="rv-code-actions";const g=document.createElement("button");if(g.className="rv-copy-btn",g.type="button",g.setAttribute("aria-label","Copy code"),g.setAttribute("title","Copy code"),p.appendChild(g),d){l.classList.add("rv-diagram");const m=document.createElement("button");m.className="rv-wrap-toggle",m.type="button",m.setAttribute("aria-label","Toggle line wrapping"),m.setAttribute("title","Toggle line wrapping"),p.appendChild(m)}l.parentNode.insertBefore(c,l),c.appendChild(p),c.appendChild(l)}),a.innerHTML}catch{}return`<pre style="white-space:pre-wrap;word-break:break-word">${t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}</pre>`}_bindResponseViewerInteractions(e){!e||e.dataset.rvBound==="1"||(e.dataset.rvBound="1",e.addEventListener("click",async t=>{const s=t.target.closest(".rv-copy-btn");if(s){t.preventDefault(),t.stopPropagation();const r=s.closest(".rv-code-wrap")?.querySelector("pre code"),l=r?await this._copyText(r.textContent||""):!1;s.classList.remove("rv-copied","rv-copy-failed"),s.classList.add(l?"rv-copied":"rv-copy-failed"),clearTimeout(s._resetTimer),s._resetTimer=setTimeout(()=>{s.classList.remove("rv-copied","rv-copy-failed")},1500);return}const i=t.target.closest(".rv-wrap-toggle");if(!i)return;t.preventDefault(),t.stopPropagation();const n=i.closest(".rv-diagram-wrap"),o=n?.querySelector("pre.rv-diagram");if(!o||!n)return;const a=o.classList.toggle("rv-nowrap");n.classList.toggle("rv-wrap-nowrap",a)}))}async _copyText(e){if(!e)return!1;try{if(navigator.clipboard?.writeText)return await navigator.clipboard.writeText(e),!0}catch{}try{const t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.cssText="position:fixed;top:0;left:0;opacity:0;pointer-events:none",document.body.appendChild(t),t.select();const s=document.execCommand("copy");return document.body.removeChild(t),s}catch{return!1}}async toggleResponseViewer(){const e=document.getElementById("responseViewer"),t=document.getElementById("responseViewerBackdrop");if(!e)return;if(e.classList.contains("visible")){e.classList.remove("visible"),t.classList.remove("visible");return}if(this.activeSessionId)try{let o=((await(await fetch(`/api/sessions/${this.activeSessionId}/last-response`)).json())?.data??{}).text||"";if(!o){const c=(await(await fetch(`/api/sessions/${this.activeSessionId}/terminal`)).json())?.data??{};c.terminalBuffer&&(o=this._cleanTerminalBuffer(c.terminalBuffer))}const a=document.getElementById("responseViewerBody");a.innerHTML=this._renderMarkdown(o),this._bindResponseViewerInteractions(a);const r=document.getElementById("responseViewerTitle"),l=document.getElementById("responseViewerMore");r&&(r.textContent="Last Response"),l&&(l.style.display="",l.textContent="More"),e.classList.add("visible"),t.classList.add("visible"),a.scrollTop=0}catch(i){console.error("Failed to load response:",i)}}async loadFullContext(){if(!this.activeSessionId)return;const e=document.getElementById("responseViewerMore");e&&(e.textContent="...");try{const i=((await(await fetch(`/api/sessions/${this.activeSessionId}/last-response?context=full`)).json())?.data??{}).messages||[],n=document.getElementById("responseViewerBody"),o=document.getElementById("responseViewerTitle");if(!n)return;if(i.length===0){n.textContent="No conversation history available";return}n.innerHTML="";for(const a of i){const r=document.createElement("div"),l=a.role==="user";r.className="rv-message "+(l?"rv-msg-user":"rv-msg-assistant");const d=document.createElement("div");d.className="rv-role "+(l?"rv-role-user":"rv-role-assistant"),d.textContent=l?"You":"Claude",r.appendChild(d);const c=document.createElement("div");c.className="rv-text",c.innerHTML=this._renderMarkdown(a.text),r.appendChild(c),n.appendChild(r)}this._bindResponseViewerInteractions(n),o&&(o.textContent=`Conversation (${i.length} messages)`),e&&(e.style.display="none"),n.scrollTop=n.scrollHeight}catch(t){console.error("Failed to load context:",t)}finally{e&&(e.textContent="More")}}async _onSessionNeedsRefresh(){if(!(!this.activeSessionId||!this.terminal)&&!this._isLoadingBuffer)try{const t=(await(await fetch(`/api/sessions/${this.activeSessionId}/terminal?tail=${TERMINAL_TAIL_SIZE}`)).json())?.data??{};t.terminalBuffer&&(this.terminal.clear(),this.terminal.reset(),await this.chunkedTerminalWrite(t.terminalBuffer),this.terminal.scrollToBottom(),this._localEchoOverlay?.rerender(),this.activeSessionId&&this.sendResize(this.activeSessionId))}catch(e){console.error("needsRefresh reload failed:",e)}}async _onSessionClearTerminal(e){if(e.id===this.activeSessionId){if(this._isLoadingBuffer)return;try{const s=(await(await fetch(`/api/sessions/${e.id}/terminal`)).json())?.data??{};if(this.terminal.clear(),this.terminal.reset(),s.terminalBuffer){const i=s.terminalBuffer.replace(DEC_SYNC_STRIP_RE,"");await this.chunkedTerminalWrite(i)}this.sendResize(e.id),this._localEchoOverlay?.rerender()}catch(t){console.error("clearTerminal refresh failed:",t)}}}_onSessionCompletion(e){this.totalCost+=e.cost||0,this.updateCost(),e.id===this.activeSessionId&&(this.terminal.writeln(""),this.terminal.writeln(`\x1B[1;32m Done (Cost: $${(e.cost||0).toFixed(4)})\x1B[0m`))}_onSessionError(e){e.id===this.activeSessionId&&this.terminal.writeln(`\x1B[1;31m Error: ${e.error}\x1B[0m`),this._notifySession(e.id,"critical","session-error","Session Error",e.error||"Unknown error")}_onSessionExit(e){this._wsSessionId===e.id&&this._disconnectWs();const t=this.sessions.get(e.id);t&&(t.status="stopped",this.renderSessionTabs(),e.id===this.activeSessionId&&this._updateLocalEchoState()),e.code&&e.code!==0&&this._notifySession(e.id,"critical","session-crash","Session Crashed",`Exited with code ${e.code}`)}_onSessionIdle(e){const t=this.sessions.get(e.id);if(t&&(t.status="idle",this.renderSessionTabs(),this.sendPendingCtrlL(e.id),e.id===this.activeSessionId&&this._updateLocalEchoState()),!this.respawnStatus[e.id]?.enabled){const s=this.notificationManager?.preferences?.stuckThresholdMs||6e5;clearTimeout(this.idleTimers.get(e.id)),this.idleTimers.set(e.id,setTimeout(()=>{this._notifySession(e.id,"warning","session-stuck","Session Idle",`Idle for ${Math.round(s/6e4)}+ minutes`),this.idleTimers.delete(e.id)},s))}}_onSessionWorking(e){const t=this.sessions.get(e.id);t&&(t.status="busy",this.pendingHooks.has(e.id)||this.tabAlerts.delete(e.id),this.renderSessionTabs(),this.sendPendingCtrlL(e.id),e.id===this.activeSessionId&&this._updateLocalEchoState());const s=this.idleTimers.get(e.id);s&&(clearTimeout(s),this.idleTimers.delete(e.id))}_onSessionAutoClear(e){e.sessionId===this.activeSessionId&&(this.showToast(`Auto-cleared at ${e.tokens.toLocaleString()} tokens`,"info"),this.updateRespawnTokens(0)),this._notifySession(e.sessionId,"info","auto-clear","Auto-Cleared",`Context reset at ${(e.tokens||0).toLocaleString()} tokens`)}_onSessionLimitPauseScheduled(e){const t=this.sessions.get(e.sessionId);t&&(t.autoResumeAt=e.resumeAt);const s=new Date(e.resumeAt).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});e.sessionId===this.activeSessionId&&this.showToast(`Usage limit reached \u2014 auto-resume at ${s}`,"warning"),this._notifySession(e.sessionId,"warning","limit-pause","Usage Limit Reached",`Auto-resume scheduled for ${s}`),this.updateAutoResumeStatus(e.sessionId)}_onSessionLimitResume(e){const t=this.sessions.get(e.sessionId);t&&(t.autoResumeAt=void 0),e.sessionId===this.activeSessionId&&this.showToast("Usage limit reset \u2014 work resumed automatically","success"),this._notifySession(e.sessionId,"info","limit-resume","Auto-Resumed","Usage limit reset \u2014 continuing work"),this.updateAutoResumeStatus(e.sessionId)}_onSessionLimitResumeCancelled(e){const t=this.sessions.get(e.sessionId);t&&(t.autoResumeAt=void 0),this.updateAutoResumeStatus(e.sessionId)}_onSessionCliInfo(e){const t=this.sessions.get(e.sessionId);t&&(e.version&&(t.cliVersion=e.version),e.model&&(t.cliModel=e.model),e.accountType&&(t.cliAccountType=e.accountType),e.latestVersion&&(t.cliLatestVersion=e.latestVersion)),e.sessionId===this.activeSessionId&&this.updateCliInfoDisplay()}_onSessionStatusTelemetry(e){this.updatePlanUsageChip(e);try{localStorage.setItem("codeman:planUsage",JSON.stringify({t:Date.now(),data:e}))}catch{}}restorePlanUsageChip(){try{const e=localStorage.getItem("codeman:planUsage");if(!e)return;const t=JSON.parse(e);t?.data&&Date.now()-(t.t||0)<12*3600*1e3&&this.updatePlanUsageChip(t.data)}catch{}}updatePlanUsageChip(e){const t=document.getElementById("planUsageChip");if(!t||!e)return;const s=l=>l&&typeof l.usedPercentage=="number"?Math.round(l.usedPercentage):null,i=s(e.fiveHour),n=s(e.sevenDay);if(i===null&&n===null)return;const o=l=>l>=85?"pu-red":l>=60?"pu-yellow":"pu-green",a=(l,d)=>{if(d===null)return"";const c=Math.round(Number(d));return Number.isFinite(c)?`<span class="pu-win"><span class="pu-label">${l}</span><span class="pu-val ${o(c)}">${c}%</span></span>`:""};t.innerHTML=[a("5h",i),a("7d",n)].filter(Boolean).join('<span class="pu-sep">\xB7</span>');const r=l=>l&&l.resetAt?new Date(l.resetAt).toLocaleString():"\u2014";t.title=`Claude plan usage
15
15
  5-hour limit: ${i??"\u2014"}% used (resets ${r(e.fiveHour)})
16
- Weekly limit: ${n??"\u2014"}% used (resets ${r(e.sevenDay)})`}_onScheduledCreated(e){this.currentRun=e,this.showTimer()}_onScheduledUpdated(e){this.currentRun=e,this.updateTimer()}_onScheduledCompleted(e){this.currentRun=e,this.hideTimer(),this.showToast("Scheduled run completed!","success")}_onScheduledStopped(){this.currentRun=null,this.hideTimer()}setConnectionStatus(e){this._connectionStatus=e,this._updateConnectionIndicator(),e==="connected"&&this._inputQueue.size>0&&this._drainInputQueues()}_connectWs(e){this._disconnectWs();const s=`${location.protocol==="https:"?"wss:":"ws:"}//${location.host}/ws/sessions/${e}/terminal`,i=new WebSocket(s);this._ws=i,this._wsSessionId=e,i.onopen=()=>{this._ws===i&&(this._wsReady=!0,this._wsReconnectAttempts=0,this.sendResize(e)?.catch?.(()=>{}),this._startMobileResizeRetry(e))},i.onmessage=n=>{if(this._ws===i)try{const o=JSON.parse(n.data);o.t==="o"?this._onSessionTerminal({id:e,data:o.d}):o.t==="c"?this._onSessionClearTerminal({id:e}):o.t==="r"&&this._onSessionNeedsRefresh({id:e})}catch{}},i.onclose=n=>{if(this._ws===i&&(this._ws=null,this._wsSessionId=null,this._wsReady=!1,this._stopMobileResizeRetry(),n.code<4004&&this.activeSessionId===e)){const o=Math.min(1e3*Math.pow(2,this._wsReconnectAttempts||0),1e4);this._wsReconnectAttempts=(this._wsReconnectAttempts||0)+1,this._wsReconnectTimer=setTimeout(()=>{this._wsReconnectTimer=null,this.activeSessionId===e&&this._connectWs(e)},o)}},i.onerror=()=>{}}_disconnectWs(){this._clearTimer("_wsReconnectTimer"),this._wsReconnectAttempts=0,this._stopMobileResizeRetry(),this._ws&&(this._ws.onclose=null,this._ws.close(),this._ws=null,this._wsSessionId=null,this._wsReady=!1)}_startMobileResizeRetry(e){this._stopMobileResizeRetry(),(typeof MobileDetection<"u"&&MobileDetection.getDeviceType?MobileDetection.getDeviceType():"desktop")!=="desktop"&&(this._mobileResizeRetryTimer=setInterval(()=>{document.visibilityState==="visible"&&(!this._wsReady||this._wsSessionId!==e||typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible||this.sendResize(e)?.catch?.(()=>{}))},MOBILE_RESIZE_RETRY_MS))}_stopMobileResizeRetry(){this._mobileResizeRetryTimer&&(clearInterval(this._mobileResizeRetryTimer),this._mobileResizeRetryTimer=null)}_sendInputAsync(e,t){if(!this.isOnline||this._connectionStatus==="disconnected"){this._enqueueInput(e,t);return}if(this._wsReady&&this._wsSessionId===e)try{this._ws.send(JSON.stringify({t:"i",d:t})),this.clearPendingHooks(e);return}catch{}this._inputSendChain=this._inputSendChain.then(()=>{fetch(`/api/sessions/${e}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:t}),keepalive:t.length<65536}).then(i=>{i.ok?this.clearPendingHooks(e):this._enqueueInput(e,t)}).catch(()=>{this._enqueueInput(e,t)})})}_enqueueInput(e,t){let i=(this._inputQueue.get(e)||"")+t;i.length>this._inputQueueMaxBytes&&(i=i.slice(i.length-this._inputQueueMaxBytes)),this._inputQueue.set(e,i),this._updateConnectionIndicator()}async _drainInputQueues(){if(this._inputQueue.size===0)return;const e=new Map(this._inputQueue);this._inputQueue.clear(),this._updateConnectionIndicator();for(const[t,s]of e)(await this._apiPost(`/api/sessions/${t}/input`,{input:s}))?.ok||this._enqueueInput(t,s);this._updateConnectionIndicator()}_updateConnectionIndicator(){const e=this.$("connectionIndicator"),t=this.$("connectionDot"),s=this.$("connectionText");if(!e||!t||!s)return;let i=0;for(const r of this._inputQueue.values())i+=r.length;const n=this._connectionStatus,o=i>0;if((n==="connected"||n==="connecting")&&!o){e.style.display="none";return}e.style.display="flex",t.className="connection-dot";const a=r=>r<1024?`${r}B`:`${(r/1024).toFixed(1)}KB`;n==="connected"&&o?(t.classList.add("draining"),s.textContent=`Sending ${a(i)}...`):n==="reconnecting"?(t.classList.add("reconnecting"),s.textContent=o?`Reconnecting (${a(i)} queued)`:"Reconnecting..."):(t.classList.add("offline"),s.textContent=o?`Offline (${a(i)} queued)`:"Offline")}setupOnlineDetection(){window.addEventListener("online",()=>{this.isOnline=!0,this.reconnectAttempts=0,this.connectSSE()}),window.addEventListener("offline",()=>{this.isOnline=!1,this.setConnectionStatus("offline")})}_updateCjkInputState(){const e=document.getElementById("cjkInput");if(!e)return;const t=this.loadAppSettingsFromStorage(),s=this.getDefaultSettings?.()||{},n=(this._serverCjkOverride||(t.cjkInputEnabled??s.cjkInputEnabled??!1))&&!!this.activeSessionId;e.classList.toggle("cjk-input-visible",!!n),document.body.classList.toggle("cjk-input-visible",!!n),e.style.display=n?"block":"none",e.setAttribute("aria-hidden",n?"false":"true"),n||(window.cjkActive=!1),typeof KeyboardHandler<"u"&&KeyboardHandler.updateLayoutForKeyboard()}_resetAllAppState(){this.sessions.clear(),this.ralphStates.clear(),this.terminalBuffers.clear(),this.terminalBufferCache.clear(),this._xtermSnapshots?.clear(),this.projectInsights.clear(),this.teams.clear(),this.teamTasks.clear();for(const e of this.idleTimers.values())clearTimeout(e);if(this.idleTimers.clear(),this._clearTimer("flickerFilterTimeout"),this.flickerFilterBuffer="",this.flickerFilterActive=!1,this._clearTimer("syncWaitTimeout"),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._bufferLoadOwner=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1,this._localEchoOverlay?.rerender(),this.pendingHooks.clear(),this._parentNameCache&&this._parentNameCache.clear(),this.subagentActivity.clear(),this.subagentToolResults.clear(),this.workflowRuns&&this.workflowRuns.clear(),this.workflowRunDetails&&this.workflowRunDetails.clear(),this.activeWorkflowRunId=null,this.activeWorkflowPhaseIndex=null,MobileDetection.cleanup(),KeyboardHandler.cleanup(),MobileDetection.init(),KeyboardHandler.init(),this.tabAlerts.clear(),this.attachmentHistoryCounts.clear(),this._shownCompletions&&this._shownCompletions.clear(),this.notificationManager?.titleFlashInterval&&(clearInterval(this.notificationManager.titleFlashInterval),this.notificationManager.titleFlashInterval=null),this.notificationManager?.groupingMap){for(const{timeout:e}of this.notificationManager.groupingMap.values())clearTimeout(e);this.notificationManager.groupingMap.clear()}this.terminalResizeObserver&&(this.terminalResizeObserver.disconnect(),this.terminalResizeObserver=null),this.planLoadingTimer&&(clearInterval(this.planLoadingTimer),this.planLoadingTimer=null),this.timerCountdownInterval&&(clearInterval(this.timerCountdownInterval),this.timerCountdownInterval=null),this.runSummaryAutoRefreshTimer&&(clearInterval(this.runSummaryAutoRefreshTimer),this.runSummaryAutoRefreshTimer=null)}handleInit(e){this._clearTimer("_initFallbackTimer");const t=++this._initGeneration;if(this._serverCjkOverride=e.inputCjkForm||!1,this._updateCjkInputState(),e.planUsage&&this.updatePlanUsageChip(e.planUsage),e.version){const n=this.$("versionDisplay"),o=this.$("headerVersion");n&&(n.textContent=`v${e.version}`,n.title=`Codeman v${e.version}`),o&&(o.textContent=`v${e.version}`,o.title=`Codeman v${e.version}`)}VoiceInput.cleanup(),this._resetAllAppState(),e.sessions.forEach(n=>{this.sessions.set(n.id,n),(n.ralphLoop||n.ralphTodos)&&!this.ralphClosedSessions.has(n.id)&&this.ralphStates.set(n.id,{loop:n.ralphLoop||null,todos:n.ralphTodos||[]})});try{localStorage.removeItem("codeman-tab-meta")}catch{}this.syncSessionOrder(),e.respawnStatus?this.respawnStatus=e.respawnStatus:this.respawnStatus={},this.respawnTimers={},this.respawnCountdownTimers={},this.respawnActionLogs={},e.globalStats&&(this.globalStats=e.globalStats),this.totalCost=e.sessions.reduce((n,o)=>n+(o.totalCost||0),0),this.totalCost+=e.scheduledRuns.reduce((n,o)=>n+(o.totalCost||0),0);const s=e.scheduledRuns.find(n=>n.status==="running");if(s&&(this.currentRun=s,this.showTimer()),this.updateCost(),this.renderSessionTabs(),this.sessions.size>0?this.startSystemStatsPolling():this.stopSystemStatsPolling(),this.cleanupAllFloatingWindows(),e.subagents&&(this.subagents.clear(),this.subagentActivity.clear(),this.subagentToolResults.clear(),e.subagents.forEach(n=>{this.subagents.set(n.agentId,n)}),this.renderSubagentPanel(),this.subagentParentMap.clear(),this.loadSubagentParentMap().then(()=>{for(const[n,o]of this.subagentParentMap){const a=this.subagents.get(n);if(a&&this.sessions.has(o)){a.parentSessionId=o;const r=this.sessions.get(o);r&&(a.parentSessionName=this.getSessionName(r)),this.subagents.set(n,a)}}for(const[n]of this.subagents)this.subagentParentMap.has(n)||this.findParentSessionForSubagent(n);this.restoreSubagentWindowStates()})),e.workflowRuns&&this.seedWorkflowRuns(e.workflowRuns),t!==this._initGeneration)return;if(this.isSoloWindow){this._applySoloMode();return}const i=this.activeSessionId;if(this.activeSessionId=null,this.sessionOrder.length>0){let n=i;if(!n||!this.sessions.has(n))try{n=localStorage.getItem("codeman-active-session")}catch{}n&&this.sessions.has(n)?this.selectSession(n):this.selectSession(this.sessionOrder[0])}}async loadState(){try{const t=await(await fetch("/api/status")).json();this.handleInit(t?.data??{})}catch(e){console.error("Failed to load state:",e)}}_debouncedCall(e,t,s=100){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{this._debounceTimers[e]=null,t.call(this)},s)}renderSessionTabs(){this._inlineRenameActive||this._debouncedCall("sessionTabs",this._renderSessionTabsImmediate)}_updateActiveTabImmediate(e){const t=this.$("sessionTabs");if(!t)return;const s=t.querySelectorAll(".session-tab[data-id]");for(const i of s)i.dataset.id===e?i.classList.add("active"):i.classList.remove("active")}_setTerminalLoadState(e,t,s){this.terminalLoadStates.set(e,{generation:t,phase:s}),this._updateTerminalLoadTab(e)}_clearTerminalLoadState(e,t){const s=this.terminalLoadStates.get(e);s&&s.generation!==t||(this.terminalLoadStates.delete(e),this._updateTerminalLoadTab(e))}_updateTerminalLoadTab(e){const t=this.$("sessionTabs")?.querySelector(`.session-tab[data-id="${e}"]`);if(!t)return;const s=this.terminalLoadStates.get(e);if(t.classList.toggle("tab-loading",!!s),s){if(t.setAttribute("aria-busy","true"),t.dataset.loadPhase=s.phase,!t.querySelector(".tab-load-spinner")){const i=document.createElement("span");i.className="tab-load-spinner",i.setAttribute("aria-hidden","true");const n=t.querySelector(".tab-number");n?n.insertAdjacentElement("afterend",i):t.insertBefore(i,t.firstChild)}}else t.setAttribute("aria-busy","false"),delete t.dataset.loadPhase,t.querySelector(".tab-load-spinner")?.remove()}_renderSessionTabsImmediate(){const e=this.$("sessionTabs"),t=e.querySelectorAll(".session-tab[data-id]"),s=new Set([...t].map(o=>o.dataset.id)),i=new Set(this.sessions.keys());if(s.size===i.size&&[...s].every(o=>i.has(o)))for(const[o,a]of this.sessions){const r=e.querySelector(`.session-tab[data-id="${o}"]`);if(!r)continue;const l=o===this.activeSessionId,u=a.status||"idle",c=this.getSessionName(a),p=a.taskStats||{running:0,total:0},_=p.running>0,m=this.terminalLoadStates.get(o);if(l&&!r.classList.contains("active")?r.classList.add("active"):!l&&r.classList.contains("active")&&r.classList.remove("active"),r.classList.toggle("tab-loading",!!m),m){if(r.setAttribute("aria-busy","true"),r.dataset.loadPhase=m.phase,!r.querySelector(".tab-load-spinner")){const S=document.createElement("span");S.className="tab-load-spinner",S.setAttribute("aria-hidden","true");const T=r.querySelector(".tab-number");T?T.insertAdjacentElement("afterend",S):r.insertBefore(S,r.firstChild)}}else r.setAttribute("aria-busy","false"),delete r.dataset.loadPhase,r.querySelector(".tab-load-spinner")?.remove();const w=this.tabAlerts.get(o),b=w==="action",R=w==="idle",g=r.classList.contains("tab-alert-action"),A=r.classList.contains("tab-alert-idle");if(b&&!g?(r.classList.add("tab-alert-action"),r.classList.remove("tab-alert-idle")):R&&!A?(r.classList.add("tab-alert-idle"),r.classList.remove("tab-alert-action")):!w&&(g||A)&&r.classList.remove("tab-alert-action","tab-alert-idle"),!r.querySelector(".tab-number")){const S=this.sessionOrder.indexOf(o);if(S>=0&&S<9){const T=document.createElement("span");T.className="tab-number",T.textContent=String(S+1),r.insertBefore(T,r.firstChild)}}const d=r.querySelector(".tab-status");d&&!d.classList.contains(u)&&(d.className=`tab-status ${u}`);const E=r.querySelector(".tab-name");if(E&&E.textContent!==c){const S=parseSessionPrefix(c);S&&S.suffix?E.innerHTML='<span class="tab-prefix">'+escapeHtml(S.prefix)+'</span><span class="tab-suffix">: '+escapeHtml(S.suffix)+"</span>":E.textContent=c}const C=r.querySelector(".tab-badge");if(_)if(C)C.textContent!==String(p.running)&&(C.textContent=p.running);else{this._fullRenderSessionTabs();return}else if(C){this._fullRenderSessionTabs();return}const f=r.querySelector(".tab-subagent-badge"),v=this.minimizedSubagents.get(o),y=v?.size||0;if(y>0&&f){const S=f.querySelector(".subagent-label"),T=y===1?"AGENT":`AGENTS (${y})`;S&&S.textContent!==T&&(S.textContent=T);const L=f.querySelector(".subagent-dropdown");if(L){const I=this.renderSubagentTabBadge(o,v),O=document.createElement("div");O.innerHTML=I;const k=O.querySelector(".subagent-dropdown");k&&(L.innerHTML=k.innerHTML)}}else if(y>0&&!f){const S=this.renderSubagentTabBadge(o,v),T=r.querySelector(".tab-gear");T&&T.insertAdjacentHTML("beforebegin",S)}else y===0&&f&&f.remove()}else this._fullRenderSessionTabs();this.updateTabOverflowMode()}updateTabOverflowMode(){const e=this.$("sessionTabs");if(!e)return;const t=MobileDetection.getDeviceType(),s=this.loadAppSettingsFromStorage(),i=this.getDefaultSettings(),n=t==="desktop"?s.tabTwoRows??i.tabTwoRows??!1:!1;if(n||t!=="desktop"){e.classList.remove("tabs-auto-wrap");return}e.classList.remove("tabs-auto-wrap");const o=window.CodemanTabOverflow?.shouldAutoWrapTabs?window.CodemanTabOverflow.shouldAutoWrapTabs({deviceType:t,manualTwoRows:n,tabCount:this.sessions.size,scrollWidth:e.scrollWidth,clientWidth:e.clientWidth}):e.scrollWidth>e.clientWidth+1;e.classList.toggle("tabs-auto-wrap",o)}_fullRenderSessionTabs(){if(this._inlineRenameActive)return;const e=this.$("sessionTabs");document.querySelectorAll("body > .subagent-dropdown").forEach(n=>n.remove()),this.cancelHideSubagentDropdown();const t=[];let s=this.sessionOrder;MobileDetection.getDeviceType()==="mobile"&&this.activeSessionId&&(s=[this.activeSessionId,...this.sessionOrder.filter(n=>n!==this.activeSessionId)]);let i=0;for(const n of s){const o=this.sessions.get(n);if(!o)continue;const a=n===this.activeSessionId,r=o.status||"idle",l=this.getSessionName(o),u=o.mode||"claude",c=o.color||"default",p=o.taskStats||{running:0,total:0},_=p.running>0,m=this.tabAlerts.get(n),w=m==="action"?" tab-alert-action":m==="idle"?" tab-alert-idle":"",b=this.terminalLoadStates.get(n),R=this.minimizedSubagents.get(n),A=(R?.size||0)>0?this.renderSubagentTabBadge(n,R):"",d=o.workingDir&&o.workingDir.split("/").pop()||"",C=(this._tallTabsEnabled??!1)&&o.name&&d&&d!==l;t.push(`<div class="session-tab ${a?"active":""}${w}${b?" tab-loading":""}" data-id="${n}" data-color="${c}" ${b?`data-load-phase="${escapeHtml(b.phase)}"`:""} onclick="app.handleSessionTabClick(event, '${escapeHtml(n)}')" oncontextmenu="event.preventDefault(); app.startInlineRename('${escapeHtml(n)}')" tabindex="0" role="tab" aria-selected="${a?"true":"false"}" aria-busy="${b?"true":"false"}" aria-label="${escapeHtml(l)} session" ${o.workingDir?`title="${escapeHtml(o.workingDir)}"`:""}>
16
+ Weekly limit: ${n??"\u2014"}% used (resets ${r(e.sevenDay)})`}_onScheduledCreated(e){this.currentRun=e,this.showTimer()}_onScheduledUpdated(e){this.currentRun=e,this.updateTimer()}_onScheduledCompleted(e){this.currentRun=e,this.hideTimer(),this.showToast("Scheduled run completed!","success")}_onScheduledStopped(){this.currentRun=null,this.hideTimer()}setConnectionStatus(e){this._connectionStatus=e,this._updateConnectionIndicator(),e==="connected"&&this._inputQueue.size>0&&this._drainInputQueues()}_connectWs(e){this._disconnectWs();const s=`${location.protocol==="https:"?"wss:":"ws:"}//${location.host}/ws/sessions/${e}/terminal`,i=new WebSocket(s);this._ws=i,this._wsSessionId=e,i.onopen=()=>{this._ws===i&&(this._wsReady=!0,this._wsReconnectAttempts=0,this.sendResize(e)?.catch?.(()=>{}),this._startMobileResizeRetry(e))},i.onmessage=n=>{if(this._ws===i)try{const o=JSON.parse(n.data);o.t==="o"?this._onSessionTerminal({id:e,data:o.d}):o.t==="c"?this._onSessionClearTerminal({id:e}):o.t==="r"&&this._onSessionNeedsRefresh({id:e})}catch{}},i.onclose=n=>{if(this._ws===i&&(this._ws=null,this._wsSessionId=null,this._wsReady=!1,this._stopMobileResizeRetry(),n.code<4004&&this.activeSessionId===e)){const o=Math.min(1e3*Math.pow(2,this._wsReconnectAttempts||0),1e4);this._wsReconnectAttempts=(this._wsReconnectAttempts||0)+1,this._wsReconnectTimer=setTimeout(()=>{this._wsReconnectTimer=null,this.activeSessionId===e&&this._connectWs(e)},o)}},i.onerror=()=>{}}_disconnectWs(){this._clearTimer("_wsReconnectTimer"),this._wsReconnectAttempts=0,this._stopMobileResizeRetry(),this._ws&&(this._ws.onclose=null,this._ws.close(),this._ws=null,this._wsSessionId=null,this._wsReady=!1)}_startMobileResizeRetry(e){this._stopMobileResizeRetry(),(typeof MobileDetection<"u"&&MobileDetection.getDeviceType?MobileDetection.getDeviceType():"desktop")!=="desktop"&&(this._mobileResizeRetryTimer=setInterval(()=>{document.visibilityState==="visible"&&(!this._wsReady||this._wsSessionId!==e||typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible||this.sendResize(e)?.catch?.(()=>{}))},MOBILE_RESIZE_RETRY_MS))}_stopMobileResizeRetry(){this._mobileResizeRetryTimer&&(clearInterval(this._mobileResizeRetryTimer),this._mobileResizeRetryTimer=null)}_sendInputAsync(e,t){if(!this.isOnline||this._connectionStatus==="disconnected"){this._enqueueInput(e,t);return}if(this._wsReady&&this._wsSessionId===e)try{this._ws.send(JSON.stringify({t:"i",d:t})),this.clearPendingHooks(e);return}catch{}this._inputSendChain=this._inputSendChain.then(()=>{fetch(`/api/sessions/${e}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:t}),keepalive:t.length<65536}).then(i=>{i.ok?this.clearPendingHooks(e):this._enqueueInput(e,t)}).catch(()=>{this._enqueueInput(e,t)})})}_enqueueInput(e,t){let i=(this._inputQueue.get(e)||"")+t;i.length>this._inputQueueMaxBytes&&(i=i.slice(i.length-this._inputQueueMaxBytes)),this._inputQueue.set(e,i),this._updateConnectionIndicator()}async _drainInputQueues(){if(this._inputQueue.size===0)return;const e=new Map(this._inputQueue);this._inputQueue.clear(),this._updateConnectionIndicator();for(const[t,s]of e)(await this._apiPost(`/api/sessions/${t}/input`,{input:s}))?.ok||this._enqueueInput(t,s);this._updateConnectionIndicator()}_updateConnectionIndicator(){const e=this.$("connectionIndicator"),t=this.$("connectionDot"),s=this.$("connectionText");if(!e||!t||!s)return;let i=0;for(const r of this._inputQueue.values())i+=r.length;const n=this._connectionStatus,o=i>0;if((n==="connected"||n==="connecting")&&!o){e.style.display="none";return}e.style.display="flex",t.className="connection-dot";const a=r=>r<1024?`${r}B`:`${(r/1024).toFixed(1)}KB`;n==="connected"&&o?(t.classList.add("draining"),s.textContent=`Sending ${a(i)}...`):n==="reconnecting"?(t.classList.add("reconnecting"),s.textContent=o?`Reconnecting (${a(i)} queued)`:"Reconnecting..."):(t.classList.add("offline"),s.textContent=o?`Offline (${a(i)} queued)`:"Offline")}setupOnlineDetection(){window.addEventListener("online",()=>{this.isOnline=!0,this.reconnectAttempts=0,this.connectSSE()}),window.addEventListener("offline",()=>{this.isOnline=!1,this.setConnectionStatus("offline")})}_updateCjkInputState(){const e=document.getElementById("cjkInput");if(!e)return;const t=this.loadAppSettingsFromStorage(),s=this.getDefaultSettings?.()||{},n=(this._serverCjkOverride||(t.cjkInputEnabled??s.cjkInputEnabled??!1))&&!!this.activeSessionId;e.classList.toggle("cjk-input-visible",!!n),document.body.classList.toggle("cjk-input-visible",!!n),e.style.display=n?"block":"none",e.setAttribute("aria-hidden",n?"false":"true"),n||(window.cjkActive=!1),typeof KeyboardHandler<"u"&&KeyboardHandler.updateLayoutForKeyboard()}_resetAllAppState(){this.sessions.clear(),this.ralphStates.clear(),this.terminalBuffers.clear(),this.terminalBufferCache.clear(),this._xtermSnapshots?.clear(),this.projectInsights.clear(),this.teams.clear(),this.teamTasks.clear();for(const e of this.idleTimers.values())clearTimeout(e);if(this.idleTimers.clear(),this._clearTimer("flickerFilterTimeout"),this.flickerFilterBuffer="",this.flickerFilterActive=!1,this._clearTimer("syncWaitTimeout"),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._bufferLoadOwner=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1,this._localEchoOverlay?.rerender(),this.pendingHooks.clear(),this._parentNameCache&&this._parentNameCache.clear(),this.subagentActivity.clear(),this.subagentToolResults.clear(),this.workflowRuns&&this.workflowRuns.clear(),this.workflowRunDetails&&this.workflowRunDetails.clear(),this.activeWorkflowRunId=null,this.activeWorkflowPhaseIndex=null,MobileDetection.cleanup(),KeyboardHandler.cleanup(),MobileDetection.init(),KeyboardHandler.init(),this.tabAlerts.clear(),this.attachmentHistoryCounts.clear(),this._shownCompletions&&this._shownCompletions.clear(),this.notificationManager?.titleFlashInterval&&(clearInterval(this.notificationManager.titleFlashInterval),this.notificationManager.titleFlashInterval=null),this.notificationManager?.groupingMap){for(const{timeout:e}of this.notificationManager.groupingMap.values())clearTimeout(e);this.notificationManager.groupingMap.clear()}this.terminalResizeObserver&&(this.terminalResizeObserver.disconnect(),this.terminalResizeObserver=null),this.planLoadingTimer&&(clearInterval(this.planLoadingTimer),this.planLoadingTimer=null),this.timerCountdownInterval&&(clearInterval(this.timerCountdownInterval),this.timerCountdownInterval=null),this.runSummaryAutoRefreshTimer&&(clearInterval(this.runSummaryAutoRefreshTimer),this.runSummaryAutoRefreshTimer=null)}handleInit(e){this._clearTimer("_initFallbackTimer");const t=++this._initGeneration;if(this._serverCjkOverride=e.inputCjkForm||!1,this._updateCjkInputState(),e.planUsage&&this.updatePlanUsageChip(e.planUsage),e.version){const n=this.$("versionDisplay"),o=this.$("headerVersion");n&&(n.textContent=`v${e.version}`,n.title=`Codeman v${e.version}`),o&&(o.textContent=`v${e.version}`,o.title=`Codeman v${e.version}`)}VoiceInput.cleanup(),this._resetAllAppState(),e.sessions.forEach(n=>{this.sessions.set(n.id,n),(n.ralphLoop||n.ralphTodos)&&!this.ralphClosedSessions.has(n.id)&&this.ralphStates.set(n.id,{loop:n.ralphLoop||null,todos:n.ralphTodos||[]})});try{localStorage.removeItem("codeman-tab-meta")}catch{}this.syncSessionOrder(),e.respawnStatus?this.respawnStatus=e.respawnStatus:this.respawnStatus={},this.respawnTimers={},this.respawnCountdownTimers={},this.respawnActionLogs={},e.globalStats&&(this.globalStats=e.globalStats),this.totalCost=e.sessions.reduce((n,o)=>n+(o.totalCost||0),0),this.totalCost+=e.scheduledRuns.reduce((n,o)=>n+(o.totalCost||0),0);const s=e.scheduledRuns.find(n=>n.status==="running");if(s&&(this.currentRun=s,this.showTimer()),this.updateCost(),this.renderSessionTabs(),this.sessions.size>0?this.startSystemStatsPolling():this.stopSystemStatsPolling(),this.cleanupAllFloatingWindows(),e.subagents&&(this.subagents.clear(),this.subagentActivity.clear(),this.subagentToolResults.clear(),e.subagents.forEach(n=>{this.subagents.set(n.agentId,n)}),this.renderSubagentPanel(),this.subagentParentMap.clear(),this.loadSubagentParentMap().then(()=>{for(const[n,o]of this.subagentParentMap){const a=this.subagents.get(n);if(a&&this.sessions.has(o)){a.parentSessionId=o;const r=this.sessions.get(o);r&&(a.parentSessionName=this.getSessionName(r)),this.subagents.set(n,a)}}for(const[n]of this.subagents)this.subagentParentMap.has(n)||this.findParentSessionForSubagent(n);this.restoreSubagentWindowStates()})),e.workflowRuns&&this.seedWorkflowRuns(e.workflowRuns),t!==this._initGeneration)return;if(this.isSoloWindow){this._applySoloMode();return}const i=this.activeSessionId;if(this.activeSessionId=null,this.sessionOrder.length>0){let n=i;if(!n||!this.sessions.has(n))try{n=localStorage.getItem("codeman-active-session")}catch{}n&&this.sessions.has(n)?this.selectSession(n):this.selectSession(this.sessionOrder[0])}}async loadState(){try{const t=await(await fetch("/api/status")).json();this.handleInit(t?.data??{})}catch(e){console.error("Failed to load state:",e)}}_debouncedCall(e,t,s=100){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{this._debounceTimers[e]=null,t.call(this)},s)}renderSessionTabs(){this._inlineRenameActive||this._debouncedCall("sessionTabs",this._renderSessionTabsImmediate)}_updateActiveTabImmediate(e){const t=this.$("sessionTabs");if(!t)return;const s=t.querySelectorAll(".session-tab[data-id]");for(const i of s)i.dataset.id===e?i.classList.add("active"):i.classList.remove("active")}_setTerminalLoadState(e,t,s){this.terminalLoadStates.set(e,{generation:t,phase:s}),this._updateTerminalLoadTab(e)}_clearTerminalLoadState(e,t){const s=this.terminalLoadStates.get(e);s&&s.generation!==t||(this.terminalLoadStates.delete(e),this._updateTerminalLoadTab(e))}_updateTerminalLoadTab(e){const t=this.$("sessionTabs")?.querySelector(`.session-tab[data-id="${e}"]`);if(!t)return;const s=this.terminalLoadStates.get(e);if(t.classList.toggle("tab-loading",!!s),s){if(t.setAttribute("aria-busy","true"),t.dataset.loadPhase=s.phase,!t.querySelector(".tab-load-spinner")){const i=document.createElement("span");i.className="tab-load-spinner",i.setAttribute("aria-hidden","true");const n=t.querySelector(".tab-number");n?n.insertAdjacentElement("afterend",i):t.insertBefore(i,t.firstChild)}}else t.setAttribute("aria-busy","false"),delete t.dataset.loadPhase,t.querySelector(".tab-load-spinner")?.remove()}_renderSessionTabsImmediate(){const e=this.$("sessionTabs"),t=e.querySelectorAll(".session-tab[data-id]"),s=new Set([...t].map(o=>o.dataset.id)),i=new Set(this.sessions.keys());if(s.size===i.size&&[...s].every(o=>i.has(o)))for(const[o,a]of this.sessions){const r=e.querySelector(`.session-tab[data-id="${o}"]`);if(!r)continue;const l=o===this.activeSessionId,d=a.status||"idle",c=this.getSessionName(a),p=a.taskStats||{running:0,total:0},g=p.running>0,m=this.terminalLoadStates.get(o);if(l&&!r.classList.contains("active")?r.classList.add("active"):!l&&r.classList.contains("active")&&r.classList.remove("active"),r.classList.toggle("tab-loading",!!m),m){if(r.setAttribute("aria-busy","true"),r.dataset.loadPhase=m.phase,!r.querySelector(".tab-load-spinner")){const S=document.createElement("span");S.className="tab-load-spinner",S.setAttribute("aria-hidden","true");const w=r.querySelector(".tab-number");w?w.insertAdjacentElement("afterend",S):r.insertBefore(S,r.firstChild)}}else r.setAttribute("aria-busy","false"),delete r.dataset.loadPhase,r.querySelector(".tab-load-spinner")?.remove();const b=this.tabAlerts.get(o),y=b==="action",C=b==="idle",E=r.classList.contains("tab-alert-action"),A=r.classList.contains("tab-alert-idle");if(y&&!E?(r.classList.add("tab-alert-action"),r.classList.remove("tab-alert-idle")):C&&!A?(r.classList.add("tab-alert-idle"),r.classList.remove("tab-alert-action")):!b&&(E||A)&&r.classList.remove("tab-alert-action","tab-alert-idle"),!r.querySelector(".tab-number")){const S=this.sessionOrder.indexOf(o);if(S>=0&&S<9){const w=document.createElement("span");w.className="tab-number",w.textContent=String(S+1),r.insertBefore(w,r.firstChild)}}const u=r.querySelector(".tab-status");u&&!u.classList.contains(d)&&(u.className=`tab-status ${d}`);const _=r.querySelector(".tab-name");if(_&&_.textContent!==c){const S=parseSessionPrefix(c);S&&S.suffix?_.innerHTML='<span class="tab-prefix">'+escapeHtml(S.prefix)+'</span><span class="tab-suffix">: '+escapeHtml(S.suffix)+"</span>":_.textContent=c}const R=r.querySelector(".tab-badge");if(g)if(R)R.textContent!==String(p.running)&&(R.textContent=p.running);else{this._fullRenderSessionTabs();return}else if(R){this._fullRenderSessionTabs();return}const f=r.querySelector(".tab-subagent-badge"),T=this.minimizedSubagents.get(o),v=T?.size||0;if(v>0&&f){const S=f.querySelector(".subagent-label"),w=v===1?"AGENT":`AGENTS (${v})`;S&&S.textContent!==w&&(S.textContent=w);const L=f.querySelector(".subagent-dropdown");if(L){const I=this.renderSubagentTabBadge(o,T),O=document.createElement("div");O.innerHTML=I;const k=O.querySelector(".subagent-dropdown");k&&(L.innerHTML=k.innerHTML)}}else if(v>0&&!f){const S=this.renderSubagentTabBadge(o,T),w=r.querySelector(".tab-gear");w&&w.insertAdjacentHTML("beforebegin",S)}else v===0&&f&&f.remove()}else this._fullRenderSessionTabs();this.updateTabOverflowMode()}updateTabOverflowMode(){const e=this.$("sessionTabs");if(!e)return;const t=MobileDetection.getDeviceType(),s=this.loadAppSettingsFromStorage(),i=this.getDefaultSettings(),n=t==="desktop"?s.tabTwoRows??i.tabTwoRows??!1:!1;if(n||t!=="desktop"){e.classList.remove("tabs-auto-wrap");return}e.classList.remove("tabs-auto-wrap");const o=window.CodemanTabOverflow?.shouldAutoWrapTabs?window.CodemanTabOverflow.shouldAutoWrapTabs({deviceType:t,manualTwoRows:n,tabCount:this.sessions.size,scrollWidth:e.scrollWidth,clientWidth:e.clientWidth}):e.scrollWidth>e.clientWidth+1;e.classList.toggle("tabs-auto-wrap",o)}_fullRenderSessionTabs(){if(this._inlineRenameActive)return;const e=this.$("sessionTabs");document.querySelectorAll("body > .subagent-dropdown").forEach(n=>n.remove()),this.cancelHideSubagentDropdown();const t=[];let s=this.sessionOrder;MobileDetection.getDeviceType()==="mobile"&&this.activeSessionId&&(s=[this.activeSessionId,...this.sessionOrder.filter(n=>n!==this.activeSessionId)]);let i=0;for(const n of s){const o=this.sessions.get(n);if(!o)continue;const a=n===this.activeSessionId,r=o.status||"idle",l=this.getSessionName(o),d=o.mode||"claude",c=o.color||"default",p=o.taskStats||{running:0,total:0},g=p.running>0,m=this.tabAlerts.get(n),b=m==="action"?" tab-alert-action":m==="idle"?" tab-alert-idle":"",y=this.terminalLoadStates.get(n),C=this.minimizedSubagents.get(n),A=(C?.size||0)>0?this.renderSubagentTabBadge(n,C):"",u=this.renderUltracodeTabBadge?this.renderUltracodeTabBadge(n):"",_=o.workingDir&&o.workingDir.split("/").pop()||"",f=(this._tallTabsEnabled??!1)&&o.name&&_&&_!==l;t.push(`<div class="session-tab ${a?"active":""}${b}${y?" tab-loading":""}" data-id="${n}" data-color="${c}" ${y?`data-load-phase="${escapeHtml(y.phase)}"`:""} onclick="app.handleSessionTabClick(event, '${escapeHtml(n)}')" oncontextmenu="event.preventDefault(); app.startInlineRename('${escapeHtml(n)}')" tabindex="0" role="tab" aria-selected="${a?"true":"false"}" aria-busy="${y?"true":"false"}" aria-label="${escapeHtml(l)} session" ${o.workingDir?`title="${escapeHtml(o.workingDir)}"`:""}>
17
17
  ${i<9?'<span class="tab-number">'+(i+1)+"</span>":""}
18
- ${b?'<span class="tab-load-spinner" aria-hidden="true"></span>':""}
18
+ ${y?'<span class="tab-load-spinner" aria-hidden="true"></span>':""}
19
19
  <span class="tab-status ${r}" aria-hidden="true"></span>
20
20
  <span class="tab-info">
21
21
  <span class="tab-name-row">
22
- ${u==="shell"?'<span class="tab-mode shell" aria-hidden="true">sh</span>':u==="opencode"?'<span class="tab-mode opencode" aria-hidden="true">oc</span>':u==="codex"?'<span class="tab-mode codex" aria-hidden="true">cx</span>':""}
23
- <span class="tab-name" data-session-id="${n}">${(()=>{const f=parseSessionPrefix(l);return f&&f.suffix?'<span class="tab-prefix">'+escapeHtml(f.prefix)+'</span><span class="tab-suffix">: '+escapeHtml(f.suffix)+"</span>":escapeHtml(l)})()}</span>
22
+ ${d==="shell"?'<span class="tab-mode shell" aria-hidden="true">sh</span>':d==="opencode"?'<span class="tab-mode opencode" aria-hidden="true">oc</span>':d==="codex"?'<span class="tab-mode codex" aria-hidden="true">cx</span>':""}
23
+ <span class="tab-name" data-session-id="${n}">${(()=>{const T=parseSessionPrefix(l);return T&&T.suffix?'<span class="tab-prefix">'+escapeHtml(T.prefix)+'</span><span class="tab-suffix">: '+escapeHtml(T.suffix)+"</span>":escapeHtml(l)})()}</span>
24
24
  <span class="tab-detached-badge" aria-hidden="true">detached</span>
25
25
  </span>
26
- ${C?`<span class="tab-folder">\u{1F4C1} ${escapeHtml(d)}</span>`:""}
26
+ ${f?`<span class="tab-folder">\u{1F4C1} ${escapeHtml(_)}</span>`:""}
27
27
  </span>
28
- ${_?`<span class="tab-badge" onclick="event.stopPropagation(); app.toggleTaskPanel()" aria-label="${p.running} running tasks">${p.running}</span>`:""}
28
+ ${g?`<span class="tab-badge" onclick="event.stopPropagation(); app.toggleTaskPanel()" aria-label="${p.running} running tasks">${p.running}</span>`:""}
29
29
  ${A}
30
+ ${u}
30
31
  <span class="tab-gear" onclick="event.stopPropagation(); app.openSessionOptions('${escapeHtml(n)}')" title="Session options" aria-label="Session options" tabindex="0">&#x2699;</span>
31
32
  <span class="tab-detach" onclick="event.stopPropagation(); app.detachSession('${escapeHtml(n)}')" title="Open in a new window" aria-label="Open session in a new window" tabindex="0">&#x29C9;</span>
32
33
  <span class="tab-close" onclick="event.stopPropagation(); app.requestCloseSession('${escapeHtml(n)}')" title="Close session" aria-label="Close session" tabindex="0">&times;</span>
33
- </div>`),i++}e.innerHTML=t.join(""),this.setupTabDragHandlers(),this.setupTabKeyboardNavigation(e),this.updateConnectionLines(),this.updateTabOverflowMode()}setupTabKeyboardNavigation(e){this._tabKeydownHandler&&e.removeEventListener("keydown",this._tabKeydownHandler),this._tabKeydownHandler=t=>{if(!["ArrowLeft","ArrowRight","Home","End","Enter"," "].includes(t.key))return;const s=[...e.querySelectorAll(".session-tab")],i=s.indexOf(document.activeElement);if((t.key==="Enter"||t.key===" ")&&i>=0){t.preventDefault();const o=s[i].dataset.id;this.selectSession(o,{forceReload:!0});return}if(i<0)return;let n;switch(t.key){case"ArrowLeft":n=i>0?i-1:s.length-1;break;case"ArrowRight":n=i<s.length-1?i+1:0;break;case"Home":n=0;break;case"End":n=s.length-1;break;default:return}t.preventDefault(),s[n]?.focus()},e.addEventListener("keydown",this._tabKeydownHandler)}handleSessionTabClick(e,t){return e?.preventDefault?.(),!(typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible===!0)&&MobileDetection.isTouchDevice()&&document.activeElement?.blur?.(),this.selectSession(t,{forceReload:!0})}syncSessionOrder(){const e=new Set(this.sessions.keys()),s=this.loadSessionOrder().filter(o=>e.has(o)),i=new Set(s),n=[...e].filter(o=>!i.has(o));this.sessionOrder=[...s,...n]}loadSessionOrder(){try{const e=localStorage.getItem("codeman-session-order");return e?JSON.parse(e):[]}catch{return[]}}saveSessionOrder(){try{localStorage.setItem("codeman-session-order",JSON.stringify(this.sessionOrder))}catch{}}setupTabDragHandlers(){const e=this.$("sessionTabs");e.querySelectorAll(".session-tab[data-id]").forEach(s=>{s.setAttribute("draggable","true"),s.addEventListener("dragstart",i=>{this.draggedTabId=s.dataset.id,s.classList.add("dragging"),i.dataTransfer.effectAllowed="move",i.dataTransfer.setData("text/plain",s.dataset.id)}),s.addEventListener("dragend",()=>{s.classList.remove("dragging"),this.draggedTabId=null,e.querySelectorAll(".session-tab").forEach(i=>{i.classList.remove("drag-over-left","drag-over-right")})}),s.addEventListener("dragover",i=>{if(i.preventDefault(),!this.draggedTabId||this.draggedTabId===s.dataset.id)return;i.dataTransfer.dropEffect="move";const n=s.getBoundingClientRect(),o=n.left+n.width/2,a=i.clientX<o;s.classList.toggle("drag-over-left",a),s.classList.toggle("drag-over-right",!a)}),s.addEventListener("dragleave",()=>{s.classList.remove("drag-over-left","drag-over-right")}),s.addEventListener("drop",i=>{if(i.preventDefault(),s.classList.remove("drag-over-left","drag-over-right"),!this.draggedTabId||this.draggedTabId===s.dataset.id)return;const n=s.dataset.id,o=this.draggedTabId,a=s.getBoundingClientRect(),r=a.left+a.width/2,l=i.clientX<r,u=this.sessionOrder.indexOf(o);let c=this.sessionOrder.indexOf(n);u===-1||c===-1||(this.sessionOrder.splice(u,1),c=this.sessionOrder.indexOf(n),c!==-1&&(l?this.sessionOrder.splice(c,0,o):this.sessionOrder.splice(c+1,0,o),this.saveSessionOrder(),this._fullRenderSessionTabs()))})})}moveActiveTabLeft(){if(!this.activeSessionId)return;const e=this.sessionOrder.indexOf(this.activeSessionId);e<=0||([this.sessionOrder[e-1],this.sessionOrder[e]]=[this.sessionOrder[e],this.sessionOrder[e-1]],this.saveSessionOrder(),this._fullRenderSessionTabs())}moveActiveTabRight(){if(!this.activeSessionId)return;const e=this.sessionOrder.indexOf(this.activeSessionId);e===-1||e>=this.sessionOrder.length-1||([this.sessionOrder[e],this.sessionOrder[e+1]]=[this.sessionOrder[e+1],this.sessionOrder[e]],this.saveSessionOrder(),this._fullRenderSessionTabs())}getShortId(e){if(!e)return"";let t=this._shortIdCache.get(e);return t||(t=e.slice(0,8),this._shortIdCache.set(e,t)),t}getSessionName(e){return e.name?e.name:e.workingDir?e.workingDir.split("/").pop()||e.workingDir:this.getShortId(e.id)}_notifySession(e,t,s,i,n){const o=this.sessions.get(e);this.notificationManager?.notify({urgency:t,category:s,sessionId:e,sessionName:o?.name||this.getShortId(e),title:i,message:n})}_isUsableXtermSnapshot(e){return!e||typeof e!="string"||e.length<8?!1:e.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g,"").replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g,"").replace(/\x1b[()][0-2A-Z]/g,"").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g,"").trim().length>=3}_persistXtermSnapshot(e,t){const s="codeman-xs-",n=()=>Object.keys(localStorage).filter(o=>o.startsWith(s)&&o!==e);try{if(localStorage.getItem(e)===null){const o=new Set(Array.from(this.sessions?.keys?.()||[])),a=n().sort((r,l)=>Number(o.has(r.slice(s.length)))-Number(o.has(l.slice(s.length))));for(;a.length>=10;)localStorage.removeItem(a.shift())}try{localStorage.setItem(e,t)}catch{for(const a of n()){localStorage.removeItem(a);try{localStorage.setItem(e,t);return}catch{}}try{localStorage.removeItem(e)}catch{}}}catch{}}_cleanupPreviousSession(e){const t=this.activeSessionId?this.sessions?.get?.(this.activeSessionId):null;if(this.activeSessionId&&t?.mode!=="shell"&&this._serializeAddon&&this._xtermSnapshots)try{const i=this._serializeAddon.serialize({scrollback:1e3});if(this._isUsableXtermSnapshot(i)){if(this._xtermSnapshots.delete(this.activeSessionId),this._xtermSnapshots.set(this.activeSessionId,i),this._xtermSnapshots.size>20){const n=this._xtermSnapshots.keys().next().value;this._xtermSnapshots.delete(n)}i.length<256*1024&&this._persistXtermSnapshot(`codeman-xs-${this.activeSessionId}`,i)}else{this._xtermSnapshots.delete(this.activeSessionId);try{localStorage.removeItem(`codeman-xs-${this.activeSessionId}`)}catch{}}}catch{}this._disconnectWs();const s=document.getElementById("cjkInput");s&&(s.value=""),this._clearTimer("flickerFilterTimeout"),this.flickerFilterBuffer="",this.flickerFilterActive=!1,this._tabCompletionSessionId=null,this._tabCompletionRetries=0,this._tabCompletionBaseText=null,this._clearTimer("_tabCompletionFallback"),this._clearTimer("_clientDropRecoveryTimer"),this._clearTimer("syncWaitTimeout"),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._bufferLoadOwner=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1;try{const i=this.terminal?._core?._compositionHelper;if(i?._isComposing){i._isComposing=!1;const n=this.terminal?.element?.querySelector(".xterm-helper-textarea");n&&n.dispatchEvent(new CompositionEvent("compositionend",{data:""}))}}catch{}if(this.activeSessionId){const i=this._localEchoOverlay?.pendingText||"",n=this._localEchoOverlay?.getFlushed()?.count||0,o=this._localEchoOverlay?.getFlushed()?.text||"";i&&this._sendInputAsync(this.activeSessionId,i);const a=n+i.length;a>0&&(this._flushedOffsets||(this._flushedOffsets=new Map),this._flushedTexts||(this._flushedTexts=new Map),this._flushedOffsets.set(this.activeSessionId,a),this._flushedTexts.set(this.activeSessionId,o+i))}this._localEchoOverlay?.clear(),this._localEchoOverlay&&!this._flushedOffsets?.has(e)&&this._localEchoOverlay.suppressBufferDetection()}_resetTerminalForReplay(){this.terminal.reset(),this.terminal.write("\x1B[3J\x1B[H\x1B[2J")}_shouldFocusTerminalForTabSwitch(){return typeof MobileDetection>"u"||!MobileDetection.isTouchDevice()?!0:typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible}async selectSession(e,t={}){if(!this.isSoloWindow&&this.detachedSessions.has(e)&&this._raiseDetached(e))return;const s=t?.forceReload===!0;if(this.activeSessionId===e&&!s)return;if(this.activeSessionId===e&&s){this.terminalBufferCache?.delete(e),this._xtermSnapshots?.delete(e);try{localStorage.removeItem(`codeman-xs-${e}`)}catch{}this._clearTimer("syncWaitTimeout"),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1,this.activeSessionId=null}const i=this._shouldFocusTerminalForTabSwitch();i&&this.terminal&&this.terminal.focus();const n=performance.now(),o=this.sessions.get(e)?.name||e.slice(0,8);_crashDiag.log(`SELECT: ${o}`),console.log(`[CRASH-DIAG] selectSession START: ${e.slice(0,8)}`);const a=++this._selectGeneration;if(this._setTerminalLoadState(e,a,"resizing"),a!==this._selectGeneration){this._clearTerminalLoadState(e,a);return}this._cleanupPreviousSession(e),this.activeSessionId=e;try{localStorage.setItem("codeman-active-session",e)}catch{}this._updateSseSubscription(e),this.hideWelcome(),this.clearPendingHooks(e,"idle_prompt"),this._updateActiveTabImmediate(e),this.renderSessionTabs(),this.updateAttachmentHistoryBadge?.(),this.attachmentHistoryDrawerOpen&&this.loadAttachmentHistory?.(e),this._updateLocalEchoState(),this._flushedOffsets?.has(e)&&this._localEchoOverlay&&this._localEchoOverlay.setFlushed(this._flushedOffsets.get(e),this._flushedTexts?.get(e)||"",!1);const r=document.querySelector(`.session-tab.active[data-id="${e}"]`);r&&(r.classList.add("tab-glow"),r.addEventListener("animationend",()=>r.classList.remove("tab-glow"),{once:!0}));const l=this.sessions.get(e);if(this.currentSessionWorkingDir=l?.workingDir||null,l&&l.pid===null)try{const c=l.mode==="shell"?`/api/sessions/${e}/shell`:`/api/sessions/${e}/interactive`;await fetch(c,{method:"POST"}),l.status="busy"}catch(c){console.error("Failed to attach to restored session:",c)}this._restoringFlushedState=!0;const u=this._beginBufferLoad(a);try{this.fitAddon&&this.fitAddon.fit();const c=await this.sendResize(e,{forceHttp:!0}).catch(()=>!1);if(this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}let p=this._xtermSnapshots?.get(e);if(p&&!this._isUsableXtermSnapshot(p)&&(this._xtermSnapshots?.delete(e),p=null),!p)try{const d=localStorage.getItem(`codeman-xs-${e}`);d&&this._isUsableXtermSnapshot(d)?(p=d,this._xtermSnapshots?.delete(e),this._xtermSnapshots?.set(e,d)):d&&localStorage.removeItem(`codeman-xs-${e}`)}catch{}const _=l&&(l.status==="busy"||l.status==="working");let m=!1;if(p&&!_&&l?.mode!=="shell"){if(_crashDiag.log(`SNAPSHOT_RESTORE: ${(p.length/1024).toFixed(0)}KB`),this._setTerminalLoadState(e,a,"replaying"),this._resetTerminalForReplay(),await new Promise(d=>this.terminal.write(p,d)),this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}this.scrollToLastNonEmptyLine(),_crashDiag.log("SNAPSHOT_RESTORE_DONE"),m=!0}const w=this.terminalBufferCache.get(e);let b=!1;if(w&&!_&&!m){if(_crashDiag.log(`CACHE_WRITE: ${(w.length/1024).toFixed(0)}KB`),this._setTerminalLoadState(e,a,"replaying"),this._resetTerminalForReplay(),await this.chunkedTerminalWrite(w,TERMINAL_CHUNK_SIZE,u),this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}this.terminal.scrollToBottom(),_crashDiag.log("CACHE_DONE")}else _&&(this._resetTerminalForReplay(),b=!0,_crashDiag.log("CACHE_SKIP_BUSY"));if(l?.mode!=="shell"&&c&&(await new Promise(d=>setTimeout(d,TUI_REDRAW_SETTLE_MS)),this._isStaleSelect(a))){this._clearTerminalLoadState(e,a);return}this._setTerminalLoadState(e,a,"fetching"),_crashDiag.log("FETCH_START");const R=await fetch(`/api/sessions/${e}/terminal?tail=${TERMINAL_TAIL_SIZE}`);if(this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}const g=(await R.json())?.data??{};if(_crashDiag.log(`FETCH_DONE: ${g.terminalBuffer?(g.terminalBuffer.length/1024).toFixed(0)+"KB":"empty"} truncated=${g.truncated}`),g.terminalBuffer){if(m||b||g.terminalBuffer!==w){if(_crashDiag.log(`REWRITE: ${(g.terminalBuffer.length/1024).toFixed(0)}KB`),this._setTerminalLoadState(e,a,"replaying"),this._resetTerminalForReplay(),g.truncated&&this.terminal.write(`\x1B[90m... (earlier output truncated for performance) ...\x1B[0m\r
34
+ </div>`),i++}e.innerHTML=t.join(""),this.setupTabDragHandlers(),this.setupTabKeyboardNavigation(e),this.updateConnectionLines(),this.updateTabOverflowMode()}setupTabKeyboardNavigation(e){this._tabKeydownHandler&&e.removeEventListener("keydown",this._tabKeydownHandler),this._tabKeydownHandler=t=>{if(!["ArrowLeft","ArrowRight","Home","End","Enter"," "].includes(t.key))return;const s=[...e.querySelectorAll(".session-tab")],i=s.indexOf(document.activeElement);if((t.key==="Enter"||t.key===" ")&&i>=0){t.preventDefault();const o=s[i].dataset.id;this.selectSession(o,{forceReload:!0});return}if(i<0)return;let n;switch(t.key){case"ArrowLeft":n=i>0?i-1:s.length-1;break;case"ArrowRight":n=i<s.length-1?i+1:0;break;case"Home":n=0;break;case"End":n=s.length-1;break;default:return}t.preventDefault(),s[n]?.focus()},e.addEventListener("keydown",this._tabKeydownHandler)}handleSessionTabClick(e,t){return e?.preventDefault?.(),!(typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible===!0)&&MobileDetection.isTouchDevice()&&document.activeElement?.blur?.(),this.selectSession(t,{forceReload:!0})}syncSessionOrder(){const e=new Set(this.sessions.keys()),s=this.loadSessionOrder().filter(o=>e.has(o)),i=new Set(s),n=[...e].filter(o=>!i.has(o));this.sessionOrder=[...s,...n]}loadSessionOrder(){try{const e=localStorage.getItem("codeman-session-order");return e?JSON.parse(e):[]}catch{return[]}}saveSessionOrder(){try{localStorage.setItem("codeman-session-order",JSON.stringify(this.sessionOrder))}catch{}}setupTabDragHandlers(){const e=this.$("sessionTabs");e.querySelectorAll(".session-tab[data-id]").forEach(s=>{s.setAttribute("draggable","true"),s.addEventListener("dragstart",i=>{this.draggedTabId=s.dataset.id,s.classList.add("dragging"),i.dataTransfer.effectAllowed="move",i.dataTransfer.setData("text/plain",s.dataset.id)}),s.addEventListener("dragend",()=>{s.classList.remove("dragging"),this.draggedTabId=null,e.querySelectorAll(".session-tab").forEach(i=>{i.classList.remove("drag-over-left","drag-over-right")})}),s.addEventListener("dragover",i=>{if(i.preventDefault(),!this.draggedTabId||this.draggedTabId===s.dataset.id)return;i.dataTransfer.dropEffect="move";const n=s.getBoundingClientRect(),o=n.left+n.width/2,a=i.clientX<o;s.classList.toggle("drag-over-left",a),s.classList.toggle("drag-over-right",!a)}),s.addEventListener("dragleave",()=>{s.classList.remove("drag-over-left","drag-over-right")}),s.addEventListener("drop",i=>{if(i.preventDefault(),s.classList.remove("drag-over-left","drag-over-right"),!this.draggedTabId||this.draggedTabId===s.dataset.id)return;const n=s.dataset.id,o=this.draggedTabId,a=s.getBoundingClientRect(),r=a.left+a.width/2,l=i.clientX<r,d=this.sessionOrder.indexOf(o);let c=this.sessionOrder.indexOf(n);d===-1||c===-1||(this.sessionOrder.splice(d,1),c=this.sessionOrder.indexOf(n),c!==-1&&(l?this.sessionOrder.splice(c,0,o):this.sessionOrder.splice(c+1,0,o),this.saveSessionOrder(),this._fullRenderSessionTabs()))})})}moveActiveTabLeft(){if(!this.activeSessionId)return;const e=this.sessionOrder.indexOf(this.activeSessionId);e<=0||([this.sessionOrder[e-1],this.sessionOrder[e]]=[this.sessionOrder[e],this.sessionOrder[e-1]],this.saveSessionOrder(),this._fullRenderSessionTabs())}moveActiveTabRight(){if(!this.activeSessionId)return;const e=this.sessionOrder.indexOf(this.activeSessionId);e===-1||e>=this.sessionOrder.length-1||([this.sessionOrder[e],this.sessionOrder[e+1]]=[this.sessionOrder[e+1],this.sessionOrder[e]],this.saveSessionOrder(),this._fullRenderSessionTabs())}getShortId(e){if(!e)return"";let t=this._shortIdCache.get(e);return t||(t=e.slice(0,8),this._shortIdCache.set(e,t)),t}getSessionName(e){return e.name?e.name:e.workingDir?e.workingDir.split("/").pop()||e.workingDir:this.getShortId(e.id)}_notifySession(e,t,s,i,n){const o=this.sessions.get(e);this.notificationManager?.notify({urgency:t,category:s,sessionId:e,sessionName:o?.name||this.getShortId(e),title:i,message:n})}_isUsableXtermSnapshot(e){return!e||typeof e!="string"||e.length<8?!1:e.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g,"").replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g,"").replace(/\x1b[()][0-2A-Z]/g,"").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g,"").trim().length>=3}_persistXtermSnapshot(e,t){const s="codeman-xs-",n=()=>Object.keys(localStorage).filter(o=>o.startsWith(s)&&o!==e);try{if(localStorage.getItem(e)===null){const o=new Set(Array.from(this.sessions?.keys?.()||[])),a=n().sort((r,l)=>Number(o.has(r.slice(s.length)))-Number(o.has(l.slice(s.length))));for(;a.length>=10;)localStorage.removeItem(a.shift())}try{localStorage.setItem(e,t)}catch{for(const a of n()){localStorage.removeItem(a);try{localStorage.setItem(e,t);return}catch{}}try{localStorage.removeItem(e)}catch{}}}catch{}}_cleanupPreviousSession(e){const t=this.activeSessionId?this.sessions?.get?.(this.activeSessionId):null;if(this.activeSessionId&&t?.mode!=="shell"&&this._serializeAddon&&this._xtermSnapshots)try{const i=this._serializeAddon.serialize({scrollback:1e3});if(this._isUsableXtermSnapshot(i)){if(this._xtermSnapshots.delete(this.activeSessionId),this._xtermSnapshots.set(this.activeSessionId,i),this._xtermSnapshots.size>20){const n=this._xtermSnapshots.keys().next().value;this._xtermSnapshots.delete(n)}i.length<256*1024&&this._persistXtermSnapshot(`codeman-xs-${this.activeSessionId}`,i)}else{this._xtermSnapshots.delete(this.activeSessionId);try{localStorage.removeItem(`codeman-xs-${this.activeSessionId}`)}catch{}}}catch{}this._disconnectWs();const s=document.getElementById("cjkInput");s&&(s.value=""),this._clearTimer("flickerFilterTimeout"),this.flickerFilterBuffer="",this.flickerFilterActive=!1,this._tabCompletionSessionId=null,this._tabCompletionRetries=0,this._tabCompletionBaseText=null,this._clearTimer("_tabCompletionFallback"),this._clearTimer("_clientDropRecoveryTimer"),this._clearTimer("syncWaitTimeout"),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._bufferLoadOwner=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1;try{const i=this.terminal?._core?._compositionHelper;if(i?._isComposing){i._isComposing=!1;const n=this.terminal?.element?.querySelector(".xterm-helper-textarea");n&&n.dispatchEvent(new CompositionEvent("compositionend",{data:""}))}}catch{}if(this.activeSessionId){const i=this._localEchoOverlay?.pendingText||"",n=this._localEchoOverlay?.getFlushed()?.count||0,o=this._localEchoOverlay?.getFlushed()?.text||"";i&&this._sendInputAsync(this.activeSessionId,i);const a=n+i.length;a>0&&(this._flushedOffsets||(this._flushedOffsets=new Map),this._flushedTexts||(this._flushedTexts=new Map),this._flushedOffsets.set(this.activeSessionId,a),this._flushedTexts.set(this.activeSessionId,o+i))}this._localEchoOverlay?.clear(),this._localEchoOverlay&&!this._flushedOffsets?.has(e)&&this._localEchoOverlay.suppressBufferDetection()}_resetTerminalForReplay(){this.terminal.reset(),this.terminal.write("\x1B[3J\x1B[H\x1B[2J")}_shouldFocusTerminalForTabSwitch(){return typeof MobileDetection>"u"||!MobileDetection.isTouchDevice()?!0:typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible}async selectSession(e,t={}){if(!this.isSoloWindow&&this.detachedSessions.has(e)&&this._raiseDetached(e))return;const s=t?.forceReload===!0;if(this.activeSessionId===e&&!s)return;if(this.activeSessionId===e&&s){this.terminalBufferCache?.delete(e),this._xtermSnapshots?.delete(e);try{localStorage.removeItem(`codeman-xs-${e}`)}catch{}this._clearTimer("syncWaitTimeout"),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1,this.activeSessionId=null}const i=this._shouldFocusTerminalForTabSwitch();i&&this.terminal&&this.terminal.focus();const n=performance.now(),o=this.sessions.get(e)?.name||e.slice(0,8);_crashDiag.log(`SELECT: ${o}`),console.log(`[CRASH-DIAG] selectSession START: ${e.slice(0,8)}`);const a=++this._selectGeneration;if(this._setTerminalLoadState(e,a,"resizing"),a!==this._selectGeneration){this._clearTerminalLoadState(e,a);return}this._cleanupPreviousSession(e),this.activeSessionId=e;try{localStorage.setItem("codeman-active-session",e)}catch{}this._updateSseSubscription(e),this.hideWelcome(),this.clearPendingHooks(e,"idle_prompt"),this._updateActiveTabImmediate(e),this.renderSessionTabs(),this.updateAttachmentHistoryBadge?.(),this.attachmentHistoryDrawerOpen&&this.loadAttachmentHistory?.(e),this._updateLocalEchoState(),this._flushedOffsets?.has(e)&&this._localEchoOverlay&&this._localEchoOverlay.setFlushed(this._flushedOffsets.get(e),this._flushedTexts?.get(e)||"",!1);const r=document.querySelector(`.session-tab.active[data-id="${e}"]`);r&&(r.classList.add("tab-glow"),r.addEventListener("animationend",()=>r.classList.remove("tab-glow"),{once:!0}));const l=this.sessions.get(e);if(this.currentSessionWorkingDir=l?.workingDir||null,l&&l.pid===null)try{const c=l.mode==="shell"?`/api/sessions/${e}/shell`:`/api/sessions/${e}/interactive`;await fetch(c,{method:"POST"}),l.status="busy"}catch(c){console.error("Failed to attach to restored session:",c)}this._restoringFlushedState=!0;const d=this._beginBufferLoad(a);try{this.fitAddon&&this.fitAddon.fit();const c=await this.sendResize(e,{forceHttp:!0}).catch(()=>!1);if(this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}let p=this._xtermSnapshots?.get(e);if(p&&!this._isUsableXtermSnapshot(p)&&(this._xtermSnapshots?.delete(e),p=null),!p)try{const u=localStorage.getItem(`codeman-xs-${e}`);u&&this._isUsableXtermSnapshot(u)?(p=u,this._xtermSnapshots?.delete(e),this._xtermSnapshots?.set(e,u)):u&&localStorage.removeItem(`codeman-xs-${e}`)}catch{}const g=l&&(l.status==="busy"||l.status==="working");let m=!1;if(p&&!g&&l?.mode!=="shell"){if(_crashDiag.log(`SNAPSHOT_RESTORE: ${(p.length/1024).toFixed(0)}KB`),this._setTerminalLoadState(e,a,"replaying"),this._resetTerminalForReplay(),await new Promise(u=>this.terminal.write(p,u)),this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}this.scrollToLastNonEmptyLine(),_crashDiag.log("SNAPSHOT_RESTORE_DONE"),m=!0}const b=this.terminalBufferCache.get(e);let y=!1;if(b&&!g&&!m){if(_crashDiag.log(`CACHE_WRITE: ${(b.length/1024).toFixed(0)}KB`),this._setTerminalLoadState(e,a,"replaying"),this._resetTerminalForReplay(),await this.chunkedTerminalWrite(b,TERMINAL_CHUNK_SIZE,d),this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}this.terminal.scrollToBottom(),_crashDiag.log("CACHE_DONE")}else g&&(this._resetTerminalForReplay(),y=!0,_crashDiag.log("CACHE_SKIP_BUSY"));if(l?.mode!=="shell"&&c&&(await new Promise(u=>setTimeout(u,TUI_REDRAW_SETTLE_MS)),this._isStaleSelect(a))){this._clearTerminalLoadState(e,a);return}this._setTerminalLoadState(e,a,"fetching"),_crashDiag.log("FETCH_START");const C=await fetch(`/api/sessions/${e}/terminal?tail=${TERMINAL_TAIL_SIZE}`);if(this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}const E=(await C.json())?.data??{};if(_crashDiag.log(`FETCH_DONE: ${E.terminalBuffer?(E.terminalBuffer.length/1024).toFixed(0)+"KB":"empty"} truncated=${E.truncated}`),E.terminalBuffer){if(m||y||E.terminalBuffer!==b){if(_crashDiag.log(`REWRITE: ${(E.terminalBuffer.length/1024).toFixed(0)}KB`),this._setTerminalLoadState(e,a,"replaying"),this._resetTerminalForReplay(),E.truncated&&this.terminal.write(`\x1B[90m... (earlier output truncated for performance) ...\x1B[0m\r
34
35
  \r
35
- `),await this.chunkedTerminalWrite(g.terminalBuffer,TERMINAL_CHUNK_SIZE,u),this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}this.terminal.scrollToBottom()}if(this.terminalBufferCache.set(e,g.terminalBuffer),this.terminalBufferCache.size>20){const E=this.terminalBufferCache.keys().next().value;this.terminalBufferCache.delete(E)}}else w||this._resetTerminalForReplay();if(this._isLoadingBuffer&&this._finishBufferLoad(u),this._restoringFlushedState=!1,this._flushedOffsets?.has(e)&&this._localEchoOverlay){this._localEchoOverlay.setFlushed(this._flushedOffsets.get(e),this._flushedTexts?.get(e)||"",!1);const d=this._localEchoOverlay;this.terminal.write("",()=>{d.hasPending&&d.rerender()})}this.sendResize(e),(typeof requestIdleCallback=="function"?requestIdleCallback:d=>setTimeout(d,16))(()=>{if(a!==this._selectGeneration)return;this.respawnStatus[e]?(this.showRespawnBanner(),this.updateRespawnBanner(this.respawnStatus[e].state),document.getElementById("respawnCycleCount").textContent=this.respawnStatus[e].cycleCount||0,this.updateCountdownTimerDisplay(),this.updateActionLogDisplay(),Object.keys(this.respawnCountdownTimers[e]||{}).length>0&&this.startCountdownInterval()):(this.hideRespawnBanner(),this.stopCountdownInterval());const d=document.getElementById("taskPanel");d&&d.classList.contains("open")&&this.renderTaskPanel();const E=this.sessions.get(e);if(E&&(E.ralphLoop||E.ralphTodos)&&this.updateRalphState(e,{loop:E.ralphLoop,todos:E.ralphTodos}),this.renderRalphStatePanel(),this.updateCliInfoDisplay(),this.renderProjectInsightsPanel(),this.updateSubagentWindowVisibility(),this.loadAppSettingsFromStorage().showFileBrowser){const f=this.$("fileBrowserPanel");if(f&&(f.classList.add("visible"),this.loadFileBrowser(e),!this.fileBrowserDragListeners)){const v=f.querySelector(".file-browser-header");if(v){const y=()=>{if(!f.style.left){const S=f.getBoundingClientRect();f.style.left=`${S.left}px`,f.style.top=`${S.top}px`,f.style.right="auto"}};v.addEventListener("mousedown",y),v.addEventListener("touchstart",y,{passive:!0}),this.fileBrowserDragListeners=this.makeWindowDraggable(f,v),this.fileBrowserDragListeners._onFirstDrag=y}}}}),this._connectWs(e),_crashDiag.log("FOCUS"),i&&this.terminal&&this.terminal.focus(),this.scrollToLastNonEmptyLine(),this._clearTerminalLoadState(e,a),_crashDiag.log(`SELECT_DONE: ${(performance.now()-n).toFixed(0)}ms`),console.log(`[CRASH-DIAG] selectSession DONE: ${e.slice(0,8)} in ${(performance.now()-n).toFixed(0)}ms`)}catch(c){this._isLoadingBuffer&&this._finishBufferLoad(u),this._restoringFlushedState=!1,this._setTerminalLoadState(e,a,"failed"),console.error("Failed to load session terminal:",c)}}_cleanupSessionData(e){this._activeRename?.sessionId===e&&this._activeRename.cancel(),this.sessions.delete(e);const t=this.sessionOrder.indexOf(e);t!==-1&&(this.sessionOrder.splice(t,1),this.saveSessionOrder()),this.terminalBuffers.delete(e),this.terminalBufferCache.delete(e),this._xtermSnapshots?.delete(e);try{localStorage.removeItem(`codeman-xs-${e}`)}catch{}this._flushedOffsets?.delete(e),this._flushedTexts?.delete(e),this._inputQueue.delete(e),this.ralphStates.delete(e),this.ralphClosedSessions.delete(e),this.projectInsights.delete(e),this.pendingHooks.delete(e),this.tabAlerts.delete(e),this.attachmentHistoryCounts.delete(e),this.attachmentHistoryDrawerOpen&&this.activeSessionId===e&&this.closeAttachmentHistory?.(),this.terminalLoadStates.delete(e),this.clearCountdownTimers(e),this.closeSessionLogViewerWindows(e),this.closeSessionImagePopups(e),this.closeSessionAttachmentCards(e),this.closeSessionSubagentWindows(e,!0);const s=this.idleTimers.get(e);s&&(clearTimeout(s),this.idleTimers.delete(e)),delete this.respawnStatus[e],delete this.respawnTimers[e],delete this.respawnCountdownTimers[e],delete this.respawnActionLogs[e]}async closeSession(e,t=!0){try{if(await this._apiDelete(`/api/sessions/${e}?killMux=${t}`),this._cleanupSessionData(e),this.activeSessionId===e){this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}if(this.sessionOrder.length>0&&this.sessions.size>0){const s=this.sessionOrder[0];this.selectSession(s)}else this.terminal.clear(),this.showWelcome(),this.renderRalphStatePanel()}this.renderSessionTabs(),t?this.showToast("Session closed and tmux killed","success"):this.showToast("Tab hidden, tmux still running","info")}catch{this.showToast("Failed to close session","error")}}requestCloseSession(e){const t=this.sessions.get(e);if(!t)return;this.pendingCloseSessionId=e;const s=this.getSessionName(t),i=document.getElementById("closeConfirmSessionName");i.textContent=s;const n=document.getElementById("closeConfirmKillTitle");n&&(n.textContent=t.mode==="opencode"?"Kill Tmux & OpenCode":t.mode==="codex"?"Kill Tmux & Codex":"Kill Tmux & Claude Code"),document.getElementById("closeConfirmModal").classList.add("active")}cancelCloseSession(){this.pendingCloseSessionId=null,document.getElementById("closeConfirmModal").classList.remove("active")}async confirmCloseSession(e=!0){const t=this.pendingCloseSessionId;this.cancelCloseSession(),t&&await this.closeSession(t,e)}nextSession(){if(this.sessionOrder.length<=1)return;const t=(this.sessionOrder.indexOf(this.activeSessionId)+1)%this.sessionOrder.length;this.selectSession(this.sessionOrder[t])}prevSession(){if(this.sessionOrder.length<=1)return;const t=(this.sessionOrder.indexOf(this.activeSessionId)-1+this.sessionOrder.length)%this.sessionOrder.length;this.selectSession(this.sessionOrder[t])}goHome(){this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.terminal.clear(),this.showWelcome(),this.renderSessionTabs(),this.renderRalphStatePanel()}ralphWizardStep=1;ralphWizardConfig={taskDescription:"",completionPhrase:"COMPLETE",maxIterations:10,caseName:"testcase",enableRespawn:!1,generatedPlan:null,planGenerated:!1,skipPlanGeneration:!1,planDetailLevel:"detailed",existingPlan:null,useExistingPlan:!1};planLoadingTimer=null;planLoadingStartTime=null;async killActiveSession(){if(!this.activeSessionId){this.showToast("No active session","warning");return}await this.closeSession(this.activeSessionId)}async killAllSessions(){if(this.sessions.size!==0&&confirm(`Kill all ${this.sessions.size} session(s)?`))try{await this._apiDelete("/api/sessions"),this.sessions.clear(),this.terminalBuffers.clear(),this.terminalBufferCache.clear(),this.terminalLoadStates.clear(),this._xtermSnapshots?.clear();try{for(const e of Object.keys(localStorage))e.startsWith("codeman-xs-")&&localStorage.removeItem(e)}catch{}this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.respawnStatus={},this.respawnCountdownTimers={},this.respawnActionLogs={},this.stopCountdownInterval(),this.hideRespawnBanner(),this.renderSessionTabs(),this.terminal.clear(),this.showWelcome(),this.showToast("All sessions killed","success")}catch{this.showToast("Failed to kill sessions","error")}}showTimer(){document.getElementById("timerBanner").style.display="flex",this.updateTimer(),this.timerInterval=setInterval(()=>this.updateTimer(),1e3)}hideTimer(){document.getElementById("timerBanner").style.display="none",this.timerInterval&&(clearInterval(this.timerInterval),this.timerInterval=null)}updateTimer(){if(!this.currentRun||this.currentRun.status!=="running")return;const e=Date.now(),t=Math.max(0,this.currentRun.endAt-e),s=this.currentRun.endAt-this.currentRun.startedAt,i=e-this.currentRun.startedAt,n=Math.min(100,i/s*100);document.getElementById("timerValue").textContent=this.formatTime(t),document.getElementById("timerProgress").style.width=`${n}%`,document.getElementById("timerMeta").textContent=`${this.currentRun.completedTasks} tasks | $${this.currentRun.totalCost.toFixed(2)}`}async stopCurrentRun(){if(this.currentRun)try{await fetch(`/api/scheduled/${this.currentRun.id}`,{method:"DELETE"})}catch{this.showToast("Failed to stop run","error")}}formatTime(e){const t=Math.floor(e/1e3),s=Math.floor(t/3600),i=Math.floor(t%3600/60),n=t%60;return`${s.toString().padStart(2,"0")}:${i.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}`}updateCost(){this.updateTokens()}updateTokens(){this._clearTimer("_updateTokensTimeout"),this._updateTokensTimeout=setTimeout(()=>{this._updateTokensTimeout=null,this._updateTokensImmediate()},200)}_updateTokensImmediate(){let e=0,t=0;this.globalStats?(e=this.globalStats.totalInputTokens||0,t=this.globalStats.totalOutputTokens||0):this.sessions.forEach(a=>{a.tokens&&(e+=a.tokens.input||0,t+=a.tokens.output||0)});const s=e+t;this.totalTokens=s;const i=this.formatTokens(s),n=this.estimateCost(e,t),o=this.$("headerTokens");if(o){const r=this.loadAppSettingsFromStorage().showCost??!1;o.textContent=s>0?r?`${i} tokens \xB7 $${n.toFixed(2)}`:`${i} tokens`:"0 tokens",o.title=this.globalStats?`Lifetime: ${this.globalStats.totalSessionsCreated} sessions created${r?`
36
+ `),await this.chunkedTerminalWrite(E.terminalBuffer,TERMINAL_CHUNK_SIZE,d),this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}this.terminal.scrollToBottom()}if(this.terminalBufferCache.set(e,E.terminalBuffer),this.terminalBufferCache.size>20){const _=this.terminalBufferCache.keys().next().value;this.terminalBufferCache.delete(_)}}else b||this._resetTerminalForReplay();if(this._isLoadingBuffer&&this._finishBufferLoad(d),this._restoringFlushedState=!1,this._flushedOffsets?.has(e)&&this._localEchoOverlay){this._localEchoOverlay.setFlushed(this._flushedOffsets.get(e),this._flushedTexts?.get(e)||"",!1);const u=this._localEchoOverlay;this.terminal.write("",()=>{u.hasPending&&u.rerender()})}this.sendResize(e),(typeof requestIdleCallback=="function"?requestIdleCallback:u=>setTimeout(u,16))(()=>{if(a!==this._selectGeneration)return;this.respawnStatus[e]?(this.showRespawnBanner(),this.updateRespawnBanner(this.respawnStatus[e].state),document.getElementById("respawnCycleCount").textContent=this.respawnStatus[e].cycleCount||0,this.updateCountdownTimerDisplay(),this.updateActionLogDisplay(),Object.keys(this.respawnCountdownTimers[e]||{}).length>0&&this.startCountdownInterval()):(this.hideRespawnBanner(),this.stopCountdownInterval());const u=document.getElementById("taskPanel");u&&u.classList.contains("open")&&this.renderTaskPanel();const _=this.sessions.get(e);if(_&&(_.ralphLoop||_.ralphTodos)&&this.updateRalphState(e,{loop:_.ralphLoop,todos:_.ralphTodos}),this.renderRalphStatePanel(),this.updateCliInfoDisplay(),this.renderProjectInsightsPanel(),this.updateSubagentWindowVisibility(),this.loadAppSettingsFromStorage().showFileBrowser){const f=this.$("fileBrowserPanel");if(f&&(f.classList.add("visible"),this.loadFileBrowser(e),!this.fileBrowserDragListeners)){const T=f.querySelector(".file-browser-header");if(T){const v=()=>{if(!f.style.left){const S=f.getBoundingClientRect();f.style.left=`${S.left}px`,f.style.top=`${S.top}px`,f.style.right="auto"}};T.addEventListener("mousedown",v),T.addEventListener("touchstart",v,{passive:!0}),this.fileBrowserDragListeners=this.makeWindowDraggable(f,T),this.fileBrowserDragListeners._onFirstDrag=v}}}}),this._connectWs(e),_crashDiag.log("FOCUS"),i&&this.terminal&&this.terminal.focus(),this.scrollToLastNonEmptyLine(),this._clearTerminalLoadState(e,a),_crashDiag.log(`SELECT_DONE: ${(performance.now()-n).toFixed(0)}ms`),console.log(`[CRASH-DIAG] selectSession DONE: ${e.slice(0,8)} in ${(performance.now()-n).toFixed(0)}ms`)}catch(c){this._isLoadingBuffer&&this._finishBufferLoad(d),this._restoringFlushedState=!1,this._setTerminalLoadState(e,a,"failed"),console.error("Failed to load session terminal:",c)}}_cleanupSessionData(e){this._activeRename?.sessionId===e&&this._activeRename.cancel(),this.sessions.delete(e);const t=this.sessionOrder.indexOf(e);t!==-1&&(this.sessionOrder.splice(t,1),this.saveSessionOrder()),this.terminalBuffers.delete(e),this.terminalBufferCache.delete(e),this._xtermSnapshots?.delete(e);try{localStorage.removeItem(`codeman-xs-${e}`)}catch{}this._flushedOffsets?.delete(e),this._flushedTexts?.delete(e),this._inputQueue.delete(e),this.ralphStates.delete(e),this.ralphClosedSessions.delete(e),this.projectInsights.delete(e),this.pendingHooks.delete(e),this.tabAlerts.delete(e),this.attachmentHistoryCounts.delete(e),this.attachmentHistoryDrawerOpen&&this.activeSessionId===e&&this.closeAttachmentHistory?.(),this.terminalLoadStates.delete(e),this.clearCountdownTimers(e),this.closeSessionLogViewerWindows(e),this.closeSessionImagePopups(e),this.closeSessionAttachmentCards(e),this.closeSessionSubagentWindows(e,!0);const s=this.idleTimers.get(e);s&&(clearTimeout(s),this.idleTimers.delete(e)),delete this.respawnStatus[e],delete this.respawnTimers[e],delete this.respawnCountdownTimers[e],delete this.respawnActionLogs[e]}async closeSession(e,t=!0){try{if(await this._apiDelete(`/api/sessions/${e}?killMux=${t}`),this._cleanupSessionData(e),this.activeSessionId===e){this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}if(this.sessionOrder.length>0&&this.sessions.size>0){const s=this.sessionOrder[0];this.selectSession(s)}else this.terminal.clear(),this.showWelcome(),this.renderRalphStatePanel()}this.renderSessionTabs(),t?this.showToast("Session closed and tmux killed","success"):this.showToast("Tab hidden, tmux still running","info")}catch{this.showToast("Failed to close session","error")}}requestCloseSession(e){const t=this.sessions.get(e);if(!t)return;this.pendingCloseSessionId=e;const s=this.getSessionName(t),i=document.getElementById("closeConfirmSessionName");i.textContent=s;const n=document.getElementById("closeConfirmKillTitle");n&&(n.textContent=t.mode==="opencode"?"Kill Tmux & OpenCode":t.mode==="codex"?"Kill Tmux & Codex":"Kill Tmux & Claude Code"),document.getElementById("closeConfirmModal").classList.add("active")}cancelCloseSession(){this.pendingCloseSessionId=null,document.getElementById("closeConfirmModal").classList.remove("active")}async confirmCloseSession(e=!0){const t=this.pendingCloseSessionId;this.cancelCloseSession(),t&&await this.closeSession(t,e)}nextSession(){if(this.sessionOrder.length<=1)return;const t=(this.sessionOrder.indexOf(this.activeSessionId)+1)%this.sessionOrder.length;this.selectSession(this.sessionOrder[t])}prevSession(){if(this.sessionOrder.length<=1)return;const t=(this.sessionOrder.indexOf(this.activeSessionId)-1+this.sessionOrder.length)%this.sessionOrder.length;this.selectSession(this.sessionOrder[t])}goHome(){this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.terminal.clear(),this.showWelcome(),this.renderSessionTabs(),this.renderRalphStatePanel()}ralphWizardStep=1;ralphWizardConfig={taskDescription:"",completionPhrase:"COMPLETE",maxIterations:10,caseName:"testcase",enableRespawn:!1,generatedPlan:null,planGenerated:!1,skipPlanGeneration:!1,planDetailLevel:"detailed",existingPlan:null,useExistingPlan:!1};planLoadingTimer=null;planLoadingStartTime=null;async killActiveSession(){if(!this.activeSessionId){this.showToast("No active session","warning");return}await this.closeSession(this.activeSessionId)}async killAllSessions(){if(this.sessions.size!==0&&confirm(`Kill all ${this.sessions.size} session(s)?`))try{await this._apiDelete("/api/sessions"),this.sessions.clear(),this.terminalBuffers.clear(),this.terminalBufferCache.clear(),this.terminalLoadStates.clear(),this._xtermSnapshots?.clear();try{for(const e of Object.keys(localStorage))e.startsWith("codeman-xs-")&&localStorage.removeItem(e)}catch{}this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.respawnStatus={},this.respawnCountdownTimers={},this.respawnActionLogs={},this.stopCountdownInterval(),this.hideRespawnBanner(),this.renderSessionTabs(),this.terminal.clear(),this.showWelcome(),this.showToast("All sessions killed","success")}catch{this.showToast("Failed to kill sessions","error")}}showTimer(){document.getElementById("timerBanner").style.display="flex",this.updateTimer(),this.timerInterval=setInterval(()=>this.updateTimer(),1e3)}hideTimer(){document.getElementById("timerBanner").style.display="none",this.timerInterval&&(clearInterval(this.timerInterval),this.timerInterval=null)}updateTimer(){if(!this.currentRun||this.currentRun.status!=="running")return;const e=Date.now(),t=Math.max(0,this.currentRun.endAt-e),s=this.currentRun.endAt-this.currentRun.startedAt,i=e-this.currentRun.startedAt,n=Math.min(100,i/s*100);document.getElementById("timerValue").textContent=this.formatTime(t),document.getElementById("timerProgress").style.width=`${n}%`,document.getElementById("timerMeta").textContent=`${this.currentRun.completedTasks} tasks | $${this.currentRun.totalCost.toFixed(2)}`}async stopCurrentRun(){if(this.currentRun)try{await fetch(`/api/scheduled/${this.currentRun.id}`,{method:"DELETE"})}catch{this.showToast("Failed to stop run","error")}}formatTime(e){const t=Math.floor(e/1e3),s=Math.floor(t/3600),i=Math.floor(t%3600/60),n=t%60;return`${s.toString().padStart(2,"0")}:${i.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}`}updateCost(){this.updateTokens()}updateTokens(){this._clearTimer("_updateTokensTimeout"),this._updateTokensTimeout=setTimeout(()=>{this._updateTokensTimeout=null,this._updateTokensImmediate()},200)}_updateTokensImmediate(){let e=0,t=0;this.globalStats?(e=this.globalStats.totalInputTokens||0,t=this.globalStats.totalOutputTokens||0):this.sessions.forEach(a=>{a.tokens&&(e+=a.tokens.input||0,t+=a.tokens.output||0)});const s=e+t;this.totalTokens=s;const i=this.formatTokens(s),n=this.estimateCost(e,t),o=this.$("headerTokens");if(o){const r=this.loadAppSettingsFromStorage().showCost??!1;o.textContent=s>0?r?`${i} tokens \xB7 $${n.toFixed(2)}`:`${i} tokens`:"0 tokens",o.title=this.globalStats?`Lifetime: ${this.globalStats.totalSessionsCreated} sessions created${r?`
36
37
  Estimated cost based on Claude Opus pricing`:""}`:`Token usage across active sessions${r?`
37
38
  Estimated cost based on Claude Opus pricing`:""}`}}}try{for(let h=0;h<localStorage.length;h++){const e=localStorage.key(h);if(e&&(e.startsWith("claudeman-")||e.startsWith("claudeman_"))){const t=e.replace(/^claudeman[-_]/,s=>"codeman"+s.charAt(s.length-1));localStorage.getItem(t)===null&&localStorage.setItem(t,localStorage.getItem(e))}}}catch{}let app;document.addEventListener("DOMContentLoaded",()=>{app=new CodemanApp,window.app=app}),window.MobileDetection=MobileDetection;
@@ -16,7 +16,7 @@
16
16
  <link rel="manifest" href="manifest.json">
17
17
  <title>Codeman</title>
18
18
  <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%2360a5fa'/%3E%3Cstop offset='100%25' stop-color='%233b82f6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='6' fill='%230a0a0a'/%3E%3Cpath d='M18 4L8 18h6l-2 10 10-14h-6z' fill='url(%23g)'/%3E%3C/svg%3E">
19
- <link rel="stylesheet" href="styles.e0388c26.css">
19
+ <link rel="stylesheet" href="styles.4d2984bd.css">
20
20
  <link rel="stylesheet" href="mobile.38346326.css" media="(max-width: 1023px)">
21
21
  <!-- xterm.css loaded async — terminal won't display until xterm.js runs anyway -->
22
22
  <link rel="preload" href="vendor/xterm.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
@@ -25,7 +25,7 @@
25
25
  instead of waiting until <script> tags at bottom-of-body are reached. -->
26
26
  <link rel="preload" href="vendor/xterm.min.js" as="script">
27
27
  <link rel="preload" href="constants.1c779517.js" as="script">
28
- <link rel="preload" href="app.a24e7b65.js" as="script">
28
+ <link rel="preload" href="app.160e8c7d.js" as="script">
29
29
  <!-- Self-hosted xterm.js — eliminates CDN DNS/TLS latency (~100ms).
30
30
  'defer' preserves execution order (xterm loads before fit addon). -->
31
31
  <script defer src="vendor/xterm.min.js"></script>
@@ -1962,11 +1962,11 @@
1962
1962
  <script defer src="mobile-handlers.b5a667ef.js"></script>
1963
1963
  <script defer src="voice-input.085e9e73.js"></script>
1964
1964
  <script defer src="notification-manager.9c984ac2.js"></script>
1965
- <script defer src="keyboard-accessory.3370a23c.js"></script>
1965
+ <script defer src="keyboard-accessory.3f91acef.js"></script>
1966
1966
  <script defer src="input-cjk.4e950e0a.js"></script>
1967
1967
  <!-- Hardened markdown HTML sanitizer (wires DOMPurify). Must precede app.js. -->
1968
1968
  <script defer src="sanitize-html.bc7078d6.js"></script>
1969
- <script defer src="app.a24e7b65.js"></script>
1969
+ <script defer src="app.160e8c7d.js"></script>
1970
1970
  <script defer src="terminal-ui.70596bbd.js"></script>
1971
1971
  <script defer src="respawn-ui.2d249da9.js"></script>
1972
1972
  <script defer src="ralph-panel.6de2d0f8.js"></script>
@@ -1977,7 +1977,7 @@
1977
1977
  <script defer src="session-ui.1463b824.js"></script>
1978
1978
  <script defer src="ralph-wizard.13a1831e.js"></script>
1979
1979
  <script defer src="api-client.c9b1cddc.js"></script>
1980
- <script defer src="subagent-windows.07e139f2.js"></script>
1980
+ <script defer src="subagent-windows.e3207234.js"></script>
1981
1981
  <script defer src="ultracode-windows.js"></script>
1982
1982
  <script defer src="image-input.0ea86695.js"></script>
1983
1983
  </body>
Binary file
Binary file
@@ -100,6 +100,7 @@ const KeyboardAccessoryBar = {
100
100
  <button class="accessory-btn" data-action="esc" title="Escape">Esc</button>
101
101
  <button class="accessory-btn" data-action="init" title="/init">/init</button>
102
102
  <button class="accessory-btn" data-action="clear" title="/clear">/clear</button>
103
+ <button class="accessory-btn" data-action="compact" title="/compact">/compact</button>
103
104
  <button class="accessory-btn accessory-btn-dismiss" data-action="dismiss" title="Dismiss keyboard">
104
105
  <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
105
106
  <path d="M19 9l-7 7-7-7"/>