aicodeman 0.9.8 → 0.9.9

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 (92) hide show
  1. package/dist/mux-interface.d.ts +3 -1
  2. package/dist/mux-interface.d.ts.map +1 -1
  3. package/dist/session.d.ts +10 -1
  4. package/dist/session.d.ts.map +1 -1
  5. package/dist/session.js +27 -6
  6. package/dist/session.js.map +1 -1
  7. package/dist/tmux-manager.d.ts +14 -1
  8. package/dist/tmux-manager.d.ts.map +1 -1
  9. package/dist/tmux-manager.js +74 -4
  10. package/dist/tmux-manager.js.map +1 -1
  11. package/dist/types/session.d.ts +17 -2
  12. package/dist/types/session.d.ts.map +1 -1
  13. package/dist/types/session.js +1 -1
  14. package/dist/types/session.js.map +1 -1
  15. package/dist/utils/codex-cli-resolver.d.ts +21 -0
  16. package/dist/utils/codex-cli-resolver.d.ts.map +1 -0
  17. package/dist/utils/codex-cli-resolver.js +64 -0
  18. package/dist/utils/codex-cli-resolver.js.map +1 -0
  19. package/dist/utils/index.d.ts +1 -0
  20. package/dist/utils/index.d.ts.map +1 -1
  21. package/dist/utils/index.js +1 -0
  22. package/dist/utils/index.js.map +1 -1
  23. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  24. package/dist/web/public/{app.a2f053a8.js → app.a8663e79.js} +2 -2
  25. package/dist/web/public/app.a8663e79.js.br +0 -0
  26. package/dist/web/public/app.a8663e79.js.gz +0 -0
  27. package/dist/web/public/constants.74211deb.js.gz +0 -0
  28. package/dist/web/public/image-input.0ea86695.js.gz +0 -0
  29. package/dist/web/public/index.html +48 -7
  30. package/dist/web/public/index.html.br +0 -0
  31. package/dist/web/public/index.html.gz +0 -0
  32. package/dist/web/public/input-cjk.b8686b5e.js.gz +0 -0
  33. package/dist/web/public/keyboard-accessory.bc753cc7.js.gz +0 -0
  34. package/dist/web/public/mobile-handlers.d54d97d6.js.gz +0 -0
  35. package/dist/web/public/mobile.959f6fe2.css.gz +0 -0
  36. package/dist/web/public/notification-manager.9c984ac2.js.gz +0 -0
  37. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  38. package/dist/web/public/panels-ui.6bb3169f.js.gz +0 -0
  39. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  40. package/dist/web/public/ralph-wizard.a6b2d36b.js.gz +0 -0
  41. package/dist/web/public/respawn-ui.2d249da9.js.gz +0 -0
  42. package/dist/web/public/session-ui.512816d8.js +36 -0
  43. package/dist/web/public/session-ui.512816d8.js.br +0 -0
  44. package/dist/web/public/session-ui.512816d8.js.gz +0 -0
  45. package/dist/web/public/settings-ui.21b009ca.js +55 -0
  46. package/dist/web/public/settings-ui.21b009ca.js.br +0 -0
  47. package/dist/web/public/settings-ui.21b009ca.js.gz +0 -0
  48. package/dist/web/public/styles.f3a0faa3.css +1 -0
  49. package/dist/web/public/styles.f3a0faa3.css.br +0 -0
  50. package/dist/web/public/styles.f3a0faa3.css.gz +0 -0
  51. package/dist/web/public/subagent-windows.a366a4ad.js.gz +0 -0
  52. package/dist/web/public/sw.js.gz +0 -0
  53. package/dist/web/public/terminal-ui.6ce91b0b.js.gz +0 -0
  54. package/dist/web/public/upload.html.gz +0 -0
  55. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  56. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  57. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  58. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  59. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  60. package/dist/web/public/vendor/xterm.css.gz +0 -0
  61. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  62. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  63. package/dist/web/routes/ralph-routes.d.ts.map +1 -1
  64. package/dist/web/routes/ralph-routes.js +4 -4
  65. package/dist/web/routes/ralph-routes.js.map +1 -1
  66. package/dist/web/routes/respawn-routes.d.ts.map +1 -1
  67. package/dist/web/routes/respawn-routes.js +10 -9
  68. package/dist/web/routes/respawn-routes.js.map +1 -1
  69. package/dist/web/routes/session-routes.d.ts.map +1 -1
  70. package/dist/web/routes/session-routes.js +27 -7
  71. package/dist/web/routes/session-routes.js.map +1 -1
  72. package/dist/web/routes/system-routes.d.ts.map +1 -1
  73. package/dist/web/routes/system-routes.js +7 -0
  74. package/dist/web/routes/system-routes.js.map +1 -1
  75. package/dist/web/schemas.d.ts +23 -0
  76. package/dist/web/schemas.d.ts.map +1 -1
  77. package/dist/web/schemas.js +31 -4
  78. package/dist/web/schemas.js.map +1 -1
  79. package/dist/web/server.js +13 -13
  80. package/dist/web/server.js.map +1 -1
  81. package/package.json +1 -1
  82. package/dist/web/public/app.a2f053a8.js.br +0 -0
  83. package/dist/web/public/app.a2f053a8.js.gz +0 -0
  84. package/dist/web/public/session-ui.7e2dbbdd.js +0 -36
  85. package/dist/web/public/session-ui.7e2dbbdd.js.br +0 -0
  86. package/dist/web/public/session-ui.7e2dbbdd.js.gz +0 -0
  87. package/dist/web/public/settings-ui.cbedc88a.js +0 -55
  88. package/dist/web/public/settings-ui.cbedc88a.js.br +0 -0
  89. package/dist/web/public/settings-ui.cbedc88a.js.gz +0 -0
  90. package/dist/web/public/styles.d978a628.css +0 -1
  91. package/dist/web/public/styles.d978a628.css.br +0 -0
  92. package/dist/web/public/styles.d978a628.css.gz +0 -0
@@ -18,4 +18,5 @@ export { assertNever } from './type-safety.js';
18
18
  export { wrapWithNice } from './nice-wrapper.js';
19
19
  export { findClaudeDir, getAugmentedPath } from './claude-cli-resolver.js';
20
20
  export { resolveOpenCodeDir } from './opencode-cli-resolver.js';
21
+ export { resolveCodexDir, isCodexAvailable } from './codex-cli-resolver.js';
21
22
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EACL,wBAAwB,EACxB,0BAA0B,EAC1B,aAAa,EACb,eAAe,EACf,SAAS,EACT,iBAAiB,EACjB,WAAW,GACZ,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC7F,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EACL,wBAAwB,EACxB,0BAA0B,EAC1B,aAAa,EACb,eAAe,EACf,SAAS,EACT,iBAAiB,EACjB,WAAW,GACZ,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC7F,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC"}
@@ -17,7 +17,7 @@
17
17
  <span class="tab-status ${r}" aria-hidden="true"></span>
18
18
  <span class="tab-info">
19
19
  <span class="tab-name-row">
20
- ${h==="shell"?'<span class="tab-mode shell" aria-hidden="true">sh</span>':h==="opencode"?'<span class="tab-mode opencode" aria-hidden="true">oc</span>':""}
20
+ ${h==="shell"?'<span class="tab-mode shell" aria-hidden="true">sh</span>':h==="opencode"?'<span class="tab-mode opencode" aria-hidden="true">oc</span>':h==="codex"?'<span class="tab-mode codex" aria-hidden="true">cx</span>':""}
21
21
  <span class="tab-name" data-session-id="${n}">${(()=>{const E=parseSessionPrefix(l);return E&&E.suffix?'<span class="tab-prefix">'+escapeHtml(E.prefix)+'</span><span class="tab-suffix">: '+escapeHtml(E.suffix)+"</span>":escapeHtml(l)})()}</span>
22
22
  <span class="tab-detached-badge" aria-hidden="true">detached</span>
23
23
  </span>
@@ -30,6 +30,6 @@
30
30
  <span class="tab-close" onclick="event.stopPropagation(); app.requestCloseSession('${escapeHtml(n)}')" title="Close session" aria-label="Close session" tabindex="0">&times;</span>
31
31
  </div>`),i++}e.innerHTML=t.join(""),this.setupTabDragHandlers(),this.setupTabKeyboardNavigation(e),this.updateConnectionLines()}setupTabKeyboardNavigation(e){this._tabKeydownHandler&&e.removeEventListener("keydown",this._tabKeydownHandler),this._tabKeydownHandler=t=>{if(!["ArrowLeft","ArrowRight","Home","End","Enter"," "].includes(t.key))return;const s=[...e.querySelectorAll(".session-tab")],i=s.indexOf(document.activeElement);if((t.key==="Enter"||t.key===" ")&&i>=0){t.preventDefault();const o=s[i].dataset.id;this.selectSession(o,{forceReload:!0});return}if(i<0)return;let n;switch(t.key){case"ArrowLeft":n=i>0?i-1:s.length-1;break;case"ArrowRight":n=i<s.length-1?i+1:0;break;case"Home":n=0;break;case"End":n=s.length-1;break;default:return}t.preventDefault(),s[n]?.focus()},e.addEventListener("keydown",this._tabKeydownHandler)}handleSessionTabClick(e,t){return e?.preventDefault?.(),!(typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible===!0)&&MobileDetection.isTouchDevice()&&document.activeElement?.blur?.(),this.selectSession(t,{forceReload:!0})}syncSessionOrder(){const e=new Set(this.sessions.keys()),s=this.loadSessionOrder().filter(o=>e.has(o)),i=new Set(s),n=[...e].filter(o=>!i.has(o));this.sessionOrder=[...s,...n]}loadSessionOrder(){try{const e=localStorage.getItem("codeman-session-order");return e?JSON.parse(e):[]}catch{return[]}}saveSessionOrder(){try{localStorage.setItem("codeman-session-order",JSON.stringify(this.sessionOrder))}catch{}}setupTabDragHandlers(){const e=this.$("sessionTabs");e.querySelectorAll(".session-tab[data-id]").forEach(s=>{s.setAttribute("draggable","true"),s.addEventListener("dragstart",i=>{this.draggedTabId=s.dataset.id,s.classList.add("dragging"),i.dataTransfer.effectAllowed="move",i.dataTransfer.setData("text/plain",s.dataset.id)}),s.addEventListener("dragend",()=>{s.classList.remove("dragging"),this.draggedTabId=null,e.querySelectorAll(".session-tab").forEach(i=>{i.classList.remove("drag-over-left","drag-over-right")})}),s.addEventListener("dragover",i=>{if(i.preventDefault(),!this.draggedTabId||this.draggedTabId===s.dataset.id)return;i.dataTransfer.dropEffect="move";const n=s.getBoundingClientRect(),o=n.left+n.width/2,a=i.clientX<o;s.classList.toggle("drag-over-left",a),s.classList.toggle("drag-over-right",!a)}),s.addEventListener("dragleave",()=>{s.classList.remove("drag-over-left","drag-over-right")}),s.addEventListener("drop",i=>{if(i.preventDefault(),s.classList.remove("drag-over-left","drag-over-right"),!this.draggedTabId||this.draggedTabId===s.dataset.id)return;const n=s.dataset.id,o=this.draggedTabId,a=s.getBoundingClientRect(),r=a.left+a.width/2,l=i.clientX<r,h=this.sessionOrder.indexOf(o);let d=this.sessionOrder.indexOf(n);h===-1||d===-1||(this.sessionOrder.splice(h,1),d=this.sessionOrder.indexOf(n),d!==-1&&(l?this.sessionOrder.splice(d,0,o):this.sessionOrder.splice(d+1,0,o),this.saveSessionOrder(),this._fullRenderSessionTabs()))})})}moveActiveTabLeft(){if(!this.activeSessionId)return;const e=this.sessionOrder.indexOf(this.activeSessionId);e<=0||([this.sessionOrder[e-1],this.sessionOrder[e]]=[this.sessionOrder[e],this.sessionOrder[e-1]],this.saveSessionOrder(),this._fullRenderSessionTabs())}moveActiveTabRight(){if(!this.activeSessionId)return;const e=this.sessionOrder.indexOf(this.activeSessionId);e===-1||e>=this.sessionOrder.length-1||([this.sessionOrder[e],this.sessionOrder[e+1]]=[this.sessionOrder[e+1],this.sessionOrder[e]],this.saveSessionOrder(),this._fullRenderSessionTabs())}getShortId(e){if(!e)return"";let t=this._shortIdCache.get(e);return t||(t=e.slice(0,8),this._shortIdCache.set(e,t)),t}getSessionName(e){return e.name?e.name:e.workingDir?e.workingDir.split("/").pop()||e.workingDir:this.getShortId(e.id)}_notifySession(e,t,s,i,n){const o=this.sessions.get(e);this.notificationManager?.notify({urgency:t,category:s,sessionId:e,sessionName:o?.name||this.getShortId(e),title:i,message:n})}_cleanupPreviousSession(e){this._disconnectWs();const t=document.getElementById("cjkInput");t&&(t.value=""),this._clearTimer("flickerFilterTimeout"),this.flickerFilterBuffer="",this.flickerFilterActive=!1,this._tabCompletionSessionId=null,this._tabCompletionRetries=0,this._tabCompletionBaseText=null,this._clearTimer("_tabCompletionFallback"),this._clearTimer("_clientDropRecoveryTimer"),this._clearTimer("syncWaitTimeout"),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._bufferLoadOwner=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1;try{const s=this.terminal?._core?._compositionHelper;if(s?._isComposing){s._isComposing=!1;const i=this.terminal?.element?.querySelector(".xterm-helper-textarea");i&&i.dispatchEvent(new CompositionEvent("compositionend",{data:""}))}}catch{}if(this.activeSessionId){const s=this._localEchoOverlay?.pendingText||"",i=this._localEchoOverlay?.getFlushed()?.count||0,n=this._localEchoOverlay?.getFlushed()?.text||"";s&&this._sendInputAsync(this.activeSessionId,s);const o=i+s.length;o>0&&(this._flushedOffsets||(this._flushedOffsets=new Map),this._flushedTexts||(this._flushedTexts=new Map),this._flushedOffsets.set(this.activeSessionId,o),this._flushedTexts.set(this.activeSessionId,n+s))}this._localEchoOverlay?.clear(),this._localEchoOverlay&&!this._flushedOffsets?.has(e)&&this._localEchoOverlay.suppressBufferDetection()}_resetTerminalForReplay(){this.terminal.reset(),this.terminal.write("\x1B[3J\x1B[H\x1B[2J")}_shouldFocusTerminalForTabSwitch(){return typeof MobileDetection>"u"||!MobileDetection.isTouchDevice()?!0:typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible}async selectSession(e,t={}){if(!this.isSoloWindow&&this.detachedSessions.has(e)&&this._raiseDetached(e))return;const s=t?.forceReload===!0;if(this.activeSessionId===e&&!s)return;this.activeSessionId===e&&s&&(this.terminalBufferCache?.delete(e),this._clearTimer("syncWaitTimeout"),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1,this.activeSessionId=null);const i=this._shouldFocusTerminalForTabSwitch();i&&this.terminal&&this.terminal.focus();const n=performance.now(),o=this.sessions.get(e)?.name||e.slice(0,8);_crashDiag.log(`SELECT: ${o}`),console.log(`[CRASH-DIAG] selectSession START: ${e.slice(0,8)}`);const a=++this._selectGeneration;if(this._setTerminalLoadState(e,a,"resizing"),a!==this._selectGeneration){this._clearTerminalLoadState(e,a);return}this._cleanupPreviousSession(e),this.activeSessionId=e;try{localStorage.setItem("codeman-active-session",e)}catch{}this._updateSseSubscription(e),this.hideWelcome(),this.clearPendingHooks(e,"idle_prompt"),this._updateActiveTabImmediate(e),this.renderSessionTabs(),this._updateLocalEchoState(),this._flushedOffsets?.has(e)&&this._localEchoOverlay&&this._localEchoOverlay.setFlushed(this._flushedOffsets.get(e),this._flushedTexts?.get(e)||"",!1);const r=document.querySelector(`.session-tab.active[data-id="${e}"]`);r&&(r.classList.add("tab-glow"),r.addEventListener("animationend",()=>r.classList.remove("tab-glow"),{once:!0}));const l=this.sessions.get(e);if(this.currentSessionWorkingDir=l?.workingDir||null,l&&l.pid===null)try{const d=l.mode==="shell"?`/api/sessions/${e}/shell`:`/api/sessions/${e}/interactive`;await fetch(d,{method:"POST"}),l.status="busy"}catch(d){console.error("Failed to attach to restored session:",d)}this._restoringFlushedState=!0;const h=this._beginBufferLoad(a);try{this.fitAddon&&this.fitAddon.fit();const d=await this.sendResize(e,{forceHttp:!0}).catch(()=>!1);if(this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}const p=l&&(l.status==="busy"||l.status==="working"),f=this.terminalBufferCache.get(e);if(f&&!p){if(_crashDiag.log(`CACHE_WRITE: ${(f.length/1024).toFixed(0)}KB`),this._setTerminalLoadState(e,a,"replaying"),this._resetTerminalForReplay(),await this.chunkedTerminalWrite(f,TERMINAL_CHUNK_SIZE,h),this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}this.terminal.scrollToBottom(),_crashDiag.log("CACHE_DONE")}else p&&(this._resetTerminalForReplay(),_crashDiag.log("CACHE_SKIP_BUSY"));if(l?.mode!=="shell"&&d&&(await new Promise(m=>setTimeout(m,TUI_REDRAW_SETTLE_MS)),this._isStaleSelect(a))){this._clearTerminalLoadState(e,a);return}this._setTerminalLoadState(e,a,"fetching"),_crashDiag.log("FETCH_START");const g=await fetch(`/api/sessions/${e}/terminal?tail=${TERMINAL_TAIL_SIZE}`);if(this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}const _=(await g.json())?.data??{};if(_crashDiag.log(`FETCH_DONE: ${_.terminalBuffer?(_.terminalBuffer.length/1024).toFixed(0)+"KB":"empty"} truncated=${_.truncated}`),_.terminalBuffer){if(p||_.terminalBuffer!==f){if(_crashDiag.log(`REWRITE: ${(_.terminalBuffer.length/1024).toFixed(0)}KB`),this._setTerminalLoadState(e,a,"replaying"),this._resetTerminalForReplay(),_.truncated&&this.terminal.write(`\x1B[90m... (earlier output truncated for performance) ...\x1B[0m\r
32
32
  \r
33
- `),await this.chunkedTerminalWrite(_.terminalBuffer,TERMINAL_CHUNK_SIZE,h),this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}this.terminal.scrollToBottom()}if(this.terminalBufferCache.set(e,_.terminalBuffer),this.terminalBufferCache.size>20){const b=this.terminalBufferCache.keys().next().value;this.terminalBufferCache.delete(b)}}else f||this._resetTerminalForReplay();if(this._isLoadingBuffer&&this._finishBufferLoad(h),this._restoringFlushedState=!1,this._flushedOffsets?.has(e)&&this._localEchoOverlay){this._localEchoOverlay.setFlushed(this._flushedOffsets.get(e),this._flushedTexts?.get(e)||"",!1);const m=this._localEchoOverlay;this.terminal.write("",()=>{m.hasPending&&m.rerender()})}this.sendResize(e),(typeof requestIdleCallback=="function"?requestIdleCallback:m=>setTimeout(m,16))(()=>{if(a!==this._selectGeneration)return;this.respawnStatus[e]?(this.showRespawnBanner(),this.updateRespawnBanner(this.respawnStatus[e].state),document.getElementById("respawnCycleCount").textContent=this.respawnStatus[e].cycleCount||0,this.updateCountdownTimerDisplay(),this.updateActionLogDisplay(),Object.keys(this.respawnCountdownTimers[e]||{}).length>0&&this.startCountdownInterval()):(this.hideRespawnBanner(),this.stopCountdownInterval());const m=document.getElementById("taskPanel");m&&m.classList.contains("open")&&this.renderTaskPanel();const b=this.sessions.get(e);if(b&&(b.ralphLoop||b.ralphTodos)&&this.updateRalphState(e,{loop:b.ralphLoop,todos:b.ralphTodos}),this.renderRalphStatePanel(),this.updateCliInfoDisplay(),this.renderProjectInsightsPanel(),this.updateSubagentWindowVisibility(),this.loadAppSettingsFromStorage().showFileBrowser){const S=this.$("fileBrowserPanel");if(S&&(S.classList.add("visible"),this.loadFileBrowser(e),!this.fileBrowserDragListeners)){const w=S.querySelector(".file-browser-header");if(w){const v=()=>{if(!S.style.left){const E=S.getBoundingClientRect();S.style.left=`${E.left}px`,S.style.top=`${E.top}px`,S.style.right="auto"}};w.addEventListener("mousedown",v),w.addEventListener("touchstart",v,{passive:!0}),this.fileBrowserDragListeners=this.makeWindowDraggable(S,w),this.fileBrowserDragListeners._onFirstDrag=v}}}}),this._connectWs(e),_crashDiag.log("FOCUS"),i&&this.terminal&&this.terminal.focus(),this.scrollToLastNonEmptyLine(),this._clearTerminalLoadState(e,a),_crashDiag.log(`SELECT_DONE: ${(performance.now()-n).toFixed(0)}ms`),console.log(`[CRASH-DIAG] selectSession DONE: ${e.slice(0,8)} in ${(performance.now()-n).toFixed(0)}ms`)}catch(d){this._isLoadingBuffer&&this._finishBufferLoad(h),this._restoringFlushedState=!1,this._setTerminalLoadState(e,a,"failed"),console.error("Failed to load session terminal:",d)}}_cleanupSessionData(e){this._activeRename?.sessionId===e&&this._activeRename.cancel(),this.sessions.delete(e);const t=this.sessionOrder.indexOf(e);t!==-1&&(this.sessionOrder.splice(t,1),this.saveSessionOrder()),this.terminalBuffers.delete(e),this.terminalBufferCache.delete(e),this._flushedOffsets?.delete(e),this._flushedTexts?.delete(e),this._inputQueue.delete(e),this.ralphStates.delete(e),this.ralphClosedSessions.delete(e),this.projectInsights.delete(e),this.pendingHooks.delete(e),this.tabAlerts.delete(e),this.terminalLoadStates.delete(e),this.clearCountdownTimers(e),this.closeSessionLogViewerWindows(e),this.closeSessionImagePopups(e),this.closeSessionSubagentWindows(e,!0);const s=this.idleTimers.get(e);s&&(clearTimeout(s),this.idleTimers.delete(e)),delete this.respawnStatus[e],delete this.respawnTimers[e],delete this.respawnCountdownTimers[e],delete this.respawnActionLogs[e]}async closeSession(e,t=!0){try{if(await this._apiDelete(`/api/sessions/${e}?killMux=${t}`),this._cleanupSessionData(e),this.activeSessionId===e){this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}if(this.sessionOrder.length>0&&this.sessions.size>0){const s=this.sessionOrder[0];this.selectSession(s)}else this.terminal.clear(),this.showWelcome(),this.renderRalphStatePanel()}this.renderSessionTabs(),t?this.showToast("Session closed and tmux killed","success"):this.showToast("Tab hidden, tmux still running","info")}catch{this.showToast("Failed to close session","error")}}requestCloseSession(e){const t=this.sessions.get(e);if(!t)return;this.pendingCloseSessionId=e;const s=this.getSessionName(t),i=document.getElementById("closeConfirmSessionName");i.textContent=s;const n=document.getElementById("closeConfirmKillTitle");n&&(n.textContent=t.mode==="opencode"?"Kill Tmux & OpenCode":"Kill Tmux & Claude Code"),document.getElementById("closeConfirmModal").classList.add("active")}cancelCloseSession(){this.pendingCloseSessionId=null,document.getElementById("closeConfirmModal").classList.remove("active")}async confirmCloseSession(e=!0){const t=this.pendingCloseSessionId;this.cancelCloseSession(),t&&await this.closeSession(t,e)}nextSession(){if(this.sessionOrder.length<=1)return;const t=(this.sessionOrder.indexOf(this.activeSessionId)+1)%this.sessionOrder.length;this.selectSession(this.sessionOrder[t])}prevSession(){if(this.sessionOrder.length<=1)return;const t=(this.sessionOrder.indexOf(this.activeSessionId)-1+this.sessionOrder.length)%this.sessionOrder.length;this.selectSession(this.sessionOrder[t])}goHome(){this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.terminal.clear(),this.showWelcome(),this.renderSessionTabs(),this.renderRalphStatePanel()}ralphWizardStep=1;ralphWizardConfig={taskDescription:"",completionPhrase:"COMPLETE",maxIterations:10,caseName:"testcase",enableRespawn:!1,generatedPlan:null,planGenerated:!1,skipPlanGeneration:!1,planDetailLevel:"detailed",existingPlan:null,useExistingPlan:!1};planLoadingTimer=null;planLoadingStartTime=null;async killActiveSession(){if(!this.activeSessionId){this.showToast("No active session","warning");return}await this.closeSession(this.activeSessionId)}async killAllSessions(){if(this.sessions.size!==0&&confirm(`Kill all ${this.sessions.size} session(s)?`))try{await this._apiDelete("/api/sessions"),this.sessions.clear(),this.terminalBuffers.clear(),this.terminalBufferCache.clear(),this.terminalLoadStates.clear(),this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.respawnStatus={},this.respawnCountdownTimers={},this.respawnActionLogs={},this.stopCountdownInterval(),this.hideRespawnBanner(),this.renderSessionTabs(),this.terminal.clear(),this.showWelcome(),this.showToast("All sessions killed","success")}catch{this.showToast("Failed to kill sessions","error")}}showTimer(){document.getElementById("timerBanner").style.display="flex",this.updateTimer(),this.timerInterval=setInterval(()=>this.updateTimer(),1e3)}hideTimer(){document.getElementById("timerBanner").style.display="none",this.timerInterval&&(clearInterval(this.timerInterval),this.timerInterval=null)}updateTimer(){if(!this.currentRun||this.currentRun.status!=="running")return;const e=Date.now(),t=Math.max(0,this.currentRun.endAt-e),s=this.currentRun.endAt-this.currentRun.startedAt,i=e-this.currentRun.startedAt,n=Math.min(100,i/s*100);document.getElementById("timerValue").textContent=this.formatTime(t),document.getElementById("timerProgress").style.width=`${n}%`,document.getElementById("timerMeta").textContent=`${this.currentRun.completedTasks} tasks | $${this.currentRun.totalCost.toFixed(2)}`}async stopCurrentRun(){if(this.currentRun)try{await fetch(`/api/scheduled/${this.currentRun.id}`,{method:"DELETE"})}catch{this.showToast("Failed to stop run","error")}}formatTime(e){const t=Math.floor(e/1e3),s=Math.floor(t/3600),i=Math.floor(t%3600/60),n=t%60;return`${s.toString().padStart(2,"0")}:${i.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}`}updateCost(){this.updateTokens()}updateTokens(){this._clearTimer("_updateTokensTimeout"),this._updateTokensTimeout=setTimeout(()=>{this._updateTokensTimeout=null,this._updateTokensImmediate()},200)}_updateTokensImmediate(){let e=0,t=0;this.globalStats?(e=this.globalStats.totalInputTokens||0,t=this.globalStats.totalOutputTokens||0):this.sessions.forEach(a=>{a.tokens&&(e+=a.tokens.input||0,t+=a.tokens.output||0)});const s=e+t;this.totalTokens=s;const i=this.formatTokens(s),n=this.estimateCost(e,t),o=this.$("headerTokens");if(o){const r=this.loadAppSettingsFromStorage().showCost??!1;o.textContent=s>0?r?`${i} tokens \xB7 $${n.toFixed(2)}`:`${i} tokens`:"0 tokens",o.title=this.globalStats?`Lifetime: ${this.globalStats.totalSessionsCreated} sessions created${r?`
33
+ `),await this.chunkedTerminalWrite(_.terminalBuffer,TERMINAL_CHUNK_SIZE,h),this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}this.terminal.scrollToBottom()}if(this.terminalBufferCache.set(e,_.terminalBuffer),this.terminalBufferCache.size>20){const b=this.terminalBufferCache.keys().next().value;this.terminalBufferCache.delete(b)}}else f||this._resetTerminalForReplay();if(this._isLoadingBuffer&&this._finishBufferLoad(h),this._restoringFlushedState=!1,this._flushedOffsets?.has(e)&&this._localEchoOverlay){this._localEchoOverlay.setFlushed(this._flushedOffsets.get(e),this._flushedTexts?.get(e)||"",!1);const m=this._localEchoOverlay;this.terminal.write("",()=>{m.hasPending&&m.rerender()})}this.sendResize(e),(typeof requestIdleCallback=="function"?requestIdleCallback:m=>setTimeout(m,16))(()=>{if(a!==this._selectGeneration)return;this.respawnStatus[e]?(this.showRespawnBanner(),this.updateRespawnBanner(this.respawnStatus[e].state),document.getElementById("respawnCycleCount").textContent=this.respawnStatus[e].cycleCount||0,this.updateCountdownTimerDisplay(),this.updateActionLogDisplay(),Object.keys(this.respawnCountdownTimers[e]||{}).length>0&&this.startCountdownInterval()):(this.hideRespawnBanner(),this.stopCountdownInterval());const m=document.getElementById("taskPanel");m&&m.classList.contains("open")&&this.renderTaskPanel();const b=this.sessions.get(e);if(b&&(b.ralphLoop||b.ralphTodos)&&this.updateRalphState(e,{loop:b.ralphLoop,todos:b.ralphTodos}),this.renderRalphStatePanel(),this.updateCliInfoDisplay(),this.renderProjectInsightsPanel(),this.updateSubagentWindowVisibility(),this.loadAppSettingsFromStorage().showFileBrowser){const S=this.$("fileBrowserPanel");if(S&&(S.classList.add("visible"),this.loadFileBrowser(e),!this.fileBrowserDragListeners)){const w=S.querySelector(".file-browser-header");if(w){const v=()=>{if(!S.style.left){const E=S.getBoundingClientRect();S.style.left=`${E.left}px`,S.style.top=`${E.top}px`,S.style.right="auto"}};w.addEventListener("mousedown",v),w.addEventListener("touchstart",v,{passive:!0}),this.fileBrowserDragListeners=this.makeWindowDraggable(S,w),this.fileBrowserDragListeners._onFirstDrag=v}}}}),this._connectWs(e),_crashDiag.log("FOCUS"),i&&this.terminal&&this.terminal.focus(),this.scrollToLastNonEmptyLine(),this._clearTerminalLoadState(e,a),_crashDiag.log(`SELECT_DONE: ${(performance.now()-n).toFixed(0)}ms`),console.log(`[CRASH-DIAG] selectSession DONE: ${e.slice(0,8)} in ${(performance.now()-n).toFixed(0)}ms`)}catch(d){this._isLoadingBuffer&&this._finishBufferLoad(h),this._restoringFlushedState=!1,this._setTerminalLoadState(e,a,"failed"),console.error("Failed to load session terminal:",d)}}_cleanupSessionData(e){this._activeRename?.sessionId===e&&this._activeRename.cancel(),this.sessions.delete(e);const t=this.sessionOrder.indexOf(e);t!==-1&&(this.sessionOrder.splice(t,1),this.saveSessionOrder()),this.terminalBuffers.delete(e),this.terminalBufferCache.delete(e),this._flushedOffsets?.delete(e),this._flushedTexts?.delete(e),this._inputQueue.delete(e),this.ralphStates.delete(e),this.ralphClosedSessions.delete(e),this.projectInsights.delete(e),this.pendingHooks.delete(e),this.tabAlerts.delete(e),this.terminalLoadStates.delete(e),this.clearCountdownTimers(e),this.closeSessionLogViewerWindows(e),this.closeSessionImagePopups(e),this.closeSessionSubagentWindows(e,!0);const s=this.idleTimers.get(e);s&&(clearTimeout(s),this.idleTimers.delete(e)),delete this.respawnStatus[e],delete this.respawnTimers[e],delete this.respawnCountdownTimers[e],delete this.respawnActionLogs[e]}async closeSession(e,t=!0){try{if(await this._apiDelete(`/api/sessions/${e}?killMux=${t}`),this._cleanupSessionData(e),this.activeSessionId===e){this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}if(this.sessionOrder.length>0&&this.sessions.size>0){const s=this.sessionOrder[0];this.selectSession(s)}else this.terminal.clear(),this.showWelcome(),this.renderRalphStatePanel()}this.renderSessionTabs(),t?this.showToast("Session closed and tmux killed","success"):this.showToast("Tab hidden, tmux still running","info")}catch{this.showToast("Failed to close session","error")}}requestCloseSession(e){const t=this.sessions.get(e);if(!t)return;this.pendingCloseSessionId=e;const s=this.getSessionName(t),i=document.getElementById("closeConfirmSessionName");i.textContent=s;const n=document.getElementById("closeConfirmKillTitle");n&&(n.textContent=t.mode==="opencode"?"Kill Tmux & OpenCode":t.mode==="codex"?"Kill Tmux & Codex":"Kill Tmux & Claude Code"),document.getElementById("closeConfirmModal").classList.add("active")}cancelCloseSession(){this.pendingCloseSessionId=null,document.getElementById("closeConfirmModal").classList.remove("active")}async confirmCloseSession(e=!0){const t=this.pendingCloseSessionId;this.cancelCloseSession(),t&&await this.closeSession(t,e)}nextSession(){if(this.sessionOrder.length<=1)return;const t=(this.sessionOrder.indexOf(this.activeSessionId)+1)%this.sessionOrder.length;this.selectSession(this.sessionOrder[t])}prevSession(){if(this.sessionOrder.length<=1)return;const t=(this.sessionOrder.indexOf(this.activeSessionId)-1+this.sessionOrder.length)%this.sessionOrder.length;this.selectSession(this.sessionOrder[t])}goHome(){this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.terminal.clear(),this.showWelcome(),this.renderSessionTabs(),this.renderRalphStatePanel()}ralphWizardStep=1;ralphWizardConfig={taskDescription:"",completionPhrase:"COMPLETE",maxIterations:10,caseName:"testcase",enableRespawn:!1,generatedPlan:null,planGenerated:!1,skipPlanGeneration:!1,planDetailLevel:"detailed",existingPlan:null,useExistingPlan:!1};planLoadingTimer=null;planLoadingStartTime=null;async killActiveSession(){if(!this.activeSessionId){this.showToast("No active session","warning");return}await this.closeSession(this.activeSessionId)}async killAllSessions(){if(this.sessions.size!==0&&confirm(`Kill all ${this.sessions.size} session(s)?`))try{await this._apiDelete("/api/sessions"),this.sessions.clear(),this.terminalBuffers.clear(),this.terminalBufferCache.clear(),this.terminalLoadStates.clear(),this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.respawnStatus={},this.respawnCountdownTimers={},this.respawnActionLogs={},this.stopCountdownInterval(),this.hideRespawnBanner(),this.renderSessionTabs(),this.terminal.clear(),this.showWelcome(),this.showToast("All sessions killed","success")}catch{this.showToast("Failed to kill sessions","error")}}showTimer(){document.getElementById("timerBanner").style.display="flex",this.updateTimer(),this.timerInterval=setInterval(()=>this.updateTimer(),1e3)}hideTimer(){document.getElementById("timerBanner").style.display="none",this.timerInterval&&(clearInterval(this.timerInterval),this.timerInterval=null)}updateTimer(){if(!this.currentRun||this.currentRun.status!=="running")return;const e=Date.now(),t=Math.max(0,this.currentRun.endAt-e),s=this.currentRun.endAt-this.currentRun.startedAt,i=e-this.currentRun.startedAt,n=Math.min(100,i/s*100);document.getElementById("timerValue").textContent=this.formatTime(t),document.getElementById("timerProgress").style.width=`${n}%`,document.getElementById("timerMeta").textContent=`${this.currentRun.completedTasks} tasks | $${this.currentRun.totalCost.toFixed(2)}`}async stopCurrentRun(){if(this.currentRun)try{await fetch(`/api/scheduled/${this.currentRun.id}`,{method:"DELETE"})}catch{this.showToast("Failed to stop run","error")}}formatTime(e){const t=Math.floor(e/1e3),s=Math.floor(t/3600),i=Math.floor(t%3600/60),n=t%60;return`${s.toString().padStart(2,"0")}:${i.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}`}updateCost(){this.updateTokens()}updateTokens(){this._clearTimer("_updateTokensTimeout"),this._updateTokensTimeout=setTimeout(()=>{this._updateTokensTimeout=null,this._updateTokensImmediate()},200)}_updateTokensImmediate(){let e=0,t=0;this.globalStats?(e=this.globalStats.totalInputTokens||0,t=this.globalStats.totalOutputTokens||0):this.sessions.forEach(a=>{a.tokens&&(e+=a.tokens.input||0,t+=a.tokens.output||0)});const s=e+t;this.totalTokens=s;const i=this.formatTokens(s),n=this.estimateCost(e,t),o=this.$("headerTokens");if(o){const r=this.loadAppSettingsFromStorage().showCost??!1;o.textContent=s>0?r?`${i} tokens \xB7 $${n.toFixed(2)}`:`${i} tokens`:"0 tokens",o.title=this.globalStats?`Lifetime: ${this.globalStats.totalSessionsCreated} sessions created${r?`
34
34
  Estimated cost based on Claude Opus pricing`:""}`:`Token usage across active sessions${r?`
35
35
  Estimated cost based on Claude Opus pricing`:""}`}}}try{for(let c=0;c<localStorage.length;c++){const e=localStorage.key(c);if(e&&(e.startsWith("claudeman-")||e.startsWith("claudeman_"))){const t=e.replace(/^claudeman[-_]/,s=>"codeman"+s.charAt(s.length-1));localStorage.getItem(t)===null&&localStorage.setItem(t,localStorage.getItem(e))}}}catch{}let app;document.addEventListener("DOMContentLoaded",()=>{app=new CodemanApp,window.app=app}),window.MobileDetection=MobileDetection;
@@ -16,7 +16,7 @@
16
16
  <link rel="manifest" href="manifest.json">
17
17
  <title>Codeman</title>
18
18
  <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%2360a5fa'/%3E%3Cstop offset='100%25' stop-color='%233b82f6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='6' fill='%230a0a0a'/%3E%3Cpath d='M18 4L8 18h6l-2 10 10-14h-6z' fill='url(%23g)'/%3E%3C/svg%3E">
19
- <link rel="stylesheet" href="styles.d978a628.css">
19
+ <link rel="stylesheet" href="styles.f3a0faa3.css">
20
20
  <link rel="stylesheet" href="mobile.959f6fe2.css" media="(max-width: 1023px)">
21
21
  <!-- xterm.css loaded async — terminal won't display until xterm.js runs anyway -->
22
22
  <link rel="preload" href="vendor/xterm.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
@@ -25,7 +25,7 @@
25
25
  instead of waiting until <script> tags at bottom-of-body are reached. -->
26
26
  <link rel="preload" href="vendor/xterm.min.js" as="script">
27
27
  <link rel="preload" href="constants.74211deb.js" as="script">
28
- <link rel="preload" href="app.a2f053a8.js" as="script">
28
+ <link rel="preload" href="app.a8663e79.js" as="script">
29
29
  <!-- Self-hosted xterm.js — eliminates CDN DNS/TLS latency (~100ms).
30
30
  'defer' preserves execution order (xterm loads before fit addon). -->
31
31
  <script defer src="vendor/xterm.min.js"></script>
@@ -110,7 +110,7 @@
110
110
  <span class="stat-value" id="statMem">--</span>
111
111
  </div>
112
112
  </div>
113
- <button class="btn-icon-header btn-response-viewer-header" onclick="app.toggleResponseViewer()" title="View last response" aria-label="View last response"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></button>
113
+ <button class="btn-icon-header btn-response-viewer-header btn-response-viewer-header--hidden" onclick="app.toggleResponseViewer()" title="View last response" aria-label="View last response"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></button>
114
114
  <button class="btn-icon-header btn-multimonitor btn-multimonitor--hidden" onclick="app.launchMultiMonitor()" title="Open Codeman across all displays" aria-label="Open Codeman across all displays"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="4" width="13" height="9" rx="1.5"/><rect x="11" y="9" width="11" height="8" rx="1.5"/></svg></button>
115
115
  <button class="btn-icon-header btn-notifications" onclick="app.toggleNotifications()" title="Notifications" aria-label="Toggle notifications" style="display:none;">
116
116
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
@@ -384,6 +384,9 @@
384
384
  <button class="run-mode-option" data-mode="opencode" onclick="app.setRunMode('opencode')">
385
385
  <span class="run-mode-dot opencode"></span>OpenCode
386
386
  </button>
387
+ <button class="run-mode-option" data-mode="codex" onclick="app.setRunMode('codex')">
388
+ <span class="run-mode-dot codex"></span>Codex
389
+ </button>
387
390
  <div class="run-mode-sep"></div>
388
391
  <div class="run-mode-header">Recent Sessions</div>
389
392
  <div class="run-mode-history" id="runModeHistory"></div>
@@ -895,6 +898,7 @@
895
898
  <div class="modal-tabs">
896
899
  <button class="modal-tab-btn active" data-tab="settings-display">Display</button>
897
900
  <button class="modal-tab-btn" data-tab="settings-claude">Claude CLI</button>
901
+ <button class="modal-tab-btn" data-tab="settings-codex">Codex CLI</button>
898
902
  <button class="modal-tab-btn" data-tab="settings-models">Models</button>
899
903
  <button class="modal-tab-btn" data-tab="settings-paths">Paths</button>
900
904
  <button class="modal-tab-btn" data-tab="settings-notifications">Notifications</button>
@@ -985,6 +989,13 @@
985
989
  <span class="slider"></span>
986
990
  </label>
987
991
  </div>
992
+ <div class="settings-item" title="Show the response viewer (eye) button in header">
993
+ <span class="settings-item-label">Response Viewer</span>
994
+ <label class="switch switch-sm">
995
+ <input type="checkbox" id="appSettingsShowResponseViewer">
996
+ <span class="slider"></span>
997
+ </label>
998
+ </div>
988
999
  <div class="settings-item" title="Show the multi-monitor button in the header (opens Codeman spanned across all displays)">
989
1000
  <span class="settings-item-label">Multi-monitor Button</span>
990
1001
  <label class="switch switch-sm">
@@ -1137,13 +1148,26 @@
1137
1148
  </label>
1138
1149
  <span class="form-hint">Enable experimental Agent Teams for all new Claude sessions (disabled by default)</span>
1139
1150
  </div>
1151
+ <div class="form-row">
1152
+ <label>Claude Model</label>
1153
+ <select id="appSettingsClaudeModel" class="form-select">
1154
+ <option value="">Default (CLI setting)</option>
1155
+ <option value="claude-fable-5[1m]">Fable 5 (1M context)</option>
1156
+ <option value="claude-fable-5">Fable 5</option>
1157
+ <option value="opus[1m]">Opus (1M context)</option>
1158
+ <option value="opus">Opus</option>
1159
+ <option value="sonnet">Sonnet</option>
1160
+ <option value="haiku">Haiku</option>
1161
+ </select>
1162
+ <span class="form-hint">Model for new Claude sessions (pinned via the case's .claude/settings.local.json) — takes precedence over the 1M Opus toggle below</span>
1163
+ </div>
1140
1164
  <div class="form-row form-row-switch">
1141
1165
  <label>1M Opus Context</label>
1142
1166
  <label class="switch">
1143
1167
  <input type="checkbox" id="appSettingsOpusContext1m">
1144
1168
  <span class="slider"></span>
1145
1169
  </label>
1146
- <span class="form-hint">Use 1M token context window (model: opus[1m]) for all new sessions</span>
1170
+ <span class="form-hint">Use 1M token context window (model: opus[1m]) for all new sessions — ignored when a Claude Model is selected above</span>
1147
1171
  </div>
1148
1172
  <div class="form-row">
1149
1173
  <label>Thinking Effort</label>
@@ -1174,12 +1198,25 @@
1174
1198
  <span class="form-hint">Process priority (-20 to 19, higher = lower priority, default: 10)</span>
1175
1199
  </div>
1176
1200
  </div>
1201
+ <!-- Codex CLI Tab -->
1202
+ <div class="modal-tab-content hidden" id="settings-codex">
1203
+ <div class="form-section-header">Codex CLI</div>
1204
+ <div class="form-row form-row-switch">
1205
+ <label>Bypass Approvals and Sandbox</label>
1206
+ <label class="switch">
1207
+ <input type="checkbox" id="appSettingsCodexDangerouslyBypassApprovals">
1208
+ <span class="slider"></span>
1209
+ </label>
1210
+ <span class="form-hint">Start new Codex sessions with --dangerously-bypass-approvals-and-sandbox</span>
1211
+ </div>
1212
+ </div>
1177
1213
  <!-- Models Tab -->
1178
1214
  <div class="modal-tab-content hidden" id="settings-models">
1179
1215
  <div class="form-row">
1180
1216
  <label>Default Model</label>
1181
1217
  <select id="appSettingsDefaultModel" class="form-select">
1182
1218
  <option value="">Default (CLI default)</option>
1219
+ <option value="claude-fable-5">Fable 5 (Most powerful)</option>
1183
1220
  <option value="opus">Opus (Most capable)</option>
1184
1221
  <option value="sonnet">Sonnet (Balanced)</option>
1185
1222
  <option value="haiku">Haiku (Fast & cheap)</option>
@@ -1203,6 +1240,7 @@
1203
1240
  <option value="haiku">Haiku</option>
1204
1241
  <option value="sonnet">Sonnet</option>
1205
1242
  <option value="opus">Opus</option>
1243
+ <option value="claude-fable-5">Fable 5</option>
1206
1244
  </select>
1207
1245
  <span class="form-hint">Quick searches, codebase exploration</span>
1208
1246
  </div>
@@ -1213,6 +1251,7 @@
1213
1251
  <option value="haiku">Haiku</option>
1214
1252
  <option value="sonnet">Sonnet</option>
1215
1253
  <option value="opus">Opus</option>
1254
+ <option value="claude-fable-5">Fable 5</option>
1216
1255
  </select>
1217
1256
  <span class="form-hint">Code writing, feature implementation</span>
1218
1257
  </div>
@@ -1223,6 +1262,7 @@
1223
1262
  <option value="haiku">Haiku</option>
1224
1263
  <option value="sonnet">Sonnet</option>
1225
1264
  <option value="opus">Opus</option>
1265
+ <option value="claude-fable-5">Fable 5</option>
1226
1266
  </select>
1227
1267
  <span class="form-hint">Writing and running tests</span>
1228
1268
  </div>
@@ -1233,6 +1273,7 @@
1233
1273
  <option value="haiku">Haiku</option>
1234
1274
  <option value="sonnet">Sonnet</option>
1235
1275
  <option value="opus">Opus</option>
1276
+ <option value="claude-fable-5">Fable 5</option>
1236
1277
  </select>
1237
1278
  <span class="form-hint">Code review, quality checks</span>
1238
1279
  </div>
@@ -1844,14 +1885,14 @@
1844
1885
  <script defer src="notification-manager.9c984ac2.js"></script>
1845
1886
  <script defer src="keyboard-accessory.bc753cc7.js"></script>
1846
1887
  <script defer src="input-cjk.b8686b5e.js"></script>
1847
- <script defer src="app.a2f053a8.js"></script>
1888
+ <script defer src="app.a8663e79.js"></script>
1848
1889
  <script defer src="terminal-ui.6ce91b0b.js"></script>
1849
1890
  <script defer src="respawn-ui.2d249da9.js"></script>
1850
1891
  <script defer src="ralph-panel.6de2d0f8.js"></script>
1851
1892
  <script defer src="orchestrator-panel.js"></script>
1852
- <script defer src="settings-ui.cbedc88a.js"></script>
1893
+ <script defer src="settings-ui.21b009ca.js"></script>
1853
1894
  <script defer src="panels-ui.6bb3169f.js"></script>
1854
- <script defer src="session-ui.7e2dbbdd.js"></script>
1895
+ <script defer src="session-ui.512816d8.js"></script>
1855
1896
  <script defer src="ralph-wizard.a6b2d36b.js"></script>
1856
1897
  <script defer src="api-client.c9b1cddc.js"></script>
1857
1898
  <script defer src="subagent-windows.a366a4ad.js"></script>
Binary file
Binary file
@@ -0,0 +1,36 @@
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},async loadQuickStartCases(e=null,t=null){try{let s=null;try{const l=t?await t:await fetch("/api/settings").then(r=>r.ok?r.json():null).then(r=>r?.data??null);l&&(s=l.lastUsedCase||null)}catch{}const n=(await(await fetch("/api/cases")).json()).data;this.cases=n,console.log("[loadQuickStartCases] Loaded cases:",n.map(l=>l.name),"lastUsedCase:",s);const i=document.getElementById("quickStartCase");let o="";const m=n.some(l=>l.name==="testcase"),d=MobileDetection.getDeviceType()==="mobile"?8:20;if(n.forEach(l=>{const r=l.name.length>d?l.name.substring(0,d)+"\u2026":l.name;o+=`<option value="${escapeHtml(l.name)}">${escapeHtml(r)}</option>`}),m||(o='<option value="testcase">testcase</option>'+o),i.innerHTML=o,console.log("[loadQuickStartCases] Set options:",i.innerHTML.substring(0,200)),e)i.value=e,this.updateDirDisplayForCase(e),this.updateMobileCaseLabel(e);else if(s&&n.some(l=>l.name===s))i.value=s,this.updateDirDisplayForCase(s),this.updateMobileCaseLabel(s);else if(n.length>0){const l=n.find(r=>r.name==="testcase")||n[0];i.value=l.name,this.updateDirDisplayForCase(l.name),this.updateMobileCaseLabel(l.name)}else i.value="testcase",document.getElementById("dirDisplay").textContent="~/codeman-cases/testcase",this.updateMobileCaseLabel("testcase");i.dataset.listenerAdded||(i.addEventListener("change",()=>{this.updateDirDisplayForCase(i.value),this.saveLastUsedCase(i.value),this.updateMobileCaseLabel(i.value)}),i.dataset.listenerAdded="true")}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():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),n=a.toLocaleDateString("en",{month:"short",day:"numeric"})+" "+a.toLocaleTimeString("en",{hour:"2-digit",minute:"2-digit",hour12:!1}),i=s.workingDir.replace(/^\/home\/[^/]+\//,"~/"),o=document.createElement("button");o.className="run-mode-option",o.title=s.workingDir,o.dataset.sessionId=s.sessionId,o.dataset.workingDir=s.workingDir;const m=document.createElement("span");m.className="hist-dir",m.textContent=i;const c=document.createElement("span");c.className="hist-meta",c.textContent=n,o.append(m,c),o.addEventListener("click",d=>{d.stopPropagation(),this.resumeHistorySession(s.sessionId,s.workingDir)}),e.appendChild(o)}}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":"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)},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 y=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!y.success)throw new Error(y.error||"Failed to create case");a=y.data.case}const n=a.path;if(!n)throw new Error("Case path not found");let i=null,o=1;for(const[,h]of this.sessions){const y=h.name&&h.name.match(/^w(\d+)-([a-zA-Z0-9_-]+)/);if(y&&y[2]===e){const b=parseInt(y[1]);b>=o&&(o=b+1)}}const m=this.isRalphTrackerEnabledByDefault(),c=[];for(let h=0;h<t;h++)c.push(`w${o+h}-${e}`);const d=this.getCaseSettings(e),l=this.loadAppSettingsFromStorage(),r=this.buildEnvOverrides(d,l),u=Object.keys(r).length>0,g=this.getEffortSetting(l),f=d.opusContext1m||l.opusContext1mEnabled,p=l.claudeModel||(f?"opus[1m]":"");this.terminal.writeln(`\x1B[90m Creating ${t} session(s)...\x1B[0m`);const w=c.map(h=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:n,name:h,...u?{envOverrides:r}:{},...g?{effort:g}:{},...p!==void 0?{modelOverride:p}:{}})}).then(y=>y.json())),v=await Promise.all(w),C=[];for(const h of v){if(!h.success)throw new Error(h.error);C.push(h.data.session.id)}i=C[0],await Promise.all(C.map(h=>fetch(`/api/sessions/${h}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:m,disableAutoEnable:!m})}))),this.terminal.writeln(`\x1B[90m Starting ${t} session(s) in parallel...\x1B[0m`),await Promise.all(C.map(h=>fetch(`/api/sessions/${h}/interactive`,{method:"POST"}))),this.terminal.writeln(`\x1B[90m All ${t} sessions ready\x1B[0m`),i&&(await this.selectSession(i),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 u=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!u.success)throw new Error(u.error||"Failed to create case");a=u.data.case}const n=a.path;if(!n)throw new Error("Case path not found");let i=1;for(const[,r]of this.sessions){const u=r.name&&r.name.match(/^s(\d+)-([a-zA-Z0-9_-]+)/);if(u&&u[2]===e){const g=parseInt(u[1]);g>=i&&(i=g+1)}}const o=[];for(let r=0;r<t;r++)o.push(`s${i+r}-${e}`);const m=o.map(r=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:n,mode:"shell",name:r})}).then(u=>u.json())),c=await Promise.all(m),d=[];for(const r of c){if(!r.success)throw new Error(r.error);d.push(r.data.session.id)}await Promise.all(d.map(r=>fetch(`/api/sessions/${r}/shell`,{method:"POST"})));const l=this.getTerminalDimensions();l&&await Promise.all(d.map(r=>fetch(`/api/sessions/${r}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(l)}))),d.length>0&&(this.activeSessionId=d[0],await this.selectSession(d[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";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting OpenCode session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!(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()),i=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"opencode",openCodeConfig:{autoAllowTools:!0},...Object.keys(a).length>0?{envOverrides:a}:{}})})).json();if(!i.success)throw new Error(i.error||"Failed to start OpenCode");i.data.sessionId&&await this.selectSession(i.data.sessionId),this.terminal.focus()}catch(t){this.terminal.writeln(`\x1B[1;31m Error: ${t.message}\x1B[0m`)}},async runCodex(){const e=document.getElementById("quickStartCase").value||"testcase";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting Codex session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!(await(await fetch("/api/codex/status")).json()).data.available){this.terminal.writeln("\x1B[1;31m Codex CLI not found.\x1B[0m"),this.terminal.writeln("\x1B[90m Install with: npm install -g @openai/codex\x1B[0m");return}const a=this.loadAppSettingsFromStorage(),n=this.buildEnvOverrides(this.getCaseSettings(e),a),o=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"codex",codexConfig:{dangerouslyBypassApprovals:a.codexDangerouslyBypassApprovals??!1,renderMode:"hybrid"},...Object.keys(n).length>0?{envOverrides:n}:{}})})).json();if(!o.success)throw new Error(o.error||"Failed to start Codex");o.data.sessionId&&await this.selectSession(o.data.sessionId),this.terminal.focus()}catch(t){this.terminal.writeln(`\x1B[1;31m Error: ${t.message}\x1B[0m`)}},openSessionOptions(e){const t=this.sessions.get(e);if(!t)return;this.editingSessionId=e,this.switchOptionsTab(t.mode==="opencode"||t.mode==="codex"?"summary":"respawn");const s=document.getElementById("sessionRespawnStatus"),a=document.getElementById("modalEnableRespawnBtn"),n=document.getElementById("modalStopRespawnBtn");this.respawnStatus[e]?(s.classList.add("active"),s.querySelector(".respawn-status-text").textContent=this.respawnStatus[e].state||"Active",a.style.display="none",n.style.display=""):(s.classList.remove("active"),s.querySelector(".respawn-status-text").textContent="Not active",a.style.display="",n.style.display="none");const i=document.getElementById("sessionRespawnSection");t.mode==="claude"&&t.pid?i.style.display="":i.style.display="none";const o=t.mode==="opencode"||t.mode==="codex";document.querySelectorAll("[data-claude-only]").forEach(p=>{p.style.display=o?"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("modalImageWatcherEnabled").checked=t.imageWatcherEnabled??!0,document.getElementById("modalFlickerFilterEnabled").checked=t.flickerFilterEnabled??!1;const c=parseSessionPrefix(t.name),d=document.getElementById("modalSessionPrefix");c?(d.textContent=c.prefix+": ",d.style.display="",document.getElementById("modalSessionName").value=c.suffix,document.getElementById("modalSessionName").placeholder="Add description..."):(d.style.display="none",d.textContent="",document.getElementById("modalSessionName").value=t.name||"",document.getElementById("modalSessionName").placeholder="Auto (directory name)");const l=t.color||"default";document.getElementById("sessionColorPicker")?.querySelectorAll(".color-swatch").forEach(p=>{p.classList.toggle("selected",p.dataset.color===l)}),this.renderPresetDropdown(),document.getElementById("respawnPresetSelect").value="",document.getElementById("presetDescriptionHint").textContent="";const u=document.querySelector('#sessionOptionsModal .modal-tab-btn[data-tab="ralph"]'),g=document.querySelector('#sessionOptionsModal .modal-tab-btn[data-tab="respawn"]');if(o?(u&&(u.style.display="none"),g&&(g.style.display="none"),this.switchOptionsTab("context")):(u&&(u.style.display=""),g&&(g.style.display="")),!o){const p=this.ralphStates.get(e);this.populateRalphForm({enabled:p?.loop?.enabled??t.ralphLoop?.enabled??!1,completionPhrase:p?.loop?.completionPhrase||t.ralphLoop?.completionPhrase||"",maxIterations:p?.loop?.maxIterations||t.ralphLoop?.maxIterations||0})}const f=document.getElementById("sessionOptionsModal");f.classList.add("active"),this.activeFocusTrap=new FocusTrap(f),this.activeFocusTrap.activate()},async saveSessionName(){if(!this.editingSessionId)return;const e=this.sessions.get(this.editingSessionId),t=e?parseSessionPrefix(e.name):null,s=document.getElementById("modalSessionName").value.trim();let a;t?a=t.prefix+(s?": "+s:""):a=s;try{await this._apiPut(`/api/sessions/${this.editingSessionId}/name`,{name:a})}catch(n){this.showToast("Failed to save session name: "+n.message,"error")}},async autoSaveAutoCompact(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-compact`,{enabled:document.getElementById("modalAutoCompactEnabled").checked,threshold:parseInt(document.getElementById("modalAutoCompactThreshold").value)||11e4,prompt:document.getElementById("modalAutoCompactPrompt").value.trim()||void 0})}catch{}},async autoSaveAutoClear(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-clear`,{enabled:document.getElementById("modalAutoClearEnabled").checked,threshold:parseInt(document.getElementById("modalAutoClearThreshold").value)||14e4})}catch{}},async toggleSessionImageWatcher(){if(!this.editingSessionId)return;const e=document.getElementById("modalImageWatcherEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/image-watcher`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.imageWatcherEnabled=e),this.showToast(`Image watcher ${e?"enabled":"disabled"}`,"success")}catch{this.showToast("Failed to toggle image watcher","error")}},async toggleFlickerFilter(){if(!this.editingSessionId)return;const e=document.getElementById("modalFlickerFilterEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/flicker-filter`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.flickerFilterEnabled=e),this.showToast(`Flicker filter ${e?"enabled":"disabled"}`,"success")}catch{this.showToast("Failed to toggle flicker filter","error")}},async autoSaveRespawnConfig(){if(!this.editingSessionId)return;const e={updatePrompt:document.getElementById("modalRespawnPrompt").value,sendClear:document.getElementById("modalRespawnSendClear").checked,sendInit:document.getElementById("modalRespawnSendInit").checked,kickstartPrompt:document.getElementById("modalRespawnKickstart").value.trim()||void 0,autoAcceptPrompts:document.getElementById("modalRespawnAutoAccept").checked};try{await this._apiPut(`/api/sessions/${this.editingSessionId}/respawn/config`,e)}catch{}},async loadSavedRespawnConfig(e){try{const s=await(await fetch(`/api/sessions/${e}/respawn/config`)).json();if(s.success&&s.data&&s.data.config){const a=s.data.config;document.getElementById("modalRespawnPrompt").value=a.updatePrompt||"update all the docs and CLAUDE.md",document.getElementById("modalRespawnSendClear").checked=a.sendClear??!0,document.getElementById("modalRespawnSendInit").checked=a.sendInit??!0,document.getElementById("modalRespawnKickstart").value=a.kickstartPrompt||"",document.getElementById("modalRespawnAutoAccept").checked=a.autoAcceptPrompts??!0,a.durationMinutes&&(document.querySelector(`.duration-preset-btn[data-minutes="${a.durationMinutes}"]`)?this.selectDurationPreset(String(a.durationMinutes)):(this.selectDurationPreset("custom"),document.getElementById("modalRespawnDuration").value=a.durationMinutes))}}catch{}},selectDurationPreset(e){document.querySelectorAll(".duration-preset-btn").forEach(n=>n.classList.remove("active"));const t=document.querySelector(`.duration-preset-btn[data-minutes="${e}"]`);t&&t.classList.add("active");const s=document.querySelector(".duration-custom-input"),a=document.getElementById("modalRespawnDuration");e==="custom"?(s.classList.add("visible"),a.focus()):(s.classList.remove("visible"),a.value="")},getSelectedDuration(){const e=document.querySelector(".duration-custom-input"),t=document.getElementById("modalRespawnDuration");if(e.classList.contains("visible"))return t.value?parseInt(t.value):null;{const a=document.querySelector(".duration-preset-btn.active")?.dataset.minutes;return a?parseInt(a):null}},switchOptionsTab(e){document.querySelectorAll("#sessionOptionsModal .modal-tab-btn").forEach(t=>{t.classList.toggle("active",t.dataset.tab===e)}),document.getElementById("respawn-tab").classList.toggle("hidden",e!=="respawn"),document.getElementById("context-tab").classList.toggle("hidden",e!=="context"),document.getElementById("ralph-tab").classList.toggle("hidden",e!=="ralph"),document.getElementById("summary-tab").classList.toggle("hidden",e!=="summary"),e==="summary"&&this.editingSessionId&&this.loadRunSummary(this.editingSessionId)},getRalphConfig(){return{enabled:document.getElementById("modalRalphEnabled").checked,completionPhrase:document.getElementById("modalRalphPhrase").value.trim(),maxIterations:parseInt(document.getElementById("modalRalphMaxIterations").value)||0,maxTodos:parseInt(document.getElementById("modalRalphMaxTodos").value)||50,todoExpirationMinutes:parseInt(document.getElementById("modalRalphTodoExpiration").value)||60}},populateRalphForm(e){document.getElementById("modalRalphEnabled").checked=e?.enabled??!1,document.getElementById("modalRalphPhrase").value=e?.completionPhrase||"",document.getElementById("modalRalphMaxIterations").value=e?.maxIterations||0,document.getElementById("modalRalphMaxTodos").value=e?.maxTodos||50,document.getElementById("modalRalphTodoExpiration").value=e?.todoExpirationMinutes||60},async saveRalphConfig(){if(!this.editingSessionId){this.showToast("No session selected","warning");return}const e=this.getRalphConfig();e.enabled&&this.ralphClosedSessions.delete(this.editingSessionId);try{const s=await(await fetch(`/api/sessions/${this.editingSessionId}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).json();if(s.error)throw new Error(s.error);this.showToast("Ralph config saved","success")}catch(t){this.showToast("Failed to save Ralph config: "+t.message,"error")}},startInlineRename(e){const t=this.sessions.get(e);if(!t)return;const s=document.querySelector(`.tab-name[data-session-id="${e}"]`);if(!s)return;this._inlineRenameActive=!0;const a=this.getSessionName(t),n=parseSessionPrefix(t.name),i=s.textContent;for(s.textContent="";s.firstChild;)s.removeChild(s.firstChild);if(n){const c=document.createElement("span");c.textContent=n.prefix+": ",c.style.cssText="color: var(--text-muted); font-size: 0.75rem; white-space: nowrap;",s.appendChild(c)}const o=document.createElement("input");o.type="text",o.value=n?n.suffix:t.name||"",o.placeholder=n?"Add description...":a,o.className="tab-rename-input",o.style.cssText="width: 80px; font-size: 0.75rem; padding: 2px 4px; background: var(--bg-input); border: 1px solid var(--accent); border-radius: 3px; color: var(--text); outline: none;",s.appendChild(o),o.focus(),o.select();const m=async({commit:c})=>{if(!this._inlineRenameActive)return;if(this._inlineRenameActive=!1,this._activeRename=null,!c){this.renderSessionTabs();return}const d=o.value.trim(),l=n?n.prefix+(d?": "+d:""):d;if(s.textContent=l||i,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=i,this.showToast("Failed to rename","error")}this.renderSessionTabs()};this._activeRename={sessionId:e,cancel:()=>m({commit:!1})},o.addEventListener("blur",()=>m({commit:!0})),o.addEventListener("keydown",c=>{c.isComposing||c.keyCode===229||(c.key==="Enter"?(c.preventDefault(),o.blur()):c.key==="Escape"&&(o.value="",o.blur()))})},toggleCaseSettings(){const e=document.getElementById("caseSettingsPopover");if(e.classList.contains("hidden")){const t=document.getElementById("quickStartCase").value||"testcase",s=this.getCaseSettings(t);document.getElementById("caseAgentTeams").checked=s.agentTeams,document.getElementById("caseOpusContext1m").checked=s.opusContext1m,e.classList.remove("hidden");const a=n=>{!e.contains(n.target)&&!n.target.classList.contains("btn-case-settings")&&(e.classList.add("hidden"),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),0)}else e.classList.add("hidden")},getCaseSettings(e){try{const t=localStorage.getItem("caseSettings_"+e);if(t)return JSON.parse(t)}catch{}return{agentTeams:!1,opusContext1m:!0}},saveCaseSettings(e,t){localStorage.setItem("caseSettings_"+e,JSON.stringify(t))},onCaseSettingChanged(){const e=document.getElementById("quickStartCase").value||"testcase",t=this.getCaseSettings(e);t.agentTeams=document.getElementById("caseAgentTeams").checked,t.opusContext1m=document.getElementById("caseOpusContext1m").checked,this.saveCaseSettings(e,t);const s=document.getElementById("caseAgentTeamsMobile");s&&(s.checked=t.agentTeams);const a=document.getElementById("caseOpusContext1mMobile");a&&(a.checked=t.opusContext1m)},toggleCaseSettingsMobile(){const e=document.getElementById("caseSettingsPopoverMobile");if(e.classList.contains("hidden")){const t=document.getElementById("quickStartCase").value||"testcase",s=this.getCaseSettings(t);document.getElementById("caseAgentTeamsMobile").checked=s.agentTeams,document.getElementById("caseOpusContext1mMobile").checked=s.opusContext1m,e.classList.remove("hidden");const a=n=>{!e.contains(n.target)&&!n.target.classList.contains("btn-case-settings-mobile")&&(e.classList.add("hidden"),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),0)}else e.classList.add("hidden")},onCaseSettingChangedMobile(){const e=document.getElementById("quickStartCase").value||"testcase",t=this.getCaseSettings(e);t.agentTeams=document.getElementById("caseAgentTeamsMobile").checked,t.opusContext1m=document.getElementById("caseOpusContext1mMobile").checked,this.saveCaseSettings(e,t);const s=document.getElementById("caseAgentTeams");s&&(s.checked=t.agentTeams);const a=document.getElementById("caseOpusContext1m");a&&(a.checked=t.opusContext1m)},showCreateCaseModal(){document.getElementById("newCaseName").value="",document.getElementById("newCaseDescription").value="",document.getElementById("linkCaseName").value="",document.getElementById("linkCasePath").value="",this.caseModalTab="case-create",this.switchCaseModalTab("case-create");const e=document.getElementById("createCaseModal");e.querySelectorAll(".modal-tabs .modal-tab-btn").forEach(t=>{t.onclick=()=>this.switchCaseModalTab(t.dataset.tab)}),e.querySelectorAll('input[type="text"]').forEach(t=>{t._mobileScrollWired||(t._mobileScrollWired=!0,t.addEventListener("focus",()=>{window.innerWidth<=430&&setTimeout(()=>t.scrollIntoView({behavior:"smooth",block:"center"}),300)}))}),e.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()):(s.style.display="",s.textContent=e==="case-create"?"Create":"Link"),e==="case-create"?document.getElementById("newCaseName").focus():e==="case-link"&&document.getElementById("linkCaseName").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():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}try{const a=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:t})})).json();a.success?(this.closeCreateCaseModal(),this.showToast(`Case "${e}" created`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(a.error||"Failed to create case","error")}catch(s){console.error("Failed to create case:",s),this.showToast("Failed to create case: "+s.message,"error")}},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")}},renderCaseManageList(){const e=document.getElementById("caseManageList"),t=this.cases||[];if(t.length===0){e.innerHTML='<div class="form-hint" style="text-align: center; padding: 2rem 0;">No cases yet</div>';return}let s="";t.forEach((a,n)=>{const i=n===0,o=n===t.length-1,m=a.path?a.path.replace(/^\/Users\/[^/]+/,"~"):"";s+=`
2
+ <div class="case-manage-item" data-case="${escapeHtml(a.name)}">
3
+ <div class="case-manage-info">
4
+ <span class="case-manage-name">${escapeHtml(a.name)}</span>
5
+ <span class="case-manage-path">${escapeHtml(m)}</span>
6
+ </div>
7
+ <div class="case-manage-actions">
8
+ <button class="case-manage-btn" onclick="app.moveCaseUp('${escapeHtml(a.name)}')"
9
+ title="Move up" ${i?"disabled":""}>&#x25B2;</button>
10
+ <button class="case-manage-btn" onclick="app.moveCaseDown('${escapeHtml(a.name)}')"
11
+ title="Move down" ${o?"disabled":""}>&#x25BC;</button>
12
+ <button class="case-manage-btn case-manage-btn-delete" onclick="app.deleteCase('${escapeHtml(a.name)}')"
13
+ title="Delete case">&#x2715;</button>
14
+ </div>
15
+ </div>
16
+ `}),e.innerHTML=s},async moveCaseUp(e){const t=this.cases||[],s=t.findIndex(n=>n.name===e);if(s<=0)return;const a=[...t];[a[s-1],a[s]]=[a[s],a[s-1]],this.cases=a,this.renderCaseManageList(),await this.saveCaseOrder(a.map(n=>n.name))},async moveCaseDown(e){const t=this.cases||[],s=t.findIndex(n=>n.name===e);if(s<0||s>=t.length-1)return;const a=[...t];[a[s],a[s+1]]=[a[s+1],a[s]],this.cases=a,this.renderCaseManageList(),await this.saveCaseOrder(a.map(n=>n.name))},async deleteCase(e){if(confirm(`Delete case "${e}"? Linked cases will only be unlinked (folder preserved). Created cases will be permanently deleted.`))try{const s=await(await fetch(`/api/cases/${encodeURIComponent(e)}`,{method:"DELETE"})).json();if(s.success){this.showToast(`Case "${e}" ${s.data?.type==="unlinked"?"unlinked":"deleted"}`,"success"),this.cases=(this.cases||[]).filter(i=>i.name!==e),this.renderCaseManageList();const n=document.getElementById("quickStartCase").value;await this.loadQuickStartCases(n===e?null:n)}else this.showToast(s.error||"Failed to delete case","error")}catch(t){this.showToast("Failed to delete case: "+t.message,"error")}},async saveCaseOrder(e){try{await fetch("/api/cases/order",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({order:e})});const s=document.getElementById("quickStartCase").value;await this.loadQuickStartCases(s)}catch(t){this.showToast("Failed to save case order: "+t.message,"error")}},showMobileCasePicker(){const e=document.getElementById("mobileCasePickerModal"),t=document.getElementById("mobileCaseList"),a=document.getElementById("quickStartCase").value;let n="";const i=this.cases||[],m=i.some(c=>c.name==="testcase")?i:[{name:"testcase"},...i];for(const c of m){const d=c.name===a;n+=`
17
+ <button class="mobile-case-item ${d?"selected":""}"
18
+ onclick="app.selectMobileCase('${escapeHtml(c.name)}')">
19
+ <span class="mobile-case-item-icon">
20
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
21
+ <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"/>
22
+ </svg>
23
+ </span>
24
+ <span class="mobile-case-item-name">${escapeHtml(c.name)}</span>
25
+ <span class="mobile-case-item-delete" onclick="event.stopPropagation(); app.deleteCaseMobile('${escapeHtml(c.name)}')" title="Delete">
26
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
27
+ <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
28
+ </svg>
29
+ </span>
30
+ <span class="mobile-case-item-check">
31
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
32
+ <polyline points="20 6 9 17 4 12"/>
33
+ </svg>
34
+ </span>
35
+ </button>
36
+ `}t.innerHTML=n,e.classList.add("active")},closeMobileCasePicker(){document.getElementById("mobileCasePickerModal").classList.remove("active")},selectMobileCase(e){const t=document.getElementById("quickStartCase");t.value=e,this.updateMobileCaseLabel(e),this.updateDirDisplayForCase(e),this.saveLastUsedCase(e),this.closeMobileCasePicker(),this.showToast(`Selected: ${e}`,"success")},updateMobileCaseLabel(e){const t=document.getElementById("mobileCaseName");t&&(t.textContent=e)},async deleteCaseMobile(e){if(confirm(`Delete case "${e}"?`))try{const s=await(await fetch(`/api/cases/${encodeURIComponent(e)}`,{method:"DELETE"})).json();s.success?(this.showToast(`Case "${e}" ${s.data?.type==="unlinked"?"unlinked":"deleted"}`,"success"),this.cases=(this.cases||[]).filter(a=>a.name!==e),this.closeMobileCasePicker(),await this.loadQuickStartCases()):this.showToast(s.error||"Failed to delete case","error")}catch(t){this.showToast("Failed to delete case: "+t.message,"error")}},showCreateCaseFromMobile(){this.closeMobileCasePicker(),this.showCreateCaseModal();const e=document.getElementById("createCaseModal");e.classList.add("from-mobile"),setTimeout(()=>e.classList.remove("from-mobile"),300)}}),Object.defineProperty(CodemanApp.prototype,"runMode",{configurable:!0,enumerable:!0,get(){return this._runMode||"claude"},set(e){this._runMode=e==="opencode"||e==="codex"||e==="claude"?e:"claude"}});