aicodeman 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/README.md +10 -8
  2. package/README.zh-CN.md +289 -92
  3. package/dist/docker-export.d.ts +13 -0
  4. package/dist/docker-export.d.ts.map +1 -1
  5. package/dist/docker-export.js +55 -8
  6. package/dist/docker-export.js.map +1 -1
  7. package/dist/docker-hosts.d.ts +36 -5
  8. package/dist/docker-hosts.d.ts.map +1 -1
  9. package/dist/docker-hosts.js +90 -16
  10. package/dist/docker-hosts.js.map +1 -1
  11. package/dist/remote-hosts.d.ts +61 -1
  12. package/dist/remote-hosts.d.ts.map +1 -1
  13. package/dist/remote-hosts.js +142 -0
  14. package/dist/remote-hosts.js.map +1 -1
  15. package/dist/remote-reconnect.d.ts +129 -0
  16. package/dist/remote-reconnect.d.ts.map +1 -0
  17. package/dist/remote-reconnect.js +119 -0
  18. package/dist/remote-reconnect.js.map +1 -0
  19. package/dist/services/unified-session-service.d.ts +13 -1
  20. package/dist/services/unified-session-service.d.ts.map +1 -1
  21. package/dist/services/unified-session-service.js +88 -3
  22. package/dist/services/unified-session-service.js.map +1 -1
  23. package/dist/session-order.d.ts +40 -0
  24. package/dist/session-order.d.ts.map +1 -0
  25. package/dist/session-order.js +67 -0
  26. package/dist/session-order.js.map +1 -0
  27. package/dist/session.d.ts +39 -0
  28. package/dist/session.d.ts.map +1 -1
  29. package/dist/session.js +87 -19
  30. package/dist/session.js.map +1 -1
  31. package/dist/state-store.d.ts +12 -0
  32. package/dist/state-store.d.ts.map +1 -1
  33. package/dist/state-store.js +33 -0
  34. package/dist/state-store.js.map +1 -1
  35. package/dist/tmux-manager.d.ts +66 -0
  36. package/dist/tmux-manager.d.ts.map +1 -1
  37. package/dist/tmux-manager.js +260 -8
  38. package/dist/tmux-manager.js.map +1 -1
  39. package/dist/types/app-state.d.ts +2 -0
  40. package/dist/types/app-state.d.ts.map +1 -1
  41. package/dist/types/app-state.js.map +1 -1
  42. package/dist/types/session.d.ts +43 -1
  43. package/dist/types/session.d.ts.map +1 -1
  44. package/dist/types/session.js +1 -1
  45. package/dist/types/session.js.map +1 -1
  46. package/dist/web/public/admin-ui.js.gz +0 -0
  47. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  48. package/dist/web/public/app.b78b0465.js +38 -0
  49. package/dist/web/public/app.b78b0465.js.br +0 -0
  50. package/dist/web/public/app.b78b0465.js.gz +0 -0
  51. package/dist/web/public/{constants.5ce97313.js → constants.21a0a83e.js} +10 -0
  52. package/dist/web/public/constants.21a0a83e.js.br +0 -0
  53. package/dist/web/public/{constants.5ce97313.js.gz → constants.21a0a83e.js.gz} +0 -0
  54. package/dist/web/public/cron-ui.js.gz +0 -0
  55. package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
  56. package/dist/web/public/index.html +32 -13
  57. package/dist/web/public/index.html.br +0 -0
  58. package/dist/web/public/index.html.gz +0 -0
  59. package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
  60. package/dist/web/public/keyboard-accessory.3f91acef.js.gz +0 -0
  61. package/dist/web/public/mobile-handlers.b5a667ef.js.gz +0 -0
  62. package/dist/web/public/mobile.597136a6.css +1 -0
  63. package/dist/web/public/mobile.597136a6.css.br +0 -0
  64. package/dist/web/public/mobile.597136a6.css.gz +0 -0
  65. package/dist/web/public/notification-manager.ca9dc40d.js.gz +0 -0
  66. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  67. package/dist/web/public/{panels-ui.3c9b0465.js → panels-ui.58582d4c.js} +2 -2
  68. package/dist/web/public/panels-ui.58582d4c.js.br +0 -0
  69. package/dist/web/public/panels-ui.58582d4c.js.gz +0 -0
  70. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  71. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  72. package/dist/web/public/respawn-ui.2d249da9.js.gz +0 -0
  73. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  74. package/dist/web/public/session-ui.0cf86dd4.js +68 -0
  75. package/dist/web/public/session-ui.0cf86dd4.js.br +0 -0
  76. package/dist/web/public/session-ui.0cf86dd4.js.gz +0 -0
  77. package/dist/web/public/{settings-ui.29214cf1.js → settings-ui.bc5f4792.js} +2 -2
  78. package/dist/web/public/settings-ui.bc5f4792.js.br +0 -0
  79. package/dist/web/public/settings-ui.bc5f4792.js.gz +0 -0
  80. package/dist/web/public/styles.7295e6f4.css +1 -0
  81. package/dist/web/public/styles.7295e6f4.css.br +0 -0
  82. package/dist/web/public/styles.7295e6f4.css.gz +0 -0
  83. package/dist/web/public/subagent-windows.69b60c35.js.gz +0 -0
  84. package/dist/web/public/sw.js.gz +0 -0
  85. package/dist/web/public/terminal-ui.9ea677e9.js +3 -0
  86. package/dist/web/public/terminal-ui.9ea677e9.js.br +0 -0
  87. package/dist/web/public/terminal-ui.9ea677e9.js.gz +0 -0
  88. package/dist/web/public/ultracode-panel.js.gz +0 -0
  89. package/dist/web/public/ultracode-windows.js.gz +0 -0
  90. package/dist/web/public/upload.html.gz +0 -0
  91. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  92. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  93. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  94. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  95. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  96. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  97. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  98. package/dist/web/public/vendor/xterm.css.gz +0 -0
  99. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  100. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  101. package/dist/web/routes/case-routes.d.ts +2 -2
  102. package/dist/web/routes/case-routes.d.ts.map +1 -1
  103. package/dist/web/routes/case-routes.js +75 -21
  104. package/dist/web/routes/case-routes.js.map +1 -1
  105. package/dist/web/routes/hook-event-routes.d.ts.map +1 -1
  106. package/dist/web/routes/hook-event-routes.js +9 -0
  107. package/dist/web/routes/hook-event-routes.js.map +1 -1
  108. package/dist/web/routes/session-routes.d.ts.map +1 -1
  109. package/dist/web/routes/session-routes.js +172 -7
  110. package/dist/web/routes/session-routes.js.map +1 -1
  111. package/dist/web/schemas.d.ts +14 -0
  112. package/dist/web/schemas.d.ts.map +1 -1
  113. package/dist/web/schemas.js +42 -0
  114. package/dist/web/schemas.js.map +1 -1
  115. package/dist/web/server.d.ts +9 -0
  116. package/dist/web/server.d.ts.map +1 -1
  117. package/dist/web/server.js +57 -1
  118. package/dist/web/server.js.map +1 -1
  119. package/dist/web/sse-events.d.ts +18 -0
  120. package/dist/web/sse-events.d.ts.map +1 -1
  121. package/dist/web/sse-events.js +21 -0
  122. package/dist/web/sse-events.js.map +1 -1
  123. package/package.json +1 -1
  124. package/dist/web/public/app.6c4b9893.js +0 -38
  125. package/dist/web/public/app.6c4b9893.js.br +0 -0
  126. package/dist/web/public/app.6c4b9893.js.gz +0 -0
  127. package/dist/web/public/constants.5ce97313.js.br +0 -0
  128. package/dist/web/public/mobile.0644e3ac.css +0 -1
  129. package/dist/web/public/mobile.0644e3ac.css.br +0 -0
  130. package/dist/web/public/mobile.0644e3ac.css.gz +0 -0
  131. package/dist/web/public/panels-ui.3c9b0465.js.br +0 -0
  132. package/dist/web/public/panels-ui.3c9b0465.js.gz +0 -0
  133. package/dist/web/public/session-ui.4851c61a.js +0 -58
  134. package/dist/web/public/session-ui.4851c61a.js.br +0 -0
  135. package/dist/web/public/session-ui.4851c61a.js.gz +0 -0
  136. package/dist/web/public/settings-ui.29214cf1.js.br +0 -0
  137. package/dist/web/public/settings-ui.29214cf1.js.gz +0 -0
  138. package/dist/web/public/styles.9e6cdb9d.css +0 -1
  139. package/dist/web/public/styles.9e6cdb9d.css.br +0 -0
  140. package/dist/web/public/styles.9e6cdb9d.css.gz +0 -0
  141. package/dist/web/public/terminal-ui.efda6083.js +0 -3
  142. package/dist/web/public/terminal-ui.efda6083.js.br +0 -0
  143. package/dist/web/public/terminal-ui.efda6083.js.gz +0 -0
@@ -1,4 +1,4 @@
1
- "use strict";const AWAY_DIGEST_LAST_VIEWED_KEY="codeman-away-digest-last-viewed",AWAY_DIGEST_SECTIONS=[["needsAttention","Needs Attention"],["completed","Completed"],["stillRunning","Still Running"],["idle","Idle"],["informational","Informational"]];Object.assign(CodemanApp.prototype,{_addActivityEntry(e,t,s=50){const n=this.subagentActivity.get(e)||[];n.push(t),n.length>s&&n.shift(),this.subagentActivity.set(e,n)},_onTaskCreated(e){this.renderSessionTabs(),e.sessionId===this.activeSessionId&&this.renderTaskPanel()},_onTaskCompleted(e){this.renderSessionTabs(),e.sessionId===this.activeSessionId&&this.renderTaskPanel()},_onTaskFailed(e){this.renderSessionTabs(),e.sessionId===this.activeSessionId&&this.renderTaskPanel()},_onTaskUpdated(e){e.sessionId===this.activeSessionId&&this.renderTaskPanel()},_onMuxCreated(e){this.muxSessions.push(e),this.renderMuxSessions()},_onMuxKilled(e){this.muxSessions=this.muxSessions.filter(t=>t.sessionId!==e.sessionId),this.renderMuxSessions()},_onMuxDied(e){this.muxSessions=this.muxSessions.filter(t=>t.sessionId!==e.sessionId),this.renderMuxSessions(),this.showToast("Mux session died: "+this.getShortId(e.sessionId),"warning")},_onMuxStatsUpdated(e){this.muxSessions=e,document.getElementById("monitorPanel").classList.contains("open")&&this.renderMuxSessions()},_onBashToolStart(e){this.handleBashToolStart(e.sessionId,e.tool)},_onBashToolEnd(e){this.handleBashToolEnd(e.sessionId,e.tool)},_onBashToolsUpdate(e){this.handleBashToolsUpdate(e.sessionId,e.tools)},_onSubagentDiscovered(e){this.subagents.set(e.agentId,e),this.subagentActivity.set(e.agentId,[]),this.subagentToolResults.delete(e.agentId),this.subagentWindows.has(e.agentId)&&this.forceCloseSubagentWindow(e.agentId),this.renderSubagentPanel(),this.findParentSessionForSubagent(e.agentId),requestAnimationFrame(()=>{this.updateConnectionLines()});const t=this.subagentParentMap.get(e.agentId);this._notifySession(t||e.sessionId,"info","subagent-spawn","Subagent Spawned",e.description||"New background agent started")},_onSubagentUpdated(e){const t=this.subagents.get(e.agentId);t?(Object.assign(t,e),this.subagents.set(e.agentId,t)):this.subagents.set(e.agentId,e),this.renderSubagentPanel(),this.subagentWindows.has(e.agentId)&&(this.renderSubagentWindowContent(e.agentId),this.updateSubagentWindowHeader(e.agentId))},_onSubagentToolCall(e){this._addActivityEntry(e.agentId,{type:"tool",...e}),this.activeSubagentId===e.agentId&&this.renderSubagentDetail(),this.renderSubagentPanel(),this.subagentWindows.has(e.agentId)&&this.scheduleSubagentWindowRender(e.agentId)},_onSubagentProgress(e){this._addActivityEntry(e.agentId,{type:"progress",...e}),this.activeSubagentId===e.agentId&&this.renderSubagentDetail(),this.subagentWindows.has(e.agentId)&&this.scheduleSubagentWindowRender(e.agentId)},_onSubagentMessage(e){this._addActivityEntry(e.agentId,{type:"message",...e}),this.activeSubagentId===e.agentId&&this.renderSubagentDetail(),this.subagentWindows.has(e.agentId)&&this.scheduleSubagentWindowRender(e.agentId)},_onSubagentToolResult(e){this.subagentToolResults.has(e.agentId)||this.subagentToolResults.set(e.agentId,new Map);const t=this.subagentToolResults.get(e.agentId);if(t.set(e.toolUseId,e),t.size>50){const s=t.keys().next().value;t.delete(s)}this._addActivityEntry(e.agentId,{type:"tool_result",...e}),this.activeSubagentId===e.agentId&&this.renderSubagentDetail(),this.subagentWindows.has(e.agentId)&&this.scheduleSubagentWindowRender(e.agentId)},async _onSubagentCompleted(e){const t=this.subagents.get(e.agentId);if(t&&(t.status="completed",this.subagents.set(e.agentId,t)),this.renderSubagentPanel(),this.updateSubagentWindows(),this.subagentWindows.has(e.agentId)){const n=this.subagentWindows.get(e.agentId);n&&!n.minimized&&(await this.closeSubagentWindow(e.agentId),this.saveSubagentWindowStates())}const s=this.subagentParentMap.get(e.agentId);this._notifySession(s||t?.sessionId||e.sessionId,"info","subagent-complete","Subagent Completed",t?.description||e.description||"Background agent finished"),setTimeout(()=>{this.subagents.get(e.agentId)?.status==="completed"&&(this.subagentActivity.delete(e.agentId),this.subagentToolResults.delete(e.agentId))},300*1e3),setTimeout(()=>{this.subagents.get(e.agentId)?.status==="completed"&&!this.subagentWindows.has(e.agentId)&&(this.subagents.delete(e.agentId),this.subagentParentMap.delete(e.agentId))},1800*1e3)},_onImageDetected(e){console.log("[Image Detected]",e),this.openImagePopup(e)},shouldOpenCommandPaletteFromShortcut(e){if(!e||!e.ctrlKey&&!e.metaKey&&!e.altKey)return!1;const s=typeof this.getShortcutRegistry=="function"&&typeof this.matchesShortcutEvent=="function"?this.getShortcutRegistry().find(o=>o.id==="command-palette"):null;if(s){if(s.disabled||!this.matchesShortcutEvent(e,s))return!1}else if((e.key||"").toLowerCase()!=="k"&&e.code!=="KeyK"||e.shiftKey)return!1;const n=e.target;if(!n)return!0;const a=(n.tagName||"").toUpperCase(),i=typeof n.className=="string"?n.className:"";return n.classList?.contains?.("xterm-helper-textarea")||i.includes("xterm-helper-textarea")?!0:!(a==="INPUT"||a==="TEXTAREA"||a==="SELECT"||n.isContentEditable||typeof n.closest=="function"&&n.closest('[contenteditable="true"]'))},openCommandPalette(){const e=document.getElementById("commandPaletteModal"),t=document.getElementById("commandPaletteSearch");!e||!t||(this.commandPaletteActiveIndex=0,t.value="",e.classList.add("active"),this._wireCommandPalette(),this.renderCommandPalette(),t.focus(),t.select?.())},closeCommandPalette(){const e=document.getElementById("commandPaletteModal");e&&e.classList.remove("active")},_wireCommandPalette(){if(this._commandPaletteWired)return;this._commandPaletteWired=!0;const e=document.getElementById("commandPaletteModal"),t=document.getElementById("commandPaletteSearch"),s=document.getElementById("commandPaletteList");t?.addEventListener("input",()=>{this.commandPaletteActiveIndex=0,this.renderCommandPalette()}),t?.addEventListener("keydown",async n=>{if(n.key==="ArrowDown"){n.preventDefault(),this.moveCommandPaletteSelection(1);return}if(n.key==="ArrowUp"){n.preventDefault(),this.moveCommandPaletteSelection(-1);return}if(n.key==="Enter"){n.preventDefault(),n.stopPropagation(),await this.activateCommandPaletteItem();return}n.key==="Escape"&&(n.preventDefault(),this.closeCommandPalette())}),e?.addEventListener("keydown",n=>{n.key==="Escape"&&(n.preventDefault(),this.closeCommandPalette())}),s?.addEventListener?.("click",n=>{const a=n.target?.closest?.("[data-command-index]");a&&(this.commandPaletteActiveIndex=Number(a.dataset.commandIndex)||0,this.activateCommandPaletteItem())})},buildCommandPaletteItems(e=""){const t=e.trim().toLowerCase(),s=[...Array.isArray(this.sessionOrder)?this.sessionOrder:[],...Array.from(this.sessions?.keys?.()||[]).filter(i=>!this.sessionOrder?.includes?.(i))],n=new Set,a=[];for(const i of s){if(n.has(i))continue;n.add(i);const r=this.sessions?.get?.(i);if(!r)continue;const o=this.getSessionName?.(r)||r.name||r.title||i.slice(0,8),l=[r.workingDir,r.mode,r.status].filter(Boolean),d=[o,r.workingDir,r.mode,r.status,i].filter(Boolean).join(" ").toLowerCase();t&&!d.includes(t)||a.push({id:`session:${i}`,type:"session",sessionId:i,title:o,subtitle:l.join(" \xB7 ")})}return a.push(this._buildCommandPaletteNewSessionItem(e)),a.push({id:"browse-sessions",type:"browse-sessions",title:"Browse all sessions\u2026",subtitle:"Open Session Manager"}),a},_buildCommandPaletteNewSessionItem(e=""){const t=this.runMode||this._runMode||"claude",s={claude:"Claude",opencode:"OpenCode",codex:"Codex",gemini:"Gemini"},n=this._findCommandPaletteCaseMatch(e)||document.getElementById("quickStartCase")?.value||"testcase";return{id:"new-session",type:"new-session",caseName:n,title:"New session",subtitle:`Run ${s[t]||t} in ${n}`}},_findCommandPaletteCaseMatch(e=""){const t=e.trim().toLowerCase();if(!t||!Array.isArray(this.cases))return null;const s=i=>{const r=String(i?.name||"").trim();if(!r)return 0;const o=[r,i?.path,i?.casePath,i?.workingDir,i?.remote?.path,i?.remote?.hostId].filter(Boolean).join(" ").toLowerCase(),l=r.toLowerCase();return l===t?100:l.startsWith(t)?90:l.includes(t)?80:o.includes(t)?60:0};let n=null,a=0;for(const i of this.cases){const r=s(i);r>a&&(n=i,a=r)}return n?.name||null},renderCommandPalette(){const e=document.getElementById("commandPaletteSearch"),t=document.getElementById("commandPaletteList");if(!t)return;const s=e?.value||"",n=this.buildCommandPaletteItems(s);this.commandPaletteItems=n,this.commandPaletteActiveIndex=Math.max(0,Math.min(this.commandPaletteActiveIndex||0,n.length-1)),t.innerHTML=n.map((a,i)=>{const r=i===this.commandPaletteActiveIndex?" active":"",o=a.type==="new-session"?"+":a.type==="browse-sessions"?"\u2261":"\u203A",l=a.type==="browse-sessions"?" command-palette-item--browse":"";return`
1
+ "use strict";const AWAY_DIGEST_LAST_VIEWED_KEY="codeman-away-digest-last-viewed",AWAY_DIGEST_SECTIONS=[["needsAttention","Needs Attention"],["completed","Completed"],["stillRunning","Still Running"],["idle","Idle"],["informational","Informational"]];Object.assign(CodemanApp.prototype,{_addActivityEntry(e,t,s=50){const n=this.subagentActivity.get(e)||[];n.push(t),n.length>s&&n.shift(),this.subagentActivity.set(e,n)},_onTaskCreated(e){this.renderSessionTabs(),e.sessionId===this.activeSessionId&&this.renderTaskPanel()},_onTaskCompleted(e){this.renderSessionTabs(),e.sessionId===this.activeSessionId&&this.renderTaskPanel()},_onTaskFailed(e){this.renderSessionTabs(),e.sessionId===this.activeSessionId&&this.renderTaskPanel()},_onTaskUpdated(e){e.sessionId===this.activeSessionId&&this.renderTaskPanel()},_onMuxCreated(e){this.muxSessions.push(e),this.renderMuxSessions()},_onMuxKilled(e){this.muxSessions=this.muxSessions.filter(t=>t.sessionId!==e.sessionId),this.renderMuxSessions()},_onMuxDied(e){this.muxSessions=this.muxSessions.filter(t=>t.sessionId!==e.sessionId),this.renderMuxSessions(),this.showToast("Mux session died: "+this.getShortId(e.sessionId),"warning")},_onMuxStatsUpdated(e){this.muxSessions=e,document.getElementById("monitorPanel").classList.contains("open")&&this.renderMuxSessions()},_onRemoteSessionReconnected(e){const t=this.getShortId(e.sessionId);this.showToast(`Remote session ${t} reconnected`,"success")},_onRemoteReconnectExhausted(e){const t=e.sessionId,s=this.getShortId(t);this.showToast(`Remote session ${s} dropped \u2014 auto-reconnect gave up`,"error",{duration:15e3,action:{label:"Reconnect",onClick:()=>{this.sessions&&this.sessions.has(t)?this.selectSession(t,{forceReload:!0}):this.showToast("Session no longer available","warning")}}})},_onBashToolStart(e){this.handleBashToolStart(e.sessionId,e.tool)},_onBashToolEnd(e){this.handleBashToolEnd(e.sessionId,e.tool)},_onBashToolsUpdate(e){this.handleBashToolsUpdate(e.sessionId,e.tools)},_onSubagentDiscovered(e){this.subagents.set(e.agentId,e),this.subagentActivity.set(e.agentId,[]),this.subagentToolResults.delete(e.agentId),this.subagentWindows.has(e.agentId)&&this.forceCloseSubagentWindow(e.agentId),this.renderSubagentPanel(),this.findParentSessionForSubagent(e.agentId),requestAnimationFrame(()=>{this.updateConnectionLines()});const t=this.subagentParentMap.get(e.agentId);this._notifySession(t||e.sessionId,"info","subagent-spawn","Subagent Spawned",e.description||"New background agent started")},_onSubagentUpdated(e){const t=this.subagents.get(e.agentId);t?(Object.assign(t,e),this.subagents.set(e.agentId,t)):this.subagents.set(e.agentId,e),this.renderSubagentPanel(),this.subagentWindows.has(e.agentId)&&(this.renderSubagentWindowContent(e.agentId),this.updateSubagentWindowHeader(e.agentId))},_onSubagentToolCall(e){this._addActivityEntry(e.agentId,{type:"tool",...e}),this.activeSubagentId===e.agentId&&this.renderSubagentDetail(),this.renderSubagentPanel(),this.subagentWindows.has(e.agentId)&&this.scheduleSubagentWindowRender(e.agentId)},_onSubagentProgress(e){this._addActivityEntry(e.agentId,{type:"progress",...e}),this.activeSubagentId===e.agentId&&this.renderSubagentDetail(),this.subagentWindows.has(e.agentId)&&this.scheduleSubagentWindowRender(e.agentId)},_onSubagentMessage(e){this._addActivityEntry(e.agentId,{type:"message",...e}),this.activeSubagentId===e.agentId&&this.renderSubagentDetail(),this.subagentWindows.has(e.agentId)&&this.scheduleSubagentWindowRender(e.agentId)},_onSubagentToolResult(e){this.subagentToolResults.has(e.agentId)||this.subagentToolResults.set(e.agentId,new Map);const t=this.subagentToolResults.get(e.agentId);if(t.set(e.toolUseId,e),t.size>50){const s=t.keys().next().value;t.delete(s)}this._addActivityEntry(e.agentId,{type:"tool_result",...e}),this.activeSubagentId===e.agentId&&this.renderSubagentDetail(),this.subagentWindows.has(e.agentId)&&this.scheduleSubagentWindowRender(e.agentId)},async _onSubagentCompleted(e){const t=this.subagents.get(e.agentId);if(t&&(t.status="completed",this.subagents.set(e.agentId,t)),this.renderSubagentPanel(),this.updateSubagentWindows(),this.subagentWindows.has(e.agentId)){const n=this.subagentWindows.get(e.agentId);n&&!n.minimized&&(await this.closeSubagentWindow(e.agentId),this.saveSubagentWindowStates())}const s=this.subagentParentMap.get(e.agentId);this._notifySession(s||t?.sessionId||e.sessionId,"info","subagent-complete","Subagent Completed",t?.description||e.description||"Background agent finished"),setTimeout(()=>{this.subagents.get(e.agentId)?.status==="completed"&&(this.subagentActivity.delete(e.agentId),this.subagentToolResults.delete(e.agentId))},300*1e3),setTimeout(()=>{this.subagents.get(e.agentId)?.status==="completed"&&!this.subagentWindows.has(e.agentId)&&(this.subagents.delete(e.agentId),this.subagentParentMap.delete(e.agentId))},1800*1e3)},_onImageDetected(e){console.log("[Image Detected]",e),this.openImagePopup(e)},shouldOpenCommandPaletteFromShortcut(e){if(!e||!e.ctrlKey&&!e.metaKey&&!e.altKey)return!1;const s=typeof this.getShortcutRegistry=="function"&&typeof this.matchesShortcutEvent=="function"?this.getShortcutRegistry().find(o=>o.id==="command-palette"):null;if(s){if(s.disabled||!this.matchesShortcutEvent(e,s))return!1}else if((e.key||"").toLowerCase()!=="k"&&e.code!=="KeyK"||e.shiftKey)return!1;const n=e.target;if(!n)return!0;const a=(n.tagName||"").toUpperCase(),i=typeof n.className=="string"?n.className:"";return n.classList?.contains?.("xterm-helper-textarea")||i.includes("xterm-helper-textarea")?!0:!(a==="INPUT"||a==="TEXTAREA"||a==="SELECT"||n.isContentEditable||typeof n.closest=="function"&&n.closest('[contenteditable="true"]'))},openCommandPalette(){const e=document.getElementById("commandPaletteModal"),t=document.getElementById("commandPaletteSearch");!e||!t||(this.commandPaletteActiveIndex=0,t.value="",e.classList.add("active"),this._wireCommandPalette(),this.renderCommandPalette(),t.focus(),t.select?.())},closeCommandPalette(){const e=document.getElementById("commandPaletteModal");e&&e.classList.remove("active")},_wireCommandPalette(){if(this._commandPaletteWired)return;this._commandPaletteWired=!0;const e=document.getElementById("commandPaletteModal"),t=document.getElementById("commandPaletteSearch"),s=document.getElementById("commandPaletteList");t?.addEventListener("input",()=>{this.commandPaletteActiveIndex=0,this.renderCommandPalette()}),t?.addEventListener("keydown",async n=>{if(n.key==="ArrowDown"){n.preventDefault(),this.moveCommandPaletteSelection(1);return}if(n.key==="ArrowUp"){n.preventDefault(),this.moveCommandPaletteSelection(-1);return}if(n.key==="Enter"){n.preventDefault(),n.stopPropagation(),await this.activateCommandPaletteItem();return}n.key==="Escape"&&(n.preventDefault(),this.closeCommandPalette())}),e?.addEventListener("keydown",n=>{n.key==="Escape"&&(n.preventDefault(),this.closeCommandPalette())}),s?.addEventListener?.("click",n=>{const a=n.target?.closest?.("[data-command-index]");a&&(this.commandPaletteActiveIndex=Number(a.dataset.commandIndex)||0,this.activateCommandPaletteItem())})},buildCommandPaletteItems(e=""){const t=e.trim().toLowerCase(),s=[...Array.isArray(this.sessionOrder)?this.sessionOrder:[],...Array.from(this.sessions?.keys?.()||[]).filter(i=>!this.sessionOrder?.includes?.(i))],n=new Set,a=[];for(const i of s){if(n.has(i))continue;n.add(i);const r=this.sessions?.get?.(i);if(!r)continue;const o=this.getSessionName?.(r)||r.name||r.title||i.slice(0,8),l=[r.workingDir,r.mode,r.status].filter(Boolean),d=[o,r.workingDir,r.mode,r.status,i].filter(Boolean).join(" ").toLowerCase();t&&!d.includes(t)||a.push({id:`session:${i}`,type:"session",sessionId:i,title:o,subtitle:l.join(" \xB7 ")})}return a.push(this._buildCommandPaletteNewSessionItem(e)),a.push({id:"browse-sessions",type:"browse-sessions",title:"Browse all sessions\u2026",subtitle:"Open Session Manager"}),a},_buildCommandPaletteNewSessionItem(e=""){const t=this.runMode||this._runMode||"claude",s={claude:"Claude",opencode:"OpenCode",codex:"Codex",gemini:"Gemini"},n=this._findCommandPaletteCaseMatch(e)||document.getElementById("quickStartCase")?.value||"testcase";return{id:"new-session",type:"new-session",caseName:n,title:"New session",subtitle:`Run ${s[t]||t} in ${n}`}},_findCommandPaletteCaseMatch(e=""){const t=e.trim().toLowerCase();if(!t||!Array.isArray(this.cases))return null;const s=i=>{const r=String(i?.name||"").trim();if(!r)return 0;const o=[r,i?.path,i?.casePath,i?.workingDir,i?.remote?.path,i?.remote?.hostId].filter(Boolean).join(" ").toLowerCase(),l=r.toLowerCase();return l===t?100:l.startsWith(t)?90:l.includes(t)?80:o.includes(t)?60:0};let n=null,a=0;for(const i of this.cases){const r=s(i);r>a&&(n=i,a=r)}return n?.name||null},renderCommandPalette(){const e=document.getElementById("commandPaletteSearch"),t=document.getElementById("commandPaletteList");if(!t)return;const s=e?.value||"",n=this.buildCommandPaletteItems(s);this.commandPaletteItems=n,this.commandPaletteActiveIndex=Math.max(0,Math.min(this.commandPaletteActiveIndex||0,n.length-1)),t.innerHTML=n.map((a,i)=>{const r=i===this.commandPaletteActiveIndex?" active":"",o=a.type==="new-session"?"+":a.type==="browse-sessions"?"\u2261":"\u203A",l=a.type==="browse-sessions"?" command-palette-item--browse":"";return`
2
2
  <button class="command-palette-item${r}${l}" type="button" data-command-index="${i}">
3
3
  <span class="command-palette-icon" aria-hidden="true">${o}</span>
4
4
  <span class="command-palette-text">
@@ -228,7 +228,7 @@
228
228
  ${y}
229
229
  ${f}
230
230
  </div>
231
- `),o&&l&&i.children)for(const v of i.children)a(v,r+1)};for(const i of t)a(i,0);e.innerHTML=s.join(""),e.querySelectorAll(".file-tree-item").forEach(i=>{i.addEventListener("click",()=>{const r=i.dataset.path;i.dataset.type==="directory"?this.toggleFileBrowserFolder(r):this.openFilePreview(r)})})},hasMatchingChild(e,t){if(!e.children)return!1;for(const s of e.children)if(s.name.toLowerCase().includes(t)||s.type==="directory"&&this.hasMatchingChild(s,t))return!0;return!1},toggleFileBrowserFolder(e){this.fileBrowserExpandedDirs.has(e)?this.fileBrowserExpandedDirs.delete(e):this.fileBrowserExpandedDirs.add(e),this.renderFileBrowserTree()},filterFileBrowser(e){this.fileBrowserFilter=e,e&&this.expandAllDirectories(this.fileBrowserData?.tree||[]),this.renderFileBrowserTree()},expandAllDirectories(e){for(const t of e)t.type==="directory"&&(this.fileBrowserExpandedDirs.add(t.path),t.children&&this.expandAllDirectories(t.children))},collapseAllDirectories(){this.fileBrowserExpandedDirs.clear()},toggleFileBrowserExpand(){this.fileBrowserAllExpanded=!this.fileBrowserAllExpanded;const e=this.$("fileBrowserExpandBtn");this.fileBrowserAllExpanded?(this.expandAllDirectories(this.fileBrowserData?.tree||[]),e&&(e.innerHTML="\u229F")):(this.collapseAllDirectories(),e&&(e.innerHTML="\u229E")),this.renderFileBrowserTree()},refreshFileBrowser(){if(this.activeSessionId){this.fileBrowserExpandedDirs.clear(),this.fileBrowserFilter="",this.fileBrowserAllExpanded=!1;const e=this.$("fileBrowserSearch");e&&(e.value=""),this.loadFileBrowser(this.activeSessionId)}},toggleFileBrowserButton(){const t=this.$("fileBrowserPanel")?.classList.contains("visible"),s=document.querySelector(".btn-file-viewer");if(t){this.closeFileBrowserPanel(),s&&s.setAttribute("aria-expanded","false");return}if(!this.activeSessionId){this.showToast("Open a session to browse its files","info");return}const n=this.loadAppSettingsFromStorage();n.showFileBrowser=!0,this.saveAppSettingsToStorage(n);const a=document.getElementById("appSettingsShowFileBrowser");a&&(a.checked=!0),this.applyMonitorVisibility(),s&&s.setAttribute("aria-expanded","true")},closeFileBrowserPanel(){const e=this.$("fileBrowserPanel");if(e&&(e.classList.remove("visible"),e.style.left="",e.style.top="",e.style.bottom="",e.style.right=""),this.fileBrowserDragListeners){const a=this.fileBrowserDragListeners;document.removeEventListener("mousemove",a.move),document.removeEventListener("mouseup",a.up),document.removeEventListener("touchmove",a.touchMove),document.removeEventListener("touchend",a.up),document.removeEventListener("touchcancel",a.up),a.handle&&(a.handle.removeEventListener("mousedown",a.handleMouseDown),a.handle.removeEventListener("touchstart",a.handleTouchStart),a._onFirstDrag&&(a.handle.removeEventListener("mousedown",a._onFirstDrag),a.handle.removeEventListener("touchstart",a._onFirstDrag))),this.fileBrowserDragListeners=null}const t=this.loadAppSettingsFromStorage();t.showFileBrowser=!1,this.saveAppSettingsToStorage(t);const s=document.getElementById("appSettingsShowFileBrowser");s&&(s.checked=!1);const n=document.querySelector(".btn-file-viewer");n&&n.setAttribute("aria-expanded","false")},async openFilePreview(e,t=this.activeSessionId,s=null){if(!t||!e)return;const n=this.$("filePreviewOverlay"),a=this.$("filePreviewTitle"),i=this.$("filePreviewBody"),r=this.$("filePreviewFooter");if(!n||!i)return;n.classList.add("visible"),a.textContent=e,i.innerHTML='<div class="binary-message">Loading...</div>',r.textContent="";const o=(e.split(".").pop()||"").toLowerCase();if(s){const l=`/api/sessions/${t}/attachments/${encodeURIComponent(s)}`,d=new Set(["png","jpg","jpeg","gif","webp","bmp","svg"]);if(r.textContent=o.toUpperCase(),d.has(o))i.innerHTML=`<img src="${escapeHtml(`${l}/raw`)}" alt="${escapeHtml(e)}">`;else if(o==="pdf")i.innerHTML=`<iframe src="${escapeHtml(`${l}/raw`)}" title="${escapeHtml(e)}"></iframe>`;else if(o==="docx"||o==="pptx")i.innerHTML=`<iframe src="${escapeHtml(`${l}/preview`)}" title="${escapeHtml(e)}"></iframe>`;else try{const c=await fetch(`${l}/raw`);if(!c.ok)throw new Error("Failed to load attachment");const m=await c.text();i.innerHTML=`<pre><code>${escapeHtml(m)}</code></pre>`}catch(c){i.innerHTML=`<div class="binary-message">Error: ${escapeHtml(c.message)}</div>`}return}if(o==="docx"||o==="pptx"){r.textContent=o.toUpperCase();const l=`/api/sessions/${t}/file-preview?path=${encodeURIComponent(e)}`;i.innerHTML=`<iframe src="${escapeHtml(l)}" title="${escapeHtml(e)}"></iframe>`;return}if(o==="pdf"){r.textContent="PDF";const l=`/api/sessions/${t}/file-raw?path=${encodeURIComponent(e)}`;i.innerHTML=`<iframe src="${escapeHtml(l)}" title="${escapeHtml(e)}"></iframe>`;return}if(o==="svg"){r.textContent="SVG";try{const l=await fetch(`/api/sessions/${t}/file-raw?path=${encodeURIComponent(e)}`);if(!l.ok)throw new Error("Failed to load image");const d=URL.createObjectURL(new Blob([await l.text()],{type:"image/svg+xml"}));i.innerHTML=`<img src="${d}" alt="${escapeHtml(e)}">`;const c=i.querySelector("img");c&&(c.onload=()=>URL.revokeObjectURL(d))}catch(l){i.innerHTML=`<div class="binary-message">Error: ${escapeHtml(l.message)}</div>`}return}try{const l=await fetch(`/api/sessions/${t}/file-content?path=${encodeURIComponent(e)}&lines=500`);if(!l.ok)throw new Error("Failed to load file");const d=await l.json();if(!d.success)throw new Error(d.error||"Failed to load file");const c=d.data;if(c.type==="image")i.innerHTML=`<img src="${c.url}" alt="${escapeHtml(e)}">`,r.textContent=`${this.formatFileSize(c.size)} \u2022 ${c.extension}`;else if(c.type==="video")i.innerHTML=`<video src="${c.url}" controls autoplay></video>`,r.textContent=`${this.formatFileSize(c.size)} \u2022 ${c.extension}`;else if(c.type==="audio")i.innerHTML=`<audio src="${c.url}" controls autoplay></audio>`,r.textContent=`${this.formatFileSize(c.size)} \u2022 ${c.extension}`;else if(c.type==="binary"){const m=`/api/sessions/${t}/file-raw?path=${encodeURIComponent(e)}&download=true`;i.innerHTML=`<div class="binary-message">Binary file (${this.formatFileSize(c.size)})<br>Cannot preview<br><a href="${escapeHtml(m)}" download>Download</a></div>`,r.textContent=c.extension||"binary"}else{this.filePreviewContent=c.content,i.innerHTML=`<pre><code>${escapeHtml(c.content)}</code></pre>`;const m=c.truncated?` (showing 500/${c.totalLines} lines)`:"";r.textContent=`${c.totalLines} lines \u2022 ${this.formatFileSize(c.size)}${m}`}}catch(l){console.error("Failed to preview file:",l),i.innerHTML=`<div class="binary-message">Error: ${escapeHtml(l.message)}</div>`}},closeFilePreview(){const e=this.$("filePreviewOverlay");e&&e.classList.remove("visible"),this.filePreviewContent=""},_onAttachmentDetected(e){if(console.log("[Attachment Detected]",e),this.addAttachmentCard(e),e.sessionId){const t=this.attachmentHistoryCounts.get(e.sessionId)??this.sessions.get(e.sessionId)?.attachmentHistory?.length??0;this.attachmentHistoryCounts.set(e.sessionId,Math.min(t+1,100)),e.sessionId===this.activeSessionId&&(this.updateAttachmentHistoryBadge(),this.attachmentHistoryDrawerOpen&&this._debouncedCall("attachmentHistoryRefresh",()=>{this.attachmentHistoryDrawerOpen&&this.activeSessionId===e.sessionId&&this.loadAttachmentHistory(e.sessionId)},250))}},ensureAttachmentCardStack(){let e=this.attachmentCardStack||document.getElementById("attachmentCardStack");return e||(e=document.createElement("div"),e.id="attachmentCardStack",e.className="attachment-card-stack",document.body.appendChild(e)),this.attachmentCardStack=e,e},openAttachmentInNewTab(e,t,s=null){const n=s?`/api/sessions/${e}/attachments/${encodeURIComponent(s)}/raw`:`/api/sessions/${e}/file-raw?path=${encodeURIComponent(t)}`;window.open(n,"_blank")},addAttachmentCard(e){const{sessionId:t,relativePath:s,fileName:n,timestamp:a,size:i,attachmentType:r,extension:o,attachmentId:l,rawUrl:d,previewUrl:c,thumbnailUrl:m}=e,u=s||n,h=l||`${t}-${a}-${n}`;if(this.attachmentCards.has(h)){this.attachmentCards.get(h).element.focus?.();return}if(this.attachmentCards.size>=10){const C=this.attachmentCards.keys().next().value;C&&this.closeAttachmentCard(C)}const y=this.ensureAttachmentCardStack(),f=this.sessions.get(t)?.name||t.substring(0,8),v=d||(l?`/api/sessions/${t}/attachments/${encodeURIComponent(l)}/raw`:`/api/sessions/${t}/file-raw?path=${encodeURIComponent(u)}`),T=c||(l?`/api/sessions/${t}/attachments/${encodeURIComponent(l)}/preview`:null),g=m||(l?`/api/sessions/${t}/attachments/${encodeURIComponent(l)}/thumbnail`:`/api/sessions/${t}/file-thumbnail?path=${encodeURIComponent(u)}`),S=l?`${v}?download=true`:`${v}&download=true`,$=(o||r||"file").toUpperCase(),w=document.createElement("article");w.className=`attachment-card attachment-${escapeHtml(r||"file")}`,w.tabIndex=0,w.dataset.attachmentId=h,w.dataset.previewUrl=T||"",w.innerHTML=`
231
+ `),o&&l&&i.children)for(const v of i.children)a(v,r+1)};for(const i of t)a(i,0);e.innerHTML=s.join(""),e.querySelectorAll(".file-tree-item").forEach(i=>{i.addEventListener("click",()=>{const r=i.dataset.path;i.dataset.type==="directory"?this.toggleFileBrowserFolder(r):this.openFilePreview(r)})})},hasMatchingChild(e,t){if(!e.children)return!1;for(const s of e.children)if(s.name.toLowerCase().includes(t)||s.type==="directory"&&this.hasMatchingChild(s,t))return!0;return!1},toggleFileBrowserFolder(e){this.fileBrowserExpandedDirs.has(e)?this.fileBrowserExpandedDirs.delete(e):this.fileBrowserExpandedDirs.add(e),this.renderFileBrowserTree()},filterFileBrowser(e){this.fileBrowserFilter=e,e&&this.expandAllDirectories(this.fileBrowserData?.tree||[]),this.renderFileBrowserTree()},expandAllDirectories(e){for(const t of e)t.type==="directory"&&(this.fileBrowserExpandedDirs.add(t.path),t.children&&this.expandAllDirectories(t.children))},collapseAllDirectories(){this.fileBrowserExpandedDirs.clear()},toggleFileBrowserExpand(){this.fileBrowserAllExpanded=!this.fileBrowserAllExpanded;const e=this.$("fileBrowserExpandBtn");this.fileBrowserAllExpanded?(this.expandAllDirectories(this.fileBrowserData?.tree||[]),e&&(e.innerHTML="\u229F")):(this.collapseAllDirectories(),e&&(e.innerHTML="\u229E")),this.renderFileBrowserTree()},refreshFileBrowser(){if(this.activeSessionId){this.fileBrowserExpandedDirs.clear(),this.fileBrowserFilter="",this.fileBrowserAllExpanded=!1;const e=this.$("fileBrowserSearch");e&&(e.value=""),this.loadFileBrowser(this.activeSessionId)}},toggleFileBrowserButton(){const t=this.$("fileBrowserPanel")?.classList.contains("visible"),s=document.querySelector(".btn-file-viewer");if(t){this.closeFileBrowserPanel(),s&&s.setAttribute("aria-expanded","false");return}if(!this.activeSessionId){this.showToast("Open a session to browse its files","info");return}const n=this.loadAppSettingsFromStorage();n.showFileBrowser=!0,this.saveAppSettingsToStorage(n);const a=document.getElementById("appSettingsShowFileBrowser");a&&(a.checked=!0),this.applyMonitorVisibility(),s&&s.setAttribute("aria-expanded","true")},closeFileBrowserPanel(){const e=this.$("fileBrowserPanel");if(e&&(e.classList.remove("visible"),e.style.left="",e.style.top="",e.style.bottom="",e.style.right=""),this.fileBrowserDragListeners){const a=this.fileBrowserDragListeners;document.removeEventListener("mousemove",a.move),document.removeEventListener("mouseup",a.up),document.removeEventListener("touchmove",a.touchMove),document.removeEventListener("touchend",a.up),document.removeEventListener("touchcancel",a.up),a.handle&&(a.handle.removeEventListener("mousedown",a.handleMouseDown),a.handle.removeEventListener("touchstart",a.handleTouchStart),a._onFirstDrag&&(a.handle.removeEventListener("mousedown",a._onFirstDrag),a.handle.removeEventListener("touchstart",a._onFirstDrag))),this.fileBrowserDragListeners=null}const t=this.loadAppSettingsFromStorage();t.showFileBrowser=!1,this.saveAppSettingsToStorage(t);const s=document.getElementById("appSettingsShowFileBrowser");s&&(s.checked=!1);const n=document.querySelector(".btn-file-viewer");n&&n.setAttribute("aria-expanded","false")},async openFilePreview(e,t=this.activeSessionId,s=null){if(!t||!e)return;const n=this.$("filePreviewOverlay"),a=this.$("filePreviewTitle"),i=this.$("filePreviewBody"),r=this.$("filePreviewFooter");if(!n||!i)return;n.classList.add("visible"),a.textContent=e,i.innerHTML='<div class="binary-message">Loading...</div>',r.textContent="";const o=(e.split(".").pop()||"").toLowerCase();if(s){const l=`/api/sessions/${t}/attachments/${encodeURIComponent(s)}`,d=new Set(["png","jpg","jpeg","gif","webp","bmp","svg"]);if(r.textContent=o.toUpperCase(),d.has(o))i.innerHTML=`<img src="${escapeHtml(`${l}/raw`)}" alt="${escapeHtml(e)}">`;else if(o==="pdf")i.innerHTML=`<iframe src="${escapeHtml(`${l}/raw`)}" title="${escapeHtml(e)}"></iframe>`;else if(o==="docx"||o==="pptx")i.innerHTML=`<iframe src="${escapeHtml(`${l}/preview`)}" title="${escapeHtml(e)}"></iframe>`;else try{const c=await fetch(`${l}/raw`);if(!c.ok)throw new Error("Failed to load attachment");const m=await c.text();i.innerHTML=`<pre><code>${escapeHtml(m)}</code></pre>`}catch(c){i.innerHTML=`<div class="binary-message">Error: ${escapeHtml(c.message)}</div>`}return}if(o==="docx"||o==="pptx"){r.textContent=o.toUpperCase();const l=`/api/sessions/${t}/file-preview?path=${encodeURIComponent(e)}`;i.innerHTML=`<iframe src="${escapeHtml(l)}" title="${escapeHtml(e)}"></iframe>`;return}if(o==="pdf"){r.textContent="PDF";const l=`/api/sessions/${t}/file-raw?path=${encodeURIComponent(e)}`;i.innerHTML=`<iframe src="${escapeHtml(l)}" title="${escapeHtml(e)}"></iframe>`;return}if(o==="svg"){r.textContent="SVG";try{const l=await fetch(`/api/sessions/${t}/file-raw?path=${encodeURIComponent(e)}`);if(!l.ok)throw new Error("Failed to load image");const d=URL.createObjectURL(new Blob([await l.text()],{type:"image/svg+xml"}));i.innerHTML=`<img src="${d}" alt="${escapeHtml(e)}">`;const c=i.querySelector("img");c&&(c.onload=()=>URL.revokeObjectURL(d))}catch(l){i.innerHTML=`<div class="binary-message">Error: ${escapeHtml(l.message)}</div>`}return}try{const l=await fetch(`/api/sessions/${t}/file-content?path=${encodeURIComponent(e)}&lines=500`);if(!l.ok)throw new Error("Failed to load file");const d=await l.json();if(!d.success)throw new Error(d.error||"Failed to load file");const c=d.data;if(c.type==="image")i.innerHTML=`<img src="${c.url}" alt="${escapeHtml(e)}">`,r.textContent=`${this.formatFileSize(c.size)} \u2022 ${c.extension}`;else if(c.type==="video")i.innerHTML=`<video src="${c.url}" controls autoplay></video>`,r.textContent=`${this.formatFileSize(c.size)} \u2022 ${c.extension}`;else if(c.type==="audio")i.innerHTML=`<audio src="${c.url}" controls autoplay></audio>`,r.textContent=`${this.formatFileSize(c.size)} \u2022 ${c.extension}`;else if(c.type==="binary"){const m=`/api/sessions/${t}/file-raw?path=${encodeURIComponent(e)}&download=true`;i.innerHTML=`<div class="binary-message">Binary file (${this.formatFileSize(c.size)})<br>Cannot preview<br><a href="${escapeHtml(m)}" download>Download</a></div>`,r.textContent=c.extension||"binary"}else{this.filePreviewContent=c.content,i.innerHTML=`<pre><code>${escapeHtml(c.content)}</code></pre>`;const m=c.truncated?` (showing 500/${c.totalLines} lines)`:"";r.textContent=`${c.totalLines} lines \u2022 ${this.formatFileSize(c.size)}${m}`}}catch(l){console.error("Failed to preview file:",l),i.innerHTML=`<div class="binary-message">Error: ${escapeHtml(l.message)}</div>`}},closeFilePreview(){const e=this.$("filePreviewOverlay");e&&e.classList.remove("visible"),this.filePreviewContent=""},_onAttachmentDetected(e){if(console.log("[Attachment Detected]",e),this.addAttachmentCard(e),e.sessionId){const t=this.attachmentHistoryCounts.get(e.sessionId)??this.sessions.get(e.sessionId)?.attachmentHistory?.length??0;this.attachmentHistoryCounts.set(e.sessionId,Math.min(t+1,100)),e.sessionId===this.activeSessionId&&(this.updateAttachmentHistoryBadge(),this.attachmentHistoryDrawerOpen&&this._debouncedCall("attachmentHistoryRefresh",()=>{this.attachmentHistoryDrawerOpen&&this.activeSessionId===e.sessionId&&this.loadAttachmentHistory(e.sessionId)},250))}},ensureAttachmentCardStack(){let e=this.attachmentCardStack||document.getElementById("attachmentCardStack");return e||(e=document.createElement("div"),e.id="attachmentCardStack",e.className="attachment-card-stack",document.body.appendChild(e)),this.attachmentCardStack=e,e},openAttachmentInNewTab(e,t,s=null){const n=s?`/api/sessions/${e}/attachments/${encodeURIComponent(s)}/raw`:`/api/sessions/${e}/file-raw?path=${encodeURIComponent(t)}`;window.open(n,"_blank")},addAttachmentCard(e){const{sessionId:t,relativePath:s,fileName:n,timestamp:a,size:i,attachmentType:r,extension:o,attachmentId:l,rawUrl:d,previewUrl:c,thumbnailUrl:m}=e,u=s||n,h=l||`${t}-${a}-${n}`;if(this.attachmentCards.has(h)){this.attachmentCards.get(h).element.focus?.();return}if(this.attachmentCards.size>=10){const I=this.attachmentCards.keys().next().value;I&&this.closeAttachmentCard(I)}const y=this.ensureAttachmentCardStack(),f=this.sessions.get(t)?.name||t.substring(0,8),v=d||(l?`/api/sessions/${t}/attachments/${encodeURIComponent(l)}/raw`:`/api/sessions/${t}/file-raw?path=${encodeURIComponent(u)}`),T=c||(l?`/api/sessions/${t}/attachments/${encodeURIComponent(l)}/preview`:null),g=m||(l?`/api/sessions/${t}/attachments/${encodeURIComponent(l)}/thumbnail`:`/api/sessions/${t}/file-thumbnail?path=${encodeURIComponent(u)}`),S=l?`${v}?download=true`:`${v}&download=true`,$=(o||r||"file").toUpperCase(),w=document.createElement("article");w.className=`attachment-card attachment-${escapeHtml(r||"file")}`,w.tabIndex=0,w.dataset.attachmentId=h,w.dataset.previewUrl=T||"",w.innerHTML=`
232
232
  <div class="attachment-thumbnail">
233
233
  ${g?`<img class="attachment-thumbnail-img" src="${escapeHtml(g)}" alt="">`:""}
234
234
  <div class="attachment-thumbnail-fallback ${g?"":"visible"}">${escapeHtml($)}</div>
@@ -0,0 +1,68 @@
1
+ "use strict";Object.assign(CodemanApp.prototype,{buildEnvOverrides(e,t){const s={};return(e?.agentTeams||t?.agentTeamsEnabled)&&(s.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS="1"),s},getEffortSetting(e){const t=e?.thinkingEffort;return["low","medium","high","xhigh","max","ultracode"].includes(t)?t:void 0},formatCasePickerLabel(e){return e?.location==="remote"&&e.remote?.hostId?`${e.name} @ ${e.remote.hostId}`:e?.location==="docker"?`${e.name} (${this.dockerCaseTag(e.docker?.hostId)})`:e?.name||""},dockerCaseTag(e){return!e||e==="default"||e==="local"||/^q-/.test(e)?"docker":e},buildCasePickerOptions(e=[]){const t=[],s=new Set;for(const a of e)!a?.name||s.has(a.name)||(s.add(a.name),t.push(a));return s.has("testcase")||t.push({name:"testcase"}),t.map(a=>{const o=this.formatCasePickerLabel(a),n=[a.name,o,a.path,a.location,a.remote?.hostId,a.remote?.label,a.remote?.path,a.docker?.container,a.docker?.image,a.docker?.path].filter(Boolean).join(" ").toLowerCase();return{name:a.name,label:o,case:a,searchText:n}}).sort((a,o)=>a.label.localeCompare(o.label,void 0,{sensitivity:"base",numeric:!0}))},filterCasePickerOptions(e,t){const s=String(t||"").trim().toLowerCase().split(/\s+/).filter(Boolean);return s.length===0?e:e.filter(a=>s.every(o=>a.searchText.includes(o)))},getCasePickerOptions(){return this.buildCasePickerOptions(this.cases||[])},updateCasePickerInput(e){const t=document.getElementById("quickStartCaseSearch");if(!t)return;const s=this.getCasePickerOptions().find(a=>a.name===e);t.value=s?.label||e||"testcase",t.title=s?.label||t.value},renderQuickStartCaseSelectOptions(e,t){e&&(e.innerHTML=t.map(s=>`<option value="${escapeHtml(s.name)}">${escapeHtml(s.label)}</option>`).join(""))},openCasePicker(e=""){const t=document.getElementById("quickStartCaseSearch"),s=document.getElementById("quickStartCaseList");!t||!s||(this._casePickerOpen=!0,this._casePickerFilter=e,this._casePickerActiveIndex=0,t.setAttribute("aria-expanded","true"),this.renderCasePickerList())},closeCasePicker(){const e=document.getElementById("quickStartCaseSearch"),t=document.getElementById("quickStartCaseList");this._casePickerOpen=!1,this._casePickerFilter="",e?.setAttribute("aria-expanded","false"),e?.removeAttribute("aria-activedescendant"),t?.classList.add("hidden")},renderCasePickerList(){const e=document.getElementById("quickStartCaseSearch"),t=document.getElementById("quickStartCaseList"),s=document.getElementById("quickStartCase");if(!e||!t||!s)return;const a=this.filterCasePickerOptions(this.getCasePickerOptions(),this._casePickerFilter||""),o=s.value||"testcase",n=Math.max(0,a.length-1);if(this._casePickerActiveIndex=Math.min(Math.max(this._casePickerActiveIndex||0,0),n),a.length===0){t.innerHTML='<div class="case-combobox-empty">No cases match</div>',t.classList.remove("hidden"),e.removeAttribute("aria-activedescendant");return}t.innerHTML=a.map((i,r)=>{const c=r===this._casePickerActiveIndex,d=i.name===o;return`
2
+ <button
3
+ type="button"
4
+ id="${`quickStartCaseOption-${r}`}"
5
+ class="case-combobox-option ${c?"active":""} ${d?"selected":""}"
6
+ role="option"
7
+ aria-selected="${d?"true":"false"}"
8
+ data-case="${escapeHtml(i.name)}"
9
+ title="${escapeHtml(i.label)}">
10
+ <span class="case-combobox-check">${d?"\u2713":""}</span>
11
+ <span class="case-combobox-option-label">${escapeHtml(i.label)}</span>
12
+ </button>
13
+ `}).join(""),t.classList.remove("hidden"),e.setAttribute("aria-activedescendant",`quickStartCaseOption-${this._casePickerActiveIndex}`)},selectQuickStartCase(e,{save:t=!0}={}){const s=document.getElementById("quickStartCase");s&&(s.value=e||"testcase",this.updateCasePickerInput(s.value),this.closeCasePicker(),this.updateDirDisplayForCase(s.value),this.updateMobileCaseLabel(s.value),t&&this.saveLastUsedCase(s.value))},setupQuickStartCasePicker(){const e=document.getElementById("quickStartCase"),t=document.getElementById("quickStartCaseSearch"),s=document.getElementById("quickStartCaseList"),a=document.getElementById("quickStartCasePicker");!e||!t||!s||!a||t.dataset.listenerAdded||(t.addEventListener("focus",()=>{t.select?.(),this.openCasePicker("")}),t.addEventListener("click",()=>{t.select?.(),this.openCasePicker("")}),t.addEventListener("input",()=>{this.openCasePicker(t.value)}),t.addEventListener("keydown",o=>{const n=this.filterCasePickerOptions(this.getCasePickerOptions(),this._casePickerFilter||t.value);if(o.key==="ArrowDown")o.preventDefault(),this._casePickerActiveIndex=Math.min((this._casePickerActiveIndex||0)+1,Math.max(0,n.length-1)),this._casePickerOpen?this.renderCasePickerList():this.openCasePicker(t.value);else if(o.key==="ArrowUp")o.preventDefault(),this._casePickerActiveIndex=Math.max((this._casePickerActiveIndex||0)-1,0),this._casePickerOpen?this.renderCasePickerList():this.openCasePicker(t.value);else if(o.key==="Enter"){const i=n[this._casePickerActiveIndex||0];i&&(o.preventDefault(),this.selectQuickStartCase(i.name),this.run?.())}else o.key==="Escape"?(o.preventDefault(),this.updateCasePickerInput(e.value),this.closeCasePicker()):o.key==="Tab"&&(this.updateCasePickerInput(e.value),this.closeCasePicker())}),s.addEventListener("mousedown",o=>o.preventDefault()),s.addEventListener("click",o=>{const n=o.target.closest?.(".case-combobox-option");n?.dataset?.case&&this.selectQuickStartCase(n.dataset.case)}),document.addEventListener&&!this._casePickerDocumentListenerAdded&&(document.addEventListener("pointerdown",o=>{a.contains(o.target)||(this.updateCasePickerInput(e.value),this.closeCasePicker())}),this._casePickerDocumentListenerAdded=!0),t.dataset.listenerAdded="true")},async loadQuickStartCases(e=null,t=null){try{let s=null;try{const r=t?await t:await fetch("/api/settings").then(c=>c.ok?c.json():null).then(c=>c?.data??null);r&&(s=r.lastUsedCase||null)}catch{}const o=(await(await fetch("/api/cases")).json()).data;this.cases=o,console.log("[loadQuickStartCases] Loaded cases:",o.map(r=>r.name),"lastUsedCase:",s);const n=document.getElementById("quickStartCase"),i=this.getCasePickerOptions();if(this.renderQuickStartCaseSelectOptions(n,i),console.log("[loadQuickStartCases] Set options:",n.innerHTML.substring(0,200)),e)n.value=e,this.updateDirDisplayForCase(e),this.updateMobileCaseLabel(e);else if(s&&o.some(r=>r.name===s))n.value=s,this.updateDirDisplayForCase(s),this.updateMobileCaseLabel(s);else if(o.length>0){const r=o.find(c=>c.name==="testcase")||o[0];n.value=r.name,this.updateDirDisplayForCase(r.name),this.updateMobileCaseLabel(r.name)}else n.value="testcase",document.getElementById("dirDisplay").textContent="~/codeman-cases/testcase",this.updateMobileCaseLabel("testcase");this.updateCasePickerInput(n.value),this.renderCasePickerList(),this.closeCasePicker(),n.dataset.listenerAdded||(n.addEventListener("change",()=>{this.updateDirDisplayForCase(n.value),this.saveLastUsedCase(n.value),this.updateMobileCaseLabel(n.value),this.updateCasePickerInput(n.value)}),n.dataset.listenerAdded="true"),this.setupQuickStartCasePicker()}catch(s){console.error("Failed to load cases:",s)}},async updateDirDisplayForCase(e){try{const s=(await(await fetch(`/api/cases/${e}`)).json()).data;s.path&&(document.getElementById("dirDisplay").textContent=s.path,document.getElementById("dirInput").value=s.path)}catch{document.getElementById("dirDisplay").textContent=e}},async saveLastUsedCase(e){try{await fetch("/api/settings",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({lastUsedCase:e})})}catch(t){console.error("Failed to save last used case:",t)}},async quickStart(){return this.run()},async run(){const e=this._runMode||"claude";return e==="opencode"?this.runOpenCode():e==="codex"?this.runCodex():e==="gemini"?this.runGemini():this.runClaude()},setRunMode(e){this._runMode=e;try{localStorage.setItem("codeman_runMode",e)}catch{}this._applyRunMode(),this._apiPut("/api/settings",{runMode:e}).catch(()=>{}),document.getElementById("runModeMenu")?.classList.remove("active")},toggleRunModeMenu(e){e?.stopPropagation();const t=document.getElementById("runModeMenu");if(t&&(t.classList.toggle("active"),t.querySelectorAll(".run-mode-option").forEach(s=>{s.classList.toggle("selected",s.dataset.mode===this.runMode)}),t.classList.contains("active"))){this._loadRunModeHistory();const s=a=>{t.contains(a.target)||(t.classList.remove("active"),document.removeEventListener("click",s))};setTimeout(()=>document.addEventListener("click",s),0)}},async _loadRunModeHistory(){const e=document.getElementById("runModeHistory");if(e){e.innerHTML='<div class="run-mode-hist-empty">Loading...</div>';try{const t=await this._fetchHistorySessions(10);if(t.length===0){e.innerHTML='<div class="run-mode-hist-empty">No history</div>';return}e.replaceChildren();for(const s of t){const a=new Date(s.lastModified),o=a.toLocaleDateString("en",{month:"short",day:"numeric"})+" "+a.toLocaleTimeString("en",{hour:"2-digit",minute:"2-digit",hour12:!1}),n=s.workingDir.replace(/^\/home\/[^/]+\//,"~/"),i=document.createElement("button");i.className="run-mode-option",i.title=s.workingDir,i.dataset.sessionId=s.sessionId,i.dataset.workingDir=s.workingDir;const r=document.createElement("span");r.className="hist-dir",r.textContent=n;const c=document.createElement("span");c.className="hist-meta",c.textContent=o,i.append(r,c),i.addEventListener("click",d=>{d.stopPropagation(),this.resumeHistorySession(s.sessionId,s.workingDir,s.name)}),e.appendChild(i)}}catch{e.innerHTML='<div class="run-mode-hist-empty">Failed to load</div>'}}},_applyRunMode(){const e=this.runMode,t=document.getElementById("runBtn"),s=t?.nextElementSibling,a=document.getElementById("runBtnLabel");t&&(t.className=`btn-toolbar btn-run mode-${e}`),s&&(s.className=`btn-toolbar btn-run-gear mode-${e}`),a&&(a.textContent=e==="opencode"?"Run OC":e==="codex"?"Run CX":e==="gemini"?"Run GM":"Run")},_initRunMode(){try{this._runMode=localStorage.getItem("codeman_runMode")||"claude"}catch{this._runMode="claude"}this._applyRunMode()},incrementTabCount(){const e=document.getElementById("tabCount"),t=parseInt(e.value)||1;e.value=Math.min(20,t+1)},decrementTabCount(){const e=document.getElementById("tabCount"),t=parseInt(e.value)||1;e.value=Math.max(1,t-1)},incrementShellCount(){const e=document.getElementById("shellCount"),t=parseInt(e.value)||1;e.value=Math.min(20,t+1)},decrementShellCount(){const e=document.getElementById("shellCount"),t=parseInt(e.value)||1;e.value=Math.max(1,t-1)},_nextCaseSessionStartNumber(e,t="w"){const s=new RegExp(`^${t}(\\d+)-([a-zA-Z0-9_-]+)`);let a=1;for(const[,o]of this.sessions||[]){const n=o.name&&o.name.match(s);if(n&&n[2]===e){const i=parseInt(n[1]);i>=a&&(a=i+1)}}return a},async runClaude(){const e=document.getElementById("quickStartCase").value||"testcase",t=Math.min(20,Math.max(1,parseInt(document.getElementById("tabCount").value)||1));this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting ${t} Claude session(s) in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{let a=(await(await fetch(`/api/cases/${e}`)).json())?.data??{};if(!a.path){const C=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!C.success)throw new Error(C.error||"Failed to create case");a=C.data.case}const o=a.path;if(!o)throw new Error("Case path not found");if(a.location==="remote"||a.location==="docker"){const f=this._nextCaseSessionStartNumber(e);let C;if(a.location==="docker"){const k=this.loadAppSettingsFromStorage(),S=this.getCaseSettings(e).opusContext1m||k.opusContext1mEnabled;C=k.claudeModel||(S?"opus[1m]":"")}const E=[];let I=!1;for(let k=0;k<t;k++){const x=JSON.stringify({caseName:e,mode:"claude",sessionName:`w${f+k}-${e}`,...C!==void 0?{modelOverride:C}:{}}),S=async()=>(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:x})).json();let b=await S();if(!b.success&&b.errorCode==="CONFLICT"&&a.location==="docker"&&!I&&(I=!0,confirm(`Container config for "${e}" changed since its container was created.
14
+
15
+ Recreate the container to apply the new config? Workspace files and the conversation survive (the conversation resumes on launch).`))){const T=await(await fetch(`/api/docker-cases/${encodeURIComponent(e)}/recreate`,{method:"POST"})).json();if(!T.success)throw new Error(T.error||"Failed to recreate container");b=await S()}if(!b.success)throw new Error(b.error||"Failed to start remote Claude session");E.push(b.data.sessionId)}this.terminal.writeln(`\x1B[90m All ${t} remote session(s) ready\x1B[0m`),E[0]&&(await this.selectSession(E[0]),this.loadQuickStartCases()),this.terminal.focus();return}let n=null;const i=this._nextCaseSessionStartNumber(e),r=this.isRalphTrackerEnabledByDefault(),c=[];for(let f=0;f<t;f++)c.push(`w${i+f}-${e}`);const d=this.getCaseSettings(e),l=this.loadAppSettingsFromStorage(),u=this.buildEnvOverrides(d,l),p=Object.keys(u).length>0,m=this.getEffortSetting(l),h=d.opusContext1m||l.opusContext1mEnabled,y=l.claudeModel||(h?"opus[1m]":"");this.terminal.writeln(`\x1B[90m Creating ${t} session(s)...\x1B[0m`);const g=c.map(f=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:o,name:f,...p?{envOverrides:u}:{},...m?{effort:m}:{},...y!==void 0?{modelOverride:y}:{},statusLineTelemetry:l.showPlanUsageLimits===!0})}).then(C=>C.json())),w=await Promise.all(g),v=[];for(const f of w){if(!f.success)throw new Error(f.error);v.push(f.data.session.id)}n=v[0],await Promise.all(v.map(f=>fetch(`/api/sessions/${f}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:r,disableAutoEnable:!r})}))),this.terminal.writeln(`\x1B[90m Starting ${t} session(s) in parallel...\x1B[0m`),await Promise.all(v.map(f=>fetch(`/api/sessions/${f}/interactive`,{method:"POST"}))),this.terminal.writeln(`\x1B[90m All ${t} sessions ready\x1B[0m`),n&&(await this.selectSession(n),this.loadQuickStartCases()),this.terminal.focus()}catch(s){this.terminal.writeln(`\x1B[1;31m Error: ${s.message}\x1B[0m`)}},stopClaude(){if(!this.activeSessionId)return;const e=document.querySelector(".btn-toolbar.btn-stop");e&&(this._stopConfirmTimer?(clearTimeout(this._stopConfirmTimer),this._stopConfirmTimer=null,e.innerHTML=e.dataset.origHtml,delete e.dataset.origHtml,e.classList.remove("confirming"),fetch(`/api/sessions/${this.activeSessionId}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:""})})):(e.dataset.origHtml=e.innerHTML,e.textContent="Tap again",e.classList.add("confirming"),this._stopConfirmTimer=setTimeout(()=>{this._stopConfirmTimer=null,e.dataset.origHtml&&(e.innerHTML=e.dataset.origHtml,delete e.dataset.origHtml),e.classList.remove("confirming")},2e3)))},async runShell(){const e=document.getElementById("quickStartCase").value||"testcase",t=Math.min(20,Math.max(1,parseInt(document.getElementById("shellCount").value)||1));this.terminal.clear(),this.terminal.writeln(`\x1B[1;33m Starting ${t} Shell session(s) in ${e}...\x1B[0m`),this.terminal.writeln("");try{let a=(await(await fetch(`/api/cases/${e}`)).json())?.data??{};if(!a.path){const h=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!h.success)throw new Error(h.error||"Failed to create case");a=h.data.case}const o=(this.cases||[]).find(m=>m.name===e),n=a.location==="remote"||a.location==="docker"||o?.location==="remote"||o?.location==="docker",i=a.path;if(!i)throw new Error("Case path not found");if(a.location==="remote"||a.location==="docker"){const m=this._nextCaseSessionStartNumber(e,"s"),h=[];for(let y=0;y<t;y++){const w=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"shell",sessionName:`s${m+y}-${e}`})})).json();if(!w.success)throw new Error(w.error||"Failed to start remote shell session");h.push(w.data.sessionId)}h[0]&&await this.selectSession(h[0]),this.terminal.focus();return}const r=this._nextCaseSessionStartNumber(e,"s"),c=[];for(let m=0;m<t;m++)c.push(`s${r+m}-${e}`);const d=c.map(m=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({...n?{caseName:e}:{workingDir:i},mode:"shell",name:m})}).then(h=>h.json())),l=await Promise.all(d),u=[];for(const m of l){if(!m.success)throw new Error(m.error);u.push(m.data.session.id)}await Promise.all(u.map(m=>fetch(`/api/sessions/${m}/shell`,{method:"POST"})));const p=this.getTerminalDimensions();p&&await Promise.all(u.map(m=>fetch(`/api/sessions/${m}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(p)}))),u.length>0&&await this.selectSession(u[0]),this.terminal.focus()}catch(s){this.terminal.writeln(`\x1B[1;31m Error: ${s.message}\x1B[0m`)}},async runOpenCode(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(a=>a.name===e)?.location,s=t==="remote"||t==="docker";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting OpenCode session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!s&&!(await(await fetch("/api/opencode/status")).json()).data.available){this.terminal.writeln("\x1B[1;31m OpenCode CLI not found.\x1B[0m"),this.terminal.writeln("\x1B[90m Install with: curl -fsSL https://opencode.ai/install | bash\x1B[0m");return}const a=this.buildEnvOverrides(this.getCaseSettings(e),this.loadAppSettingsFromStorage()),n=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"opencode",sessionName:`w${this._nextCaseSessionStartNumber(e)}-${e}`,...s?{}:{openCodeConfig:{autoAllowTools:!0},...Object.keys(a).length>0?{envOverrides:a}:{}}})})).json();if(!n.success)throw new Error(n.error||"Failed to start OpenCode");n.data.sessionId&&await this.selectSession(n.data.sessionId),this.terminal.focus()}catch(a){this.terminal.writeln(`\x1B[1;31m Error: ${a.message}\x1B[0m`)}},async runCodex(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(a=>a.name===e)?.location,s=t==="remote"||t==="docker";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting Codex session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!s&&!(await(await fetch("/api/codex/status")).json()).data.available){this.terminal.writeln("\x1B[1;31m Codex CLI not found.\x1B[0m"),this.terminal.writeln("\x1B[90m Install with: npm install -g @openai/codex\x1B[0m");return}const a=this.loadAppSettingsFromStorage(),o=this.buildEnvOverrides(this.getCaseSettings(e),a),i=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"codex",sessionName:`w${this._nextCaseSessionStartNumber(e)}-${e}`,...s?{}:{codexConfig:{dangerouslyBypassApprovals:a.codexDangerouslyBypassApprovals??!1,renderMode:"hybrid"},...Object.keys(o).length>0?{envOverrides:o}:{}}})})).json();if(!i.success)throw new Error(i.error||"Failed to start Codex");i.data.sessionId&&await this.selectSession(i.data.sessionId),this.terminal.focus()}catch(a){this.terminal.writeln(`\x1B[1;31m Error: ${a.message}\x1B[0m`)}},async runGemini(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(a=>a.name===e)?.location,s=t==="remote"||t==="docker";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting Gemini session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!s&&!(await(await fetch("/api/gemini/status")).json()).data.available){this.terminal.writeln("\x1B[1;31m Gemini CLI not found.\x1B[0m"),this.terminal.writeln("\x1B[90m Install with: npm install -g @google/gemini-cli\x1B[0m");return}const a=this.buildEnvOverrides(this.getCaseSettings(e),this.loadAppSettingsFromStorage()),n=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"gemini",sessionName:`w${this._nextCaseSessionStartNumber(e)}-${e}`,...s?{}:{geminiConfig:{approvalMode:"yolo"},...Object.keys(a).length>0?{envOverrides:a}:{}}})})).json();if(!n.success)throw new Error(n.error||"Failed to start Gemini");n.data.sessionId&&await this.selectSession(n.data.sessionId),this.terminal.focus()}catch(a){this.terminal.writeln(`\x1B[1;31m Error: ${a.message}\x1B[0m`)}},openSessionOptions(e){const t=this.sessions.get(e);if(!t)return;this.editingSessionId=e;const s=t.mode==="opencode"||t.mode==="codex"||t.mode==="gemini";this.switchOptionsTab(s?"summary":"respawn");const a=document.getElementById("sessionRespawnStatus"),o=document.getElementById("modalEnableRespawnBtn"),n=document.getElementById("modalStopRespawnBtn");this.respawnStatus[e]?(a.classList.add("active"),a.querySelector(".respawn-status-text").textContent=this.respawnStatus[e].state||"Active",o.style.display="none",n.style.display=""):(a.classList.remove("active"),a.querySelector(".respawn-status-text").textContent="Not active",o.style.display="",n.style.display="none");const i=document.getElementById("sessionRespawnSection");t.mode==="claude"&&t.pid?i.style.display="":i.style.display="none";const r=t.mode==="opencode"||t.mode==="codex"||t.mode==="gemini";document.querySelectorAll("[data-claude-only]").forEach(g=>{g.style.display=r?"none":""}),this.selectDurationPreset(""),this.loadSavedRespawnConfig(e),document.getElementById("modalAutoCompactEnabled").checked=t.autoCompactEnabled??!1,document.getElementById("modalAutoCompactThreshold").value=t.autoCompactThreshold??11e4,document.getElementById("modalAutoCompactPrompt").value=t.autoCompactPrompt??"",document.getElementById("modalAutoClearEnabled").checked=t.autoClearEnabled??!1,document.getElementById("modalAutoClearThreshold").value=t.autoClearThreshold??14e4,document.getElementById("modalAutoResumeEnabled").checked=t.autoResumeEnabled??!1,this.updateAutoResumeStatus(e),document.getElementById("modalImageWatcherEnabled").checked=t.imageWatcherEnabled??!0,document.getElementById("modalFlickerFilterEnabled").checked=t.flickerFilterEnabled??!1;const d=parseSessionPrefix(t.name),l=document.getElementById("modalSessionPrefix");d?(l.textContent=d.prefix+": ",l.style.display="",document.getElementById("modalSessionName").value=d.suffix,document.getElementById("modalSessionName").placeholder="Add description..."):(l.style.display="none",l.textContent="",document.getElementById("modalSessionName").value=t.name||"",document.getElementById("modalSessionName").placeholder="Auto (directory name)");const u=t.color||"default";document.getElementById("sessionColorPicker")?.querySelectorAll(".color-swatch").forEach(g=>{g.classList.toggle("selected",g.dataset.color===u)}),this.renderPresetDropdown(),document.getElementById("respawnPresetSelect").value="",document.getElementById("presetDescriptionHint").textContent="";const m=document.querySelector('#sessionOptionsModal .modal-tab-btn[data-tab="ralph"]'),h=document.querySelector('#sessionOptionsModal .modal-tab-btn[data-tab="respawn"]');if(r?(m&&(m.style.display="none"),h&&(h.style.display="none"),this.switchOptionsTab("context")):(m&&(m.style.display=""),h&&(h.style.display="")),!r){const g=this.ralphStates.get(e);this.populateRalphForm({enabled:g?.loop?.enabled??t.ralphLoop?.enabled??!1,completionPhrase:g?.loop?.completionPhrase||t.ralphLoop?.completionPhrase||"",maxIterations:g?.loop?.maxIterations||t.ralphLoop?.maxIterations||0,maxTodos:g?.loop?.maxTodos||t.ralphLoop?.maxTodos,todoExpirationMinutes:g?.loop?.todoExpirationMinutes||t.ralphLoop?.todoExpirationMinutes})}const y=document.getElementById("sessionOptionsModal");y.classList.add("active"),this.activeFocusTrap=new FocusTrap(y),this.activeFocusTrap.activate()},async saveSessionName(){if(!this.editingSessionId)return;const e=this.sessions.get(this.editingSessionId),t=e?parseSessionPrefix(e.name):null,s=document.getElementById("modalSessionName").value.trim();let a;t?a=t.prefix+(s?": "+s:""):a=s;try{await this._apiPut(`/api/sessions/${this.editingSessionId}/name`,{name:a})}catch(o){this.showToast("Failed to save session name: "+o.message,"error")}},async autoSaveAutoCompact(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-compact`,{enabled:document.getElementById("modalAutoCompactEnabled").checked,threshold:parseInt(document.getElementById("modalAutoCompactThreshold").value)||11e4,prompt:document.getElementById("modalAutoCompactPrompt").value.trim()||void 0})}catch{}},async autoSaveAutoClear(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-clear`,{enabled:document.getElementById("modalAutoClearEnabled").checked,threshold:parseInt(document.getElementById("modalAutoClearThreshold").value)||14e4})}catch{}},async autoSaveAutoResume(){if(!this.editingSessionId)return;const e=document.getElementById("modalAutoResumeEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-resume`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.autoResumeEnabled=e,e||(t.autoResumeAt=void 0)),this.updateAutoResumeStatus(this.editingSessionId),this.showToast(`Auto-resume on usage limit ${e?"enabled":"disabled"}`,"success")}catch(t){this.showToast("Failed to toggle auto-resume: "+t.message,"error")}},updateAutoResumeStatus(e){const t=document.getElementById("autoResumeStatus");if(!t||this.editingSessionId!==e)return;const s=this.sessions.get(e);if(s?.autoResumeAt&&s.autoResumeAt>Date.now()){const a=new Date(s.autoResumeAt).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});t.textContent=`Usage limit pause active \u2014 resumes at ${a}`,t.classList.add("active")}else t.textContent="",t.classList.remove("active")},async toggleSessionImageWatcher(){if(!this.editingSessionId)return;const e=document.getElementById("modalImageWatcherEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/image-watcher`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.imageWatcherEnabled=e),this.showToast(`Image watcher ${e?"enabled":"disabled"}`,"success")}catch{this.showToast("Failed to toggle image watcher","error")}},async toggleFlickerFilter(){if(!this.editingSessionId)return;const e=document.getElementById("modalFlickerFilterEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/flicker-filter`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.flickerFilterEnabled=e),this.showToast(`Flicker filter ${e?"enabled":"disabled"}`,"success")}catch{this.showToast("Failed to toggle flicker filter","error")}},async autoSaveRespawnConfig(){if(!this.editingSessionId)return;const e={updatePrompt:document.getElementById("modalRespawnPrompt").value,sendClear:document.getElementById("modalRespawnSendClear").checked,sendInit:document.getElementById("modalRespawnSendInit").checked,kickstartPrompt:document.getElementById("modalRespawnKickstart").value.trim()||void 0,autoAcceptPrompts:document.getElementById("modalRespawnAutoAccept").checked};try{await this._apiPut(`/api/sessions/${this.editingSessionId}/respawn/config`,e)}catch{}},async loadSavedRespawnConfig(e){try{const s=await(await fetch(`/api/sessions/${e}/respawn/config`)).json();if(s.success&&s.data&&s.data.config){const a=s.data.config;document.getElementById("modalRespawnPrompt").value=a.updatePrompt||"update all the docs and CLAUDE.md",document.getElementById("modalRespawnSendClear").checked=a.sendClear??!0,document.getElementById("modalRespawnSendInit").checked=a.sendInit??!0,document.getElementById("modalRespawnKickstart").value=a.kickstartPrompt||"",document.getElementById("modalRespawnAutoAccept").checked=a.autoAcceptPrompts??!0,a.durationMinutes&&(document.querySelector(`.duration-preset-btn[data-minutes="${a.durationMinutes}"]`)?this.selectDurationPreset(String(a.durationMinutes)):(this.selectDurationPreset("custom"),document.getElementById("modalRespawnDuration").value=a.durationMinutes))}}catch{}},selectDurationPreset(e){document.querySelectorAll(".duration-preset-btn").forEach(o=>o.classList.remove("active"));const t=document.querySelector(`.duration-preset-btn[data-minutes="${e}"]`);t&&t.classList.add("active");const s=document.querySelector(".duration-custom-input"),a=document.getElementById("modalRespawnDuration");e==="custom"?(s.classList.add("visible"),a.focus()):(s.classList.remove("visible"),a.value="")},getSelectedDuration(){const e=document.querySelector(".duration-custom-input"),t=document.getElementById("modalRespawnDuration");if(e.classList.contains("visible"))return t.value?parseInt(t.value):null;{const a=document.querySelector(".duration-preset-btn.active")?.dataset.minutes;return a?parseInt(a):null}},switchOptionsTab(e){document.querySelectorAll("#sessionOptionsModal .modal-tab-btn").forEach(t=>{t.classList.toggle("active",t.dataset.tab===e)}),document.getElementById("respawn-tab").classList.toggle("hidden",e!=="respawn"),document.getElementById("context-tab").classList.toggle("hidden",e!=="context"),document.getElementById("ralph-tab").classList.toggle("hidden",e!=="ralph"),document.getElementById("summary-tab").classList.toggle("hidden",e!=="summary"),e==="summary"&&this.editingSessionId&&this.loadRunSummary(this.editingSessionId)},getRalphConfig(){return{enabled:document.getElementById("modalRalphEnabled").checked,completionPhrase:document.getElementById("modalRalphPhrase").value.trim(),maxIterations:parseInt(document.getElementById("modalRalphMaxIterations").value)||0,maxTodos:parseInt(document.getElementById("modalRalphMaxTodos").value)||50,todoExpirationMinutes:parseInt(document.getElementById("modalRalphTodoExpiration").value)||60}},populateRalphForm(e){document.getElementById("modalRalphEnabled").checked=e?.enabled??!1,document.getElementById("modalRalphPhrase").value=e?.completionPhrase||"",document.getElementById("modalRalphMaxIterations").value=e?.maxIterations||0,document.getElementById("modalRalphMaxTodos").value=e?.maxTodos||50,document.getElementById("modalRalphTodoExpiration").value=e?.todoExpirationMinutes||60},async saveRalphConfig(){if(!this.editingSessionId){this.showToast("No session selected","warning");return}const e=this.getRalphConfig();e.enabled&&this.ralphClosedSessions.delete(this.editingSessionId);try{const s=await(await fetch(`/api/sessions/${this.editingSessionId}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).json();if(s.error)throw new Error(s.error);this.showToast("Ralph config saved","success")}catch(t){this.showToast("Failed to save Ralph config: "+t.message,"error")}},startInlineRename(e){const t=this.sessions.get(e);if(!t)return;const s=document.querySelector(`.tab-name[data-session-id="${e}"]`);if(!s)return;this._inlineRenameActive=!0;const a=this.getSessionName(t),o=parseSessionPrefix(t.name),n=s.textContent;for(s.textContent="";s.firstChild;)s.removeChild(s.firstChild);if(o){const c=document.createElement("span");c.textContent=o.prefix+": ",c.style.cssText="color: var(--text-muted); font-size: 0.75rem; white-space: nowrap;",s.appendChild(c)}const i=document.createElement("input");i.type="text",i.value=o?o.suffix:t.name||"",i.placeholder=o?"Add description...":a,i.className="tab-rename-input",i.style.cssText="width: 80px; font-size: 0.75rem; padding: 2px 4px; background: var(--bg-input); border: 1px solid var(--accent); border-radius: 3px; color: var(--text); outline: none;",s.appendChild(i),i.focus(),i.select();const r=async({commit:c})=>{if(!this._inlineRenameActive)return;if(this._inlineRenameActive=!1,this._activeRename=null,!c){this.renderSessionTabs();return}const d=i.value.trim(),l=o?o.prefix+(d?": "+d:""):d;if(s.textContent=l||n,this.sessions.has(e)&&l!==t.name)try{await fetch(`/api/sessions/${e}/name`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:l})})}catch{s.textContent=n,this.showToast("Failed to rename","error")}this.renderSessionTabs()};this._activeRename={sessionId:e,cancel:()=>r({commit:!1})},i.addEventListener("blur",()=>r({commit:!0})),i.addEventListener("keydown",c=>{c.isComposing||c.keyCode===229||(c.key==="Enter"?(c.preventDefault(),i.blur()):c.key==="Escape"&&(i.value="",i.blur()))})},toggleCaseSettings(){const e=document.getElementById("caseSettingsPopover");if(e.classList.contains("hidden")){const t=document.getElementById("quickStartCase").value||"testcase",s=this.getCaseSettings(t);document.getElementById("caseAgentTeams").checked=s.agentTeams,document.getElementById("caseOpusContext1m").checked=s.opusContext1m,e.classList.remove("hidden");const a=o=>{!e.contains(o.target)&&!o.target.classList.contains("btn-case-settings")&&(e.classList.add("hidden"),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),0)}else e.classList.add("hidden")},getCaseSettings(e){try{const t=localStorage.getItem("caseSettings_"+e);if(t)return JSON.parse(t)}catch{}return{agentTeams:!1,opusContext1m:!0}},saveCaseSettings(e,t){localStorage.setItem("caseSettings_"+e,JSON.stringify(t))},onCaseSettingChanged(){const e=document.getElementById("quickStartCase").value||"testcase",t=this.getCaseSettings(e);t.agentTeams=document.getElementById("caseAgentTeams").checked,t.opusContext1m=document.getElementById("caseOpusContext1m").checked,this.saveCaseSettings(e,t);const s=document.getElementById("caseAgentTeamsMobile");s&&(s.checked=t.agentTeams);const a=document.getElementById("caseOpusContext1mMobile");a&&(a.checked=t.opusContext1m)},toggleCaseSettingsMobile(){const e=document.getElementById("caseSettingsPopoverMobile");if(e.classList.contains("hidden")){const t=document.getElementById("quickStartCase").value||"testcase",s=this.getCaseSettings(t);document.getElementById("caseAgentTeamsMobile").checked=s.agentTeams,document.getElementById("caseOpusContext1mMobile").checked=s.opusContext1m,e.classList.remove("hidden");const a=o=>{!e.contains(o.target)&&!o.target.classList.contains("btn-case-settings-mobile")&&(e.classList.add("hidden"),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),0)}else e.classList.add("hidden")},onCaseSettingChangedMobile(){const e=document.getElementById("quickStartCase").value||"testcase",t=this.getCaseSettings(e);t.agentTeams=document.getElementById("caseAgentTeamsMobile").checked,t.opusContext1m=document.getElementById("caseOpusContext1mMobile").checked,this.saveCaseSettings(e,t);const s=document.getElementById("caseAgentTeams");s&&(s.checked=t.agentTeams);const a=document.getElementById("caseOpusContext1m");a&&(a.checked=t.opusContext1m)},showCreateCaseModal(){document.getElementById("newCaseName").value="",document.getElementById("newCaseDescription").value="",document.getElementById("linkCaseName").value="",document.getElementById("linkCasePath").value="",["remoteCaseName","remoteCasePath","remoteHostId","remoteHostAddress","remoteHostUsername","remoteHostPort","remoteHostCodexCommand","remoteHostIdentityFile","remoteHostSocksProxy","remoteHostJumpHost","remoteHostExtraSshOptions"].forEach(s=>{const a=document.getElementById(s);a&&(a.value="")}),this.caseModalTab="case-create",this.switchCaseModalTab("case-create");const t=document.getElementById("createCaseModal");t.querySelectorAll(".modal-tabs .modal-tab-btn").forEach(s=>{s.onclick=()=>this.switchCaseModalTab(s.dataset.tab)}),t.querySelectorAll('input[type="text"]').forEach(s=>{s._mobileScrollWired||(s._mobileScrollWired=!0,s.addEventListener("focus",()=>{window.innerWidth<=430&&setTimeout(()=>s.scrollIntoView({behavior:"smooth",block:"center"}),300)}))}),t.classList.add("active"),document.getElementById("newCaseName").focus()},switchCaseModalTab(e){this.caseModalTab=e;const t=document.getElementById("createCaseModal");t.querySelectorAll(".modal-tabs .modal-tab-btn").forEach(a=>{a.classList.toggle("active",a.dataset.tab===e)}),t.querySelectorAll(".modal-tab-content").forEach(a=>{a.classList.toggle("hidden",a.id!==e)});const s=document.getElementById("caseModalSubmit");e==="case-manage"?(s.style.display="none",this.renderCaseManageList(),this.refreshDockerExports()):(s.style.display="",s.textContent=e==="case-create"?"Create":e==="case-remote"?"Link Remote":e==="case-docker"?"Link Docker":"Link"),e==="case-create"?document.getElementById("newCaseName").focus():e==="case-link"?document.getElementById("linkCaseName").focus():e==="case-remote"?document.getElementById("remoteCaseName").focus():e==="case-docker"&&document.getElementById("dockerCaseName").focus()},closeCreateCaseModal(){document.getElementById("createCaseModal").classList.remove("active")},async submitCaseModal(){const e=document.getElementById("caseModalSubmit"),t=e.textContent;e.classList.add("loading"),e.textContent=this.caseModalTab==="case-create"?"Creating...":"Linking...";try{this.caseModalTab==="case-create"?await this.createCase():this.caseModalTab==="case-remote"?await this.linkRemoteCase():this.caseModalTab==="case-docker"?await this.linkDockerCase():await this.linkCase()}finally{e.classList.remove("loading"),e.textContent=t}},async createCase(){const e=document.getElementById("newCaseName").value.trim(),t=document.getElementById("newCaseDescription").value.trim();if(!e){this.showToast("Please enter a case name","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}const s=document.getElementById("newCaseDocker")?.checked,a=s?"/api/cases/docker-quickcreate":"/api/cases",o=s?{name:e,description:t,...this._collectDockerQuickSettings()}:{name:e,description:t};try{const i=await(await fetch(a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)})).json();if(i.success)if(this.closeCreateCaseModal(),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e),s){const r=i.data?.capsEnforced===!1?" (resource caps advisory on this engine)":"";this.showToast(`Docker case "${e}" created${r} \u2014 starting session\u2026`,"success"),await this.runClaude()}else this.showToast(`Case "${e}" created`,"success");else this.showToast(i.error||"Failed to create case","error")}catch(n){console.error("Failed to create case:",n),this.showToast("Failed to create case: "+n.message,"error")}},applyDockerTemplate(){const e=document.getElementById("quickDockerTemplate")?.value,s={small:{m:"2g",c:"1",g:""},medium:{m:"",c:"",g:""},large:{m:"8g",c:"4",g:""},gpu:{m:"8g",c:"4",g:"all"}}[e];if(!s)return;const a=(o,n)=>{const i=document.getElementById(o);i&&(i.value=n)};a("quickDockerMemory",s.m),a("quickDockerCpus",s.c),a("quickDockerGpus",s.g)},_collectDockerQuickSettings(){const e=c=>(document.getElementById(c)?.value||"").trim(),t={},s=e("quickDockerMemory");s&&(t.memory=s);const a=e("quickDockerCpus");a&&(t.cpus=a);const o=e("quickDockerGpus");o&&o.toLowerCase()!=="none"&&(t.gpus=o);const n=document.getElementById("quickDockerNetwork")?.value;n&&n!=="bridge"&&(t.network=n);const i=e("quickDockerImage");i&&(t.image=i);const r=document.getElementById("quickDockerMountCreds");return r&&!r.checked&&(t.mountCredentials=!1),t},async linkCase(){const e=document.getElementById("linkCaseName").value.trim(),t=document.getElementById("linkCasePath").value.trim();if(!e){this.showToast("Please enter a case name","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}if(!t){this.showToast("Please enter a folder path","error");return}try{const a=await(await fetch("/api/cases/link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,path:t})})).json();a.success?(this.closeCreateCaseModal(),this.showToast(`Case "${e}" linked to ${t}`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(a.error||"Failed to link case","error")}catch(s){console.error("Failed to link case:",s),this.showToast("Failed to link case: "+s.message,"error")}},async linkRemoteCase(){const e=document.getElementById("remoteCaseName").value.trim(),t=document.getElementById("remoteCasePath").value.trim(),s=document.getElementById("remoteHostId").value.trim(),a=document.getElementById("remoteHostAddress").value.trim(),o=document.getElementById("remoteHostUsername").value.trim(),n=document.getElementById("remoteHostCodexCommand").value.trim(),i=document.getElementById("remoteHostPort").value.trim(),r=document.getElementById("remoteHostIdentityFile").value.trim(),c=document.getElementById("remoteHostSocksProxy").value.trim(),d=document.getElementById("remoteHostJumpHost").value.trim(),l=document.getElementById("remoteHostExtraSshOptions").value.split(`
16
+ `).map(p=>p.trim()).filter(p=>p.length>0);if(!e||!t||!s||!a||!o){this.showToast("Please complete all required remote fields","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)||!/^[a-zA-Z0-9_-]+$/.test(s)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}if(!t.startsWith("/")){this.showToast("Remote path must be absolute","error");return}let u;if(i&&(u=Number(i),!Number.isInteger(u)||u<1||u>65535)){this.showToast("SSH port must be a number between 1 and 65535","error");return}try{const p={id:s,label:s,host:a,username:o,...u?{port:u}:{},...r?{identityFile:r}:{},...c?{socksProxy:c}:{},...d?{jumpHost:d}:{},...l.length?{extraSshOptions:l}:{},...n?{commands:{codex:n}}:{}},h=await(await fetch("/api/remote-hosts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(p)})).json();if(!h.success&&h.errorCode!=="ALREADY_EXISTS")throw new Error(h.error||"Failed to save remote host");const g=await(await fetch("/api/cases/remote-link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,hostId:s,remotePath:t})})).json();g.success?(this.closeCreateCaseModal(),this.showToast(`Remote case "${e}" linked`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(g.error||"Failed to link remote case","error")}catch(p){console.error("Failed to link remote case:",p),this.showToast("Failed to link remote case: "+p.message,"error")}},async linkDockerCase(){const e=document.getElementById("dockerCaseName").value.trim(),t=document.getElementById("dockerWorkspacePath").value.trim(),s=document.getElementById("dockerHostId").value.trim()||"local",a=document.getElementById("dockerImage").value.trim()||"codeman/agent:base",o=document.getElementById("dockerNetwork").value,n=document.getElementById("dockerMemory").value.trim(),i=document.getElementById("dockerCpus").value.trim(),r=document.getElementById("dockerMountCredentials").checked,c=document.getElementById("dockerResumeOnStart").checked,d=document.getElementById("dockerLinkStatus");if(!e||!t){this.showToast("Please enter a case name and workspace path","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)||!/^[a-zA-Z0-9_-]+$/.test(s)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}if(!t.startsWith("/")){this.showToast("Workspace path must be absolute","error");return}try{d&&(d.textContent="Checking docker daemon + base image...");const l={};n&&(l.memory=n),i&&(l.cpus=i);const u={id:s,label:s,image:a,network:o,mountCredentials:r,resumeOnStart:c,...Object.keys(l).length?{resources:l}:{}};let p=await fetch("/api/docker-hosts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(u)}),m=await p.json();if(!m.success&&m.errorCode==="ALREADY_EXISTS"&&(p=await fetch(`/api/docker-hosts/${encodeURIComponent(s)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(u)}),m=await p.json()),!m.success)throw new Error(m.error||"Failed to save docker host");const y=await(await fetch("/api/cases/docker-link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,hostId:s,hostWorkspacePath:t})})).json();if(y.success){this.closeCreateCaseModal();const g=y.data?.capsEnforced===!1?" (resource caps are advisory on this engine)":"";this.showToast(`Docker case "${e}" linked${g}`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)}else d&&(d.textContent=y.error||"Failed to link docker case"),this.showToast(y.error||"Failed to link docker case","error")}catch(l){console.error("Failed to link docker case:",l),d&&(d.textContent=l.message),this.showToast("Failed to link docker case: "+l.message,"error")}},async refreshDockerExports(){const e=document.getElementById("dockerExportsList");if(e)try{const a=(await(await fetch("/api/docker-exports")).json())?.data?.exports||[];if(a.length===0){e.innerHTML='<span class="form-hint">No exports yet. Export a docker case from its tab.</span>';return}e.innerHTML=a.map(o=>{const n=(o.sizeBytes/1e6).toFixed(1),i=escapeHtml(o.name);return`<div class="case-manage-item" style="display:flex; align-items:center; gap:8px; justify-content:space-between;">
17
+ <span style="overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="${i}">${i} <span class="form-hint">(${n} MB)</span></span>
18
+ <span style="flex-shrink:0;">
19
+ <a class="btn-toolbar" href="/api/docker-exports/${encodeURIComponent(o.name)}" download>Download</a>
20
+ <button class="btn-toolbar" onclick="app.importDockerBundle('${i.replace(/'/g,"\\'")}')">Import</button>
21
+ <button class="btn-toolbar" onclick="app.deleteDockerExport('${i.replace(/'/g,"\\'")}')">Delete</button>
22
+ </span>
23
+ </div>`}).join("")}catch(t){e.innerHTML=`<span class="form-hint">Failed to load exports: ${t.message}</span>`}},async exportDockerCaseBundle(e,t="full"){try{const a=await(await fetch(`/api/docker-cases/${encodeURIComponent(e)}/export`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({mode:t})})).json();a.success?this.showToast(`Exporting "${e}" (${t})... you'll be notified when the bundle is ready`,"info"):this.showToast(a.error||"Export failed","error")}catch(s){this.showToast("Export failed: "+s.message,"error")}},async importDockerBundle(e){const t=prompt("New case name for the imported bundle:",e.split("-")[0]+"-imported");if(!t)return;const s=prompt("Absolute host directory to restore the workspace into:","");if(s)try{const o=await(await fetch("/api/docker-cases/import",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({bundle:e,newCaseName:t,destWorkspacePath:s})})).json();o.success?(this.showToast(`Imported as "${t}"`,"success"),await this.loadQuickStartCases(t)):this.showToast(o.error||"Import failed","error")}catch(a){this.showToast("Import failed: "+a.message,"error")}},async deleteDockerExport(e){if(confirm(`Delete export bundle "${e}"?`))try{const s=await(await fetch(`/api/docker-exports/${encodeURIComponent(e)}`,{method:"DELETE"})).json();s.success?(this.showToast("Export deleted","success"),this.refreshDockerExports()):this.showToast(s.error||"Delete failed","error")}catch(t){this.showToast("Delete failed: "+t.message,"error")}},_readRemoteHostFromForm(){const e=document.getElementById("remoteHostId").value.trim(),t=document.getElementById("remoteHostAddress").value.trim(),s=document.getElementById("remoteHostUsername").value.trim(),a=document.getElementById("remoteHostPort").value.trim(),o=document.getElementById("remoteHostIdentityFile").value.trim(),n=document.getElementById("remoteHostSocksProxy").value.trim(),i=document.getElementById("remoteHostJumpHost").value.trim(),r=document.getElementById("remoteHostCodexCommand").value.trim(),c=document.getElementById("remoteHostExtraSshOptions").value.split(`
24
+ `).map(l=>l.trim()).filter(l=>l.length>0);let d;if(a){const l=Number(a);Number.isInteger(l)&&l>=1&&l<=65535&&(d=l)}return{id:e,label:e,host:t,username:s,...d?{port:d}:{},...o?{identityFile:o}:{},...n?{socksProxy:n}:{},...i?{jumpHost:i}:{},...c.length?{extraSshOptions:c}:{},...r?{commands:{codex:r}}:{}}},async discoverRemoteSessions(){const e=document.getElementById("remoteDiscoverResults"),t=document.getElementById("remoteDiscoverBtn"),s=this._readRemoteHostFromForm();if(!s.id||!s.host||!s.username){this.showToast("Fill in Host ID, address, and username first","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(s.id)){this.showToast("Invalid Host ID. Use letters, numbers, hyphens, underscores.","error");return}t&&(t.disabled=!0),e&&(e.innerHTML='<div class="form-hint">Discovering\u2026</div>');try{const o=await(await fetch("/api/remote-hosts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})).json();if(!o.success&&o.errorCode!=="ALREADY_EXISTS")throw new Error(o.error||"Failed to save remote host");const i=await(await fetch(`/api/remote-hosts/${encodeURIComponent(s.id)}/sessions`)).json();if(!i.success)throw new Error(i.error||"Discovery failed");this._renderDiscoveredSessions(s.id,i.data.sessions||[])}catch(a){console.error("Discover remote sessions failed:",a),e&&(e.innerHTML=`<div class="form-hint" style="color: var(--error, #e06c75);">${escapeHtml(a.message)}</div>`)}finally{t&&(t.disabled=!1)}},_renderDiscoveredSessions(e,t){const s=document.getElementById("remoteDiscoverResults");if(!s)return;if(!t.length){s.innerHTML='<div class="form-hint">No <code>codeman-*</code> sessions running on this host (or it is unreachable).</div>';return}const a=Math.floor(Date.now()/1e3),o=t.map(n=>{const i=Math.max(0,a-(n.created||0)),r=i<3600?`${Math.floor(i/60)}m`:i<86400?`${Math.floor(i/3600)}h`:`${Math.floor(i/86400)}d`,c=n.attachedClients!=null?n.attachedClients:n.attached?1:0,d=c>1?`<span class="case-location-badge" style="background: var(--warning, #e5c07b); color: #000;">shared \xB7 ${c} clients</span>`:c===1?'<span class="case-location-badge" style="background: var(--accent, #61afef);">attached</span>':"";return`
25
+ <div class="remote-discover-item">
26
+ <div class="remote-discover-info">
27
+ <span class="remote-discover-name">${escapeHtml(n.name)} ${d}</span>
28
+ <span class="form-hint">age ${r} \xB7 ${n.windows||1} window(s)</span>
29
+ </div>
30
+ <button type="button" class="btn-toolbar" onclick="app.attachDiscoveredSession('${escapeHtml(e)}', '${escapeHtml(n.name)}')">Attach</button>
31
+ </div>`}).join("");s.innerHTML=o},async attachDiscoveredSession(e,t){try{const a=await(await fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({mode:"shell",name:t,attachRemoteSession:{hostId:e,remoteSessionName:t}})})).json();if(!a.success)throw new Error(a.error||"Failed to create session");const o=a.data.session.id;await fetch(`/api/sessions/${o}/shell`,{method:"POST"});const n=this.getTerminalDimensions();n&&await fetch(`/api/sessions/${o}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)}),this.closeCreateCaseModal(),this.showToast(`Attached to ${t} (detach on close)`,"success"),this.activeSessionId=o,await this.selectSession(o),this.terminal&&typeof this.terminal.focus=="function"&&this.terminal.focus()}catch(s){console.error("Attach discovered session failed:",s),this.showToast("Failed to attach: "+s.message,"error")}},renderCaseManageList(){const e=document.getElementById("caseManageList"),t=this.cases||[];if(t.length===0){e.innerHTML='<div class="form-hint" style="text-align: center; padding: 2rem 0;">No cases yet</div>';return}let s="";t.forEach((a,o)=>{const n=o===0,i=o===t.length-1,r=a.path?a.path.replace(/^\/Users\/[^/]+/,"~"):"";s+=`
32
+ <div class="case-manage-item" data-case="${escapeHtml(a.name)}">
33
+ <div class="case-manage-info">
34
+ <span class="case-manage-name">${escapeHtml(a.name)}</span>
35
+ <span class="case-manage-path">${escapeHtml(r)}</span>
36
+ </div>
37
+ <div class="case-manage-actions">
38
+ ${a.location==="docker"?`<button class="case-manage-btn" onclick="app.exportDockerCaseBundle(${escapeHtml(JSON.stringify(a.name))}, 'full')"
39
+ title="Export container (full image + workspace) to move to another machine">&#x1F4E6;</button>`:""}
40
+ <button class="case-manage-btn" onclick="app.moveCaseUp(${escapeHtml(JSON.stringify(a.name))})"
41
+ title="Move up" ${n?"disabled":""}>&#x25B2;</button>
42
+ <button class="case-manage-btn" onclick="app.moveCaseDown(${escapeHtml(JSON.stringify(a.name))})"
43
+ title="Move down" ${i?"disabled":""}>&#x25BC;</button>
44
+ <button class="case-manage-btn case-manage-btn-delete" onclick="app.deleteCase(${escapeHtml(JSON.stringify(a.name))})"
45
+ title="Delete case">&#x2715;</button>
46
+ </div>
47
+ </div>
48
+ `}),e.innerHTML=s},async moveCaseUp(e){const t=this.cases||[],s=t.findIndex(o=>o.name===e);if(s<=0)return;const a=[...t];[a[s-1],a[s]]=[a[s],a[s-1]],this.cases=a,this.renderCaseManageList(),await this.saveCaseOrder(a.map(o=>o.name))},async moveCaseDown(e){const t=this.cases||[],s=t.findIndex(o=>o.name===e);if(s<0||s>=t.length-1)return;const a=[...t];[a[s],a[s+1]]=[a[s+1],a[s]],this.cases=a,this.renderCaseManageList(),await this.saveCaseOrder(a.map(o=>o.name))},async deleteCase(e){if(confirm(`Delete case "${e}"? Linked cases will only be unlinked (folder preserved). Created cases will be permanently deleted.`))try{const s=await(await fetch(`/api/cases/${encodeURIComponent(e)}`,{method:"DELETE"})).json();if(s.success){this.showToast(`Case "${e}" ${s.data?.type==="unlinked"?"unlinked":"deleted"}`,"success"),this.cases=(this.cases||[]).filter(n=>n.name!==e),this.renderCaseManageList();const a=document.getElementById("quickStartCase"),o=a.value;o===e&&a.blur?.(),await this.loadQuickStartCases(o===e?null:o),o===e&&await this.saveLastUsedCase(document.getElementById("quickStartCase")?.value||"testcase")}else this.showToast(s.error||"Failed to delete case","error")}catch(t){this.showToast("Failed to delete case: "+t.message,"error")}},async saveCaseOrder(e){try{await fetch("/api/cases/order",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({order:e})});const s=document.getElementById("quickStartCase").value;await this.loadQuickStartCases(s)}catch(t){this.showToast("Failed to save case order: "+t.message,"error")}},showMobileCasePicker(){const e=document.getElementById("mobileCasePickerModal"),t=document.getElementById("mobileCaseList"),a=document.getElementById("quickStartCase").value;let o="";const n=this.getCasePickerOptions();for(const i of n){const r=i.name===a;o+=`
49
+ <button class="mobile-case-item ${r?"selected":""}"
50
+ onclick="app.selectMobileCase(${escapeHtml(JSON.stringify(i.name))})">
51
+ <span class="mobile-case-item-icon">
52
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
53
+ <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
54
+ </svg>
55
+ </span>
56
+ <span class="mobile-case-item-name">${escapeHtml(i.label)}</span>
57
+ <span class="mobile-case-item-delete" onclick="event.stopPropagation(); app.deleteCaseMobile(${escapeHtml(JSON.stringify(i.name))})" title="Delete">
58
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
59
+ <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
60
+ </svg>
61
+ </span>
62
+ <span class="mobile-case-item-check">
63
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
64
+ <polyline points="20 6 9 17 4 12"/>
65
+ </svg>
66
+ </span>
67
+ </button>
68
+ `}t.innerHTML=o,e.classList.add("active")},closeMobileCasePicker(){document.getElementById("mobileCasePickerModal").classList.remove("active")},selectMobileCase(e){const t=document.getElementById("quickStartCase");t.value=e,this.updateMobileCaseLabel(e),this.updateDirDisplayForCase(e),this.saveLastUsedCase(e),this.closeMobileCasePicker(),this.showToast(`Selected: ${e}`,"success")},updateMobileCaseLabel(e){const t=document.getElementById("mobileCaseName");t&&(t.textContent=e)},async deleteCaseMobile(e){if(confirm(`Delete case "${e}"?`))try{const s=await(await fetch(`/api/cases/${encodeURIComponent(e)}`,{method:"DELETE"})).json();s.success?(this.showToast(`Case "${e}" ${s.data?.type==="unlinked"?"unlinked":"deleted"}`,"success"),this.cases=(this.cases||[]).filter(a=>a.name!==e),this.closeMobileCasePicker(),await this.loadQuickStartCases()):this.showToast(s.error||"Failed to delete case","error")}catch(t){this.showToast("Failed to delete case: "+t.message,"error")}},showCreateCaseFromMobile(){this.closeMobileCasePicker(),this.showCreateCaseModal();const e=document.getElementById("createCaseModal");e.classList.add("from-mobile"),setTimeout(()=>e.classList.remove("from-mobile"),300)}}),Object.defineProperty(CodemanApp.prototype,"runMode",{configurable:!0,enumerable:!0,get(){return this._runMode||"claude"},set(e){this._runMode=e==="opencode"||e==="codex"||e==="gemini"||e==="claude"?e:"claude"}});