aicodeman 1.1.15 → 1.1.16

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 (74) hide show
  1. package/dist/config/buffer-limits.d.ts +10 -0
  2. package/dist/config/buffer-limits.d.ts.map +1 -1
  3. package/dist/config/buffer-limits.js +13 -0
  4. package/dist/config/buffer-limits.js.map +1 -1
  5. package/dist/session.d.ts +23 -0
  6. package/dist/session.d.ts.map +1 -1
  7. package/dist/session.js +37 -0
  8. package/dist/session.js.map +1 -1
  9. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  10. package/dist/web/public/app.e485a055.js +38 -0
  11. package/dist/web/public/app.e485a055.js.br +0 -0
  12. package/dist/web/public/app.e485a055.js.gz +0 -0
  13. package/dist/web/public/constants.1c779517.js.gz +0 -0
  14. package/dist/web/public/gesture/gesture-codeman.js +87 -4
  15. package/dist/web/public/{image-input.0ea86695.js → image-input.ee16ad88.js} +80 -24
  16. package/dist/web/public/image-input.ee16ad88.js.br +0 -0
  17. package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
  18. package/dist/web/public/index.html +4 -4
  19. package/dist/web/public/index.html.br +0 -0
  20. package/dist/web/public/index.html.gz +0 -0
  21. package/dist/web/public/input-cjk.4e950e0a.js.gz +0 -0
  22. package/dist/web/public/keyboard-accessory.3f91acef.js.gz +0 -0
  23. package/dist/web/public/mobile-handlers.b5a667ef.js.gz +0 -0
  24. package/dist/web/public/mobile.38346326.css.gz +0 -0
  25. package/dist/web/public/notification-manager.ca9dc40d.js.gz +0 -0
  26. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  27. package/dist/web/public/panels-ui.aedcf302.js.gz +0 -0
  28. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  29. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  30. package/dist/web/public/respawn-ui.2d249da9.js.gz +0 -0
  31. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  32. package/dist/web/public/session-ui.0ee6c334.js.gz +0 -0
  33. package/dist/web/public/settings-ui.38feb956.js.gz +0 -0
  34. package/dist/web/public/styles.4d2984bd.css.gz +0 -0
  35. package/dist/web/public/subagent-windows.69b60c35.js.gz +0 -0
  36. package/dist/web/public/sw.js.gz +0 -0
  37. package/dist/web/public/{terminal-ui.70596bbd.js → terminal-ui.f4f3b686.js} +2 -2
  38. package/dist/web/public/terminal-ui.f4f3b686.js.br +0 -0
  39. package/dist/web/public/terminal-ui.f4f3b686.js.gz +0 -0
  40. package/dist/web/public/ultracode-panel.js.gz +0 -0
  41. package/dist/web/public/ultracode-windows.js.gz +0 -0
  42. package/dist/web/public/upload.html.gz +0 -0
  43. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  44. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  45. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  46. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  47. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  48. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  49. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  50. package/dist/web/public/vendor/xterm.css.gz +0 -0
  51. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  52. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  53. package/dist/web/routes/session-routes.d.ts.map +1 -1
  54. package/dist/web/routes/session-routes.js +33 -9
  55. package/dist/web/routes/session-routes.js.map +1 -1
  56. package/dist/web/routes/ws-routes.d.ts +4 -1
  57. package/dist/web/routes/ws-routes.d.ts.map +1 -1
  58. package/dist/web/routes/ws-routes.js +21 -6
  59. package/dist/web/routes/ws-routes.js.map +1 -1
  60. package/dist/web/schemas.d.ts +2 -0
  61. package/dist/web/schemas.d.ts.map +1 -1
  62. package/dist/web/schemas.js +9 -0
  63. package/dist/web/schemas.js.map +1 -1
  64. package/dist/web/server.d.ts.map +1 -1
  65. package/dist/web/server.js +3 -2
  66. package/dist/web/server.js.map +1 -1
  67. package/package.json +1 -1
  68. package/dist/web/public/app.61e2966f.js +0 -38
  69. package/dist/web/public/app.61e2966f.js.br +0 -0
  70. package/dist/web/public/app.61e2966f.js.gz +0 -0
  71. package/dist/web/public/image-input.0ea86695.js.br +0 -0
  72. package/dist/web/public/image-input.0ea86695.js.gz +0 -0
  73. package/dist/web/public/terminal-ui.70596bbd.js.br +0 -0
  74. package/dist/web/public/terminal-ui.70596bbd.js.gz +0 -0
@@ -0,0 +1,38 @@
1
+ "use strict";const _crashDiag={_entries:[],_maxEntries:50,log(h){const e=`${new Date().toISOString().slice(11,23)} ${h}`;this._entries.push(e),this._entries.length>this._maxEntries&&this._entries.shift();try{localStorage.setItem("codeman-crash-diag",this._entries.join(`
2
+ `))}catch{}}};try{const h=localStorage.getItem("codeman-crash-diag");h&&console.log(`[CRASH-DIAG] Previous session breadcrumbs:
3
+ `+h)}catch{}if(_crashDiag.log("PAGE LOAD"),setInterval(()=>{try{localStorage.setItem("codeman-crash-heartbeat",String(Date.now())),_crashDiag._entries.length>0&&navigator.sendBeacon("/api/crash-diag",JSON.stringify({data:_crashDiag._entries.join(`
4
+ `)}))}catch{}},2e3),window.addEventListener("error",h=>{_crashDiag.log(`ERROR: ${h.message} at ${h.filename}:${h.lineno}`),console.error("[CRASH-DIAG] Uncaught error:",h.message,`
5
+ File:`,h.filename,":",h.lineno,":",h.colno,`
6
+ Stack:`,h.error?.stack)}),window.addEventListener("unhandledrejection",h=>{_crashDiag.log(`UNHANDLED: ${h.reason?.message||h.reason}`),console.error("[CRASH-DIAG] Unhandled promise rejection:",h.reason?.message||h.reason,`
7
+ Stack:`,h.reason?.stack)}),typeof PerformanceObserver<"u")try{new PerformanceObserver(e=>{for(const t of e.getEntries())t.duration>200&&(_crashDiag.log(`LONG_TASK: ${t.duration.toFixed(0)}ms`),console.warn(`[CRASH-DIAG] Long task: ${t.duration.toFixed(0)}ms (type: ${t.entryType}, name: ${t.name})`))}).observe({type:"longtask",buffered:!0})}catch{}const _origGetContext=HTMLCanvasElement.prototype.getContext;HTMLCanvasElement.prototype.getContext=function(h,...e){const t=_origGetContext.call(this,h,...e);return(h==="webgl2"||h==="webgl")&&(this.addEventListener("webglcontextlost",s=>{_crashDiag.log(`WEBGL_LOST: ${this.width}x${this.height}`),console.error("[CRASH-DIAG] WebGL context LOST on canvas",this.width,"x",this.height,"\u2014 prevented:",s.defaultPrevented)}),this.addEventListener("webglcontextrestored",()=>{_crashDiag.log("WEBGL_RESTORED"),console.warn("[CRASH-DIAG] WebGL context restored")})),t};const _SSE_HANDLER_MAP=[[SSE_EVENTS.INIT,"_onInit"],[SSE_EVENTS.SESSION_CREATED,"_onSessionCreated"],[SSE_EVENTS.SESSION_UPDATED,"_onSessionUpdated"],[SSE_EVENTS.SESSION_DELETED,"_onSessionDeleted"],[SSE_EVENTS.SESSION_TERMINAL,"_onSSETerminal"],[SSE_EVENTS.SESSION_NEEDS_REFRESH,"_onSSENeedsRefresh"],[SSE_EVENTS.SESSION_CLEAR_TERMINAL,"_onSSEClearTerminal"],[SSE_EVENTS.SESSION_COMPLETION,"_onSessionCompletion"],[SSE_EVENTS.SESSION_ERROR,"_onSessionError"],[SSE_EVENTS.SESSION_EXIT,"_onSessionExit"],[SSE_EVENTS.SESSION_IDLE,"_onSessionIdle"],[SSE_EVENTS.SESSION_WORKING,"_onSessionWorking"],[SSE_EVENTS.SESSION_AUTO_CLEAR,"_onSessionAutoClear"],[SSE_EVENTS.SESSION_LIMIT_PAUSE_SCHEDULED,"_onSessionLimitPauseScheduled"],[SSE_EVENTS.SESSION_LIMIT_RESUME,"_onSessionLimitResume"],[SSE_EVENTS.SESSION_LIMIT_RESUME_CANCELLED,"_onSessionLimitResumeCancelled"],[SSE_EVENTS.SESSION_CLI_INFO,"_onSessionCliInfo"],[SSE_EVENTS.SESSION_STATUS_TELEMETRY,"_onSessionStatusTelemetry"],[SSE_EVENTS.SCHEDULED_CREATED,"_onScheduledCreated"],[SSE_EVENTS.SCHEDULED_UPDATED,"_onScheduledUpdated"],[SSE_EVENTS.SCHEDULED_COMPLETED,"_onScheduledCompleted"],[SSE_EVENTS.SCHEDULED_STOPPED,"_onScheduledStopped"],[SSE_EVENTS.RESPAWN_STARTED,"_onRespawnStarted"],[SSE_EVENTS.RESPAWN_STOPPED,"_onRespawnStopped"],[SSE_EVENTS.RESPAWN_STATE_CHANGED,"_onRespawnStateChanged"],[SSE_EVENTS.RESPAWN_CYCLE_STARTED,"_onRespawnCycleStarted"],[SSE_EVENTS.RESPAWN_BLOCKED,"_onRespawnBlocked"],[SSE_EVENTS.RESPAWN_AUTO_ACCEPT_SENT,"_onRespawnAutoAcceptSent"],[SSE_EVENTS.RESPAWN_DETECTION_UPDATE,"_onRespawnDetectionUpdate"],[SSE_EVENTS.RESPAWN_TIMER_STARTED,"_onRespawnTimerStarted"],[SSE_EVENTS.RESPAWN_TIMER_CANCELLED,"_onRespawnTimerCancelled"],[SSE_EVENTS.RESPAWN_TIMER_COMPLETED,"_onRespawnTimerCompleted"],[SSE_EVENTS.RESPAWN_ERROR,"_onRespawnError"],[SSE_EVENTS.RESPAWN_ACTION_LOG,"_onRespawnActionLog"],[SSE_EVENTS.TASK_CREATED,"_onTaskCreated"],[SSE_EVENTS.TASK_COMPLETED,"_onTaskCompleted"],[SSE_EVENTS.TASK_FAILED,"_onTaskFailed"],[SSE_EVENTS.TASK_UPDATED,"_onTaskUpdated"],[SSE_EVENTS.MUX_CREATED,"_onMuxCreated"],[SSE_EVENTS.MUX_KILLED,"_onMuxKilled"],[SSE_EVENTS.MUX_DIED,"_onMuxDied"],[SSE_EVENTS.MUX_STATS_UPDATED,"_onMuxStatsUpdated"],[SSE_EVENTS.SESSION_RALPH_LOOP_UPDATE,"_onRalphLoopUpdate"],[SSE_EVENTS.SESSION_RALPH_TODO_UPDATE,"_onRalphTodoUpdate"],[SSE_EVENTS.SESSION_RALPH_COMPLETION_DETECTED,"_onRalphCompletionDetected"],[SSE_EVENTS.SESSION_RALPH_STATUS_UPDATE,"_onRalphStatusUpdate"],[SSE_EVENTS.SESSION_CIRCUIT_BREAKER_UPDATE,"_onCircuitBreakerUpdate"],[SSE_EVENTS.SESSION_EXIT_GATE_MET,"_onExitGateMet"],[SSE_EVENTS.SESSION_BASH_TOOL_START,"_onBashToolStart"],[SSE_EVENTS.SESSION_BASH_TOOL_END,"_onBashToolEnd"],[SSE_EVENTS.SESSION_BASH_TOOLS_UPDATE,"_onBashToolsUpdate"],[SSE_EVENTS.HOOK_IDLE_PROMPT,"_onHookIdlePrompt"],[SSE_EVENTS.HOOK_PERMISSION_PROMPT,"_onHookPermissionPrompt"],[SSE_EVENTS.HOOK_ELICITATION_DIALOG,"_onHookElicitationDialog"],[SSE_EVENTS.HOOK_STOP,"_onHookStop"],[SSE_EVENTS.HOOK_TEAMMATE_IDLE,"_onHookTeammateIdle"],[SSE_EVENTS.HOOK_TASK_COMPLETED,"_onHookTaskCompleted"],[SSE_EVENTS.SUBAGENT_DISCOVERED,"_onSubagentDiscovered"],[SSE_EVENTS.SUBAGENT_UPDATED,"_onSubagentUpdated"],[SSE_EVENTS.SUBAGENT_TOOL_CALL,"_onSubagentToolCall"],[SSE_EVENTS.SUBAGENT_PROGRESS,"_onSubagentProgress"],[SSE_EVENTS.SUBAGENT_MESSAGE,"_onSubagentMessage"],[SSE_EVENTS.SUBAGENT_TOOL_RESULT,"_onSubagentToolResult"],[SSE_EVENTS.SUBAGENT_COMPLETED,"_onSubagentCompleted"],[SSE_EVENTS.WORKFLOW_RUN_DISCOVERED,"_onWorkflowRunDiscovered"],[SSE_EVENTS.WORKFLOW_RUN_UPDATED,"_onWorkflowRunUpdated"],[SSE_EVENTS.WORKFLOW_RUN_REMOVED,"_onWorkflowRunRemoved"],[SSE_EVENTS.IMAGE_DETECTED,"_onImageDetected"],[SSE_EVENTS.ATTACHMENT_DETECTED,"_onAttachmentDetected"],[SSE_EVENTS.TUNNEL_STARTED,"_onTunnelStarted"],[SSE_EVENTS.TUNNEL_STOPPED,"_onTunnelStopped"],[SSE_EVENTS.TUNNEL_PROGRESS,"_onTunnelProgress"],[SSE_EVENTS.TUNNEL_ERROR,"_onTunnelError"],[SSE_EVENTS.TUNNEL_QR_ROTATED,"_onTunnelQrRotated"],[SSE_EVENTS.TUNNEL_QR_REGENERATED,"_onTunnelQrRegenerated"],[SSE_EVENTS.TUNNEL_QR_AUTH_USED,"_onTunnelQrAuthUsed"],[SSE_EVENTS.PLAN_SUBAGENT,"_onPlanSubagent"],[SSE_EVENTS.PLAN_PROGRESS,"_onPlanProgress"],[SSE_EVENTS.PLAN_STARTED,"_onPlanStarted"],[SSE_EVENTS.PLAN_CANCELLED,"_onPlanCancelled"],[SSE_EVENTS.PLAN_COMPLETED,"_onPlanCompleted"],[SSE_EVENTS.ORCHESTRATOR_STATE_CHANGED,"_onOrchestratorStateChanged"],[SSE_EVENTS.ORCHESTRATOR_PLAN_PROGRESS,"_onOrchestratorPlanProgress"],[SSE_EVENTS.ORCHESTRATOR_PLAN_READY,"_onOrchestratorPlanReady"],[SSE_EVENTS.ORCHESTRATOR_PHASE_STARTED,"_onOrchestratorPhaseStarted"],[SSE_EVENTS.ORCHESTRATOR_PHASE_COMPLETED,"_onOrchestratorPhaseCompleted"],[SSE_EVENTS.ORCHESTRATOR_PHASE_FAILED,"_onOrchestratorPhaseFailed"],[SSE_EVENTS.ORCHESTRATOR_VERIFICATION,"_onOrchestratorVerification"],[SSE_EVENTS.ORCHESTRATOR_TASK_ASSIGNED,"_onOrchestratorTaskAssigned"],[SSE_EVENTS.ORCHESTRATOR_TASK_COMPLETED,"_onOrchestratorTaskCompleted"],[SSE_EVENTS.ORCHESTRATOR_TASK_FAILED,"_onOrchestratorTaskFailed"],[SSE_EVENTS.ORCHESTRATOR_COMPLETED,"_onOrchestratorCompleted"],[SSE_EVENTS.ORCHESTRATOR_ERROR,"_onOrchestratorError"],[SSE_EVENTS.CLIPBOARD_WRITE,"_onClipboardWrite"]];function parseSessionPrefix(h){if(!h)return null;const e=h.match(/^(w\d+-[a-zA-Z0-9_-]+|s\d+-[a-zA-Z0-9_-]+)/);if(!e)return null;const t=e[1],s=h.slice(t.length);return s===""?{prefix:t,suffix:""}:s.startsWith(": ")?{prefix:t,suffix:s.slice(2)}:null}class CodemanApp{constructor(){this.sessions=new Map,this._shortIdCache=new Map,this.sessionOrder=[],this.draggedTabId=null,this.cases=[],this.currentRun=null,this.totalTokens=0,this.globalStats=null,this.eventSource=null,this._clientId=typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"c-"+Math.random().toString(36).slice(2)+Date.now().toString(36),this.terminal=null,this.fitAddon=null,this.activeSessionId=null,this.soloSessionId=this._detectSoloSessionId(),this.isSoloWindow=!!this.soloSessionId,this.detachedSessions=new Set,this.detachedWindows=new Map,this._detachWatchTimers=new Map,this.windowChannel=null,this._redockGrace=new Map,this._detachPingPending=null,this._detachLivenessTimer=null,this._detachOrphanStrikes=new Map,this._initGeneration=0,this._initFallbackTimer=null,this._selectGeneration=0,this.terminalLoadStates=new Map,this.respawnStatus={},this.respawnTimers={},this.respawnCountdownTimers={},this.respawnActionLogs={},this.timerCountdownInterval=null,this.terminalBuffers=new Map,this.editingSessionId=null,this.pendingCloseSessionId=null,this.muxSessions=[],this.ralphStates=new Map,this.subagents=new Map,this.subagentActivity=new Map,this.subagentToolResults=new Map,this.activeSubagentId=null,this.subagentPanelVisible=!1,this.workflowRuns=new Map,this.workflowRunDetails=new Map,this.activeWorkflowRunId=null,this.activeWorkflowPhaseIndex=null,this.ultracodeWindows=new Map,this.ultracodeWindowsClosed=new Set,this.ultracodeWindowCloseTimers=new Map,this.ultracodeWindowZIndex=1e3,this.subagentWindows=new Map,this.subagentWindowZIndex=ZINDEX_SUBAGENT_BASE,this.minimizedSubagents=new Map,this._subagentHideTimeout=null,this.subagentParentMap=new Map,this.teams=new Map,this.teamTasks=new Map,this.teammateMap=new Map,this.teammatePanesByName=new Map,this.teammateTerminals=new Map,this.terminalBufferCache=new Map,this.ralphStatePanelCollapsed=!0,this.ralphClosedSessions=new Set,this.planSubagents=new Map,this.planSubagentWindowZIndex=ZINDEX_PLAN_SUBAGENT_BASE,this.planGenerationStopped=!1,this.planAgentsMinimized=!1,this.wizardDragState=null,this.wizardDragListeners=null,this.wizardPosition=null,this.projectInsights=new Map,this.logViewerWindows=new Map,this.logViewerWindowZIndex=ZINDEX_LOG_VIEWER_BASE,this.projectInsightsPanelVisible=!1,this.orchestratorState=null,this.orchestratorPanelVisible=!1,this.currentSessionWorkingDir=null,this.imagePopups=new Map,this.imagePopupZIndex=ZINDEX_IMAGE_POPUP_BASE,this.attachmentCards=new Map,this.attachmentCardStack=null,this.attachmentHistoryCounts=new Map,this.attachmentHistoryItems=[],this.attachmentHistoryDrawerOpen=!1,this.fileBrowserData=null,this.fileBrowserExpandedDirs=new Set,this.fileBrowserFilter="",this.fileBrowserAllExpanded=!1,this.fileBrowserDragListeners=null,this.filePreviewContent="",this._toastContainer=null,this._tunnelUrl=null,this.tabAlerts=new Map,this.pendingHooks=new Map,this._ws=null,this._wsSessionId=null,this._wsReady=!1,this.pendingWrites=[],this.writeFrameScheduled=!1,this._wasAtBottomBeforeWrite=!0,this.syncWaitTimeout=null,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._bufferLoadSeq=0,this._bufferLoadOwner=null,this.flickerFilterBuffer="",this.flickerFilterActive=!1,this.flickerFilterTimeout=null,this._debounceTimers=Object.create(null),this.systemStatsInterval=null,this.sseReconnectTimeout=null,this._sseListenerCleanup=null,this.reconnectAttempts=0,this.maxReconnectAttempts=10,this.isOnline=navigator.onLine,this._connectionStatus="connected",this._clientId="",this._seqCounters=new Map,this._pendingDeliveries=new Map,this._postDraining=new Set,this._persistReliableTimer=null,this._reliableAckTimeoutMs=4e3,this._reliableMaxBytes=256*1024,this._loadReliableState(),this._reliableSweepTimer=setInterval(()=>this._redeliverSweep(),2e3),window.addEventListener("pagehide",()=>this._persistReliableNow()),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this._persistReliableNow()}),this._localEchoOverlay=null,this._localEchoEnabled=!1,this._restoringFlushedState=!1,this.activeFocusTrap=null,this.notificationManager=new NotificationManager(this),this.idleTimers=new Map,this._elemCache={},this.init()}$(e){return this._elemCache[e]||(this._elemCache[e]=document.getElementById(e)),this._elemCache[e]}_clearTimer(e){this[e]&&(clearTimeout(this[e]),this[e]=null)}_isStaleSelect(e){return e!==this._selectGeneration?(this._isLoadingBuffer&&this._finishBufferLoad(e),this._restoringFlushedState=!1,!0):!1}formatTokens(e){if(e>=1e6){const t=e/1e6;return t>=10?`${t.toFixed(1)}m`:`${t.toFixed(2)}m`}else if(e>=1e3){const t=e/1e3;return t>=100?`${t.toFixed(0)}k`:`${t.toFixed(1)}k`}return String(e)}estimateCost(e,t){const s=e/1e6*15,i=t/1e6*75;return s+i}setPendingHook(e,t){this.pendingHooks.has(e)||this.pendingHooks.set(e,new Set),this.pendingHooks.get(e).add(t),this.updateTabAlertFromHooks(e)}clearPendingHooks(e,t=null){const s=this.pendingHooks.get(e);s&&(t?s.delete(t):s.clear(),s.size===0&&this.pendingHooks.delete(e),this.updateTabAlertFromHooks(e))}updateTabAlertFromHooks(e){const t=this.pendingHooks.get(e);!t||t.size===0?this.tabAlerts.delete(e):t.has("permission_prompt")||t.has("elicitation_dialog")?this.tabAlerts.set(e,"action"):t.has("idle_prompt")&&this.tabAlerts.set(e,"idle"),this.renderSessionTabs()}init(){MobileDetection.init(),this._initWindowChannel(),this.isSoloWindow&&document.body.classList.add("solo-mode"),KeyboardHandler.init(),SwipeHandler.init(),VoiceInput.init(),KeyboardAccessoryBar.init(),this.loadAppSettingsFromStorage().extendedKeyboardBar&&KeyboardAccessoryBar.setMode("extended"),this.applyHeaderVisibilitySettings(),this.restorePlanUsageChip(),this.applySkin(),this.applyTabWrapSettings(),this.applyMonitorVisibility(),document.documentElement.classList.remove("mobile-init"),requestAnimationFrame(()=>{this.initTerminal(),this.loadFontSize(),this.connectSSE(),this._initFallbackTimer=setTimeout(()=>{this._initGeneration===0&&this.loadState()},3e3)}),this.registerServiceWorker(),this.loadTunnelStatus();const t=fetch("/api/settings").then(s=>s.ok?s.json():null).then(s=>s?.data??null).catch(()=>null);if(this.loadQuickStartCases(null,t),this._initRunMode(),this.setupEventListeners(),MobileDetection.isTouchDevice()){const s=i=>{i&&i.addEventListener("touchstart",n=>{if(!KeyboardHandler.keyboardVisible)return;const o=n.target.closest("button");o&&(n.preventDefault(),o.click(),typeof app<"u"&&app.terminal&&app.terminal.focus())},{passive:!1})};s(document.querySelector(".toolbar")),s(document.querySelector(".welcome-overlay"))}this.setupOnlineDetection(),this.loadAppSettingsFromServer(t).then(()=>{this.applyHeaderVisibilitySettings(),this.applySkin(),this.applyTabWrapSettings(),this.applyMonitorVisibility(),typeof this.syncAllUltracodeFloatingWindows=="function"&&this.syncAllUltracodeFloatingWindows()}),document.body.classList.add("app-loaded")}_initWebGL(){if(!(typeof WebglAddon>"u"))try{this._webglAddon=new WebglAddon.WebglAddon,this._webglAddon.onContextLoss(()=>{console.error("[CRASH-DIAG] WebGL context LOST \u2014 falling back to canvas renderer"),_crashDiag.log("WEBGL_LOST"),this._disableWebGLSticky("context-lost"),this._disposeWebGLObserver(),this._webglAddon?.dispose(),this._webglAddon=null,this._scheduleTerminalRepaint()}),this.terminal.loadAddon(this._webglAddon),console.log("[CRASH-DIAG] WebGL renderer enabled"),this._installWebGLLongTaskGuard()}catch{}}_installWebGLLongTaskGuard(){if(typeof PerformanceObserver>"u"||this._webglLongTaskObserver)return;const e=performance.now(),t=[];try{this._webglLongTaskObserver=new PerformanceObserver(s=>{if(!this._webglAddon)return;const i=performance.now();i-e<WEBGL_FALLBACK.GRACE_MS||evaluateWebGLLongTaskTrip(t,s.getEntries(),i)&&(console.warn(`[CRASH-DIAG] WebGL long-task threshold (${t.length} stalls/${WEBGL_FALLBACK.WINDOW_MS}ms) \u2014 falling back to canvas renderer`),_crashDiag.log(`WEBGL_FALLBACK: ${t.length}`),this._disableWebGLSticky("long-tasks"),this._disposeWebGLObserver(),this._webglAddon?.dispose(),this._webglAddon=null,this._scheduleTerminalRepaint())}),this._webglLongTaskObserver.observe({type:"longtask",buffered:!1})}catch{}}_disposeWebGLObserver(){if(this._webglLongTaskObserver){try{this._webglLongTaskObserver.disconnect()}catch{}this._webglLongTaskObserver=null}}_scheduleTerminalRepaint(){if(this._terminalRepaintScheduled)return;this._terminalRepaintScheduled=!0,(typeof requestAnimationFrame=="function"?requestAnimationFrame:t=>setTimeout(t,0))(()=>{this._terminalRepaintScheduled=!1;try{this.terminal?.refresh(0,this.terminal.rows-1)}catch{}})}_disableWebGLSticky(e){try{localStorage.setItem("codeman-webgl-disabled",JSON.stringify({reason:e,at:Date.now()}))}catch{}}setupEventListeners(){const e=[{key:"?",altKey:"/",ctrl:!0,action:()=>this.showHelp()},{key:"w",ctrl:!0,action:()=>this.killActiveSession()},{key:"Tab",ctrl:!0,action:()=>this.nextSession()},{key:"l",ctrl:!0,action:()=>this.clearTerminal()},{key:"R",ctrl:!0,shift:!0,action:()=>this.restoreTerminalSize()},{key:"=",altKey:"+",ctrl:!0,action:()=>this.increaseFontSize()},{key:"-",ctrl:!0,action:()=>this.decreaseFontSize()},{key:"V",ctrl:!0,shift:!0,action:()=>VoiceInput.toggle()},{key:"{",ctrl:!0,shift:!0,action:()=>this.moveActiveTabLeft()},{key:"}",ctrl:!0,shift:!0,action:()=>this.moveActiveTabRight()}];document.addEventListener("keydown",s=>{if(!(s.isComposing||s.keyCode===229)){if(s.key==="Escape"&&(this.closeAllPanels(),this.closeHelp(),this.attachmentHistoryDrawerOpen&&this.closeAttachmentHistory()),s.altKey&&!s.ctrlKey&&!s.shiftKey){const n=(s.code||"").match(/^Digit([1-9])$/);if(n){const o=parseInt(n[1],10)-1;o<this.sessionOrder.length&&(s.preventDefault(),this.selectSession(this.sessionOrder[o]));return}if(s.code==="BracketLeft"){s.preventDefault(),this.prevSession();return}if(s.code==="BracketRight"){s.preventDefault(),this.nextSession();return}}for(const i of e){const n=s.key===i.key||i.altKey&&s.key===i.altKey,o=i.ctrl?s.ctrlKey||s.metaKey:!0,a=i.shift?s.shiftKey:!s.shiftKey;if(n&&o&&a){s.preventDefault(),i.action();return}}}},!0);const t=this.$("headerTokens");t&&!t._statsHandlerAttached&&(t.classList.add("clickable"),t._statsHandlerAttached=!0,t.addEventListener("click",()=>this.openTokenStats())),this.setupColorPicker()}_updateSseSubscription(e){try{const t=JSON.stringify({clientId:this._clientId,sessions:e?[e]:null});fetch("/api/events/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:t,keepalive:!0}).catch(()=>{})}catch{}}_detectSoloSessionId(){try{if(typeof window<"u"&&typeof window.__CODEMAN_SOLO__=="string"&&window.__CODEMAN_SOLO__)return window.__CODEMAN_SOLO__;const e=location.pathname.match(/^\/session\/([^/]+)\/?$/);return e?decodeURIComponent(e[1]):null}catch{return null}}detachSession(e){if(this.isSoloWindow||!this.sessions.has(e)||this.detachedSessions.has(e)&&this._raiseDetached(e))return;const t="width=960,height=680,menubar=no,toolbar=no,location=no,status=no";let s=null;try{s=window.open("/session/"+encodeURIComponent(e),"codeman-session-"+e,t)}catch{}if(!s){this.showToast?.("Pop-out blocked \u2014 allow popups for this site to detach a session","error");return}this.detachedWindows.set(e,s),this._markDetached(e,!0),this._watchDetachedWindow(e,s),this._postWindowMessage({type:"detached",id:e});try{s.focus()}catch{}}_raiseDetached(e){const t=this.detachedWindows.get(e);if(t&&!t.closed){try{t.focus()}catch{}return!0}return t&&t.closed?(this._redock(e),!1):(this._postWindowMessage({type:"focus-request",id:e}),!0)}redockSession(e){const t=this.detachedWindows.get(e);if(t&&!t.closed)try{t.close()}catch{}this._postWindowMessage({type:"close-request",id:e}),this._redock(e)}_redock(e){const t=this._detachWatchTimers.get(e);t&&(clearInterval(t),this._detachWatchTimers.delete(e)),this._cancelPendingRedock(e),this._detachOrphanStrikes.delete(e),this.detachedWindows.delete(e),this._markDetached(e,!1)}_scheduleRedock(e){if(this._redockGrace.has(e))return;const t=setTimeout(()=>{this._redockGrace.delete(e),this._redock(e)},1500);this._redockGrace.set(e,t)}_cancelPendingRedock(e){const t=this._redockGrace.get(e);t&&(clearTimeout(t),this._redockGrace.delete(e))}_markDetached(e,t){t?this.detachedSessions.add(e):this.detachedSessions.delete(e);const s=this.$("sessionTabs"),i=s&&s.querySelector(`.session-tab[data-id="${e}"]`);i&&i.classList.toggle("detached",t)}_watchDetachedWindow(e,t){const s=this._detachWatchTimers.get(e);s&&clearInterval(s);const i=setInterval(()=>{(!t||t.closed)&&(clearInterval(i),this._detachWatchTimers.delete(e),this._redock(e))},800);this._detachWatchTimers.set(e,i)}_initWindowChannel(){if(!(typeof BroadcastChannel>"u")){try{this.windowChannel=new BroadcastChannel("codeman-windows")}catch{this.windowChannel=null;return}if(this.windowChannel.onmessage=e=>this._onWindowMessage(e.data),this.isSoloWindow){this._postWindowMessage({type:"detached",id:this.soloSessionId});const e=()=>this._postWindowMessage({type:"redocked",id:this.soloSessionId});window.addEventListener("pagehide",e),window.addEventListener("beforeunload",e)}else this._postWindowMessage({type:"roll-call"}),this._startDetachLiveness()}}_postWindowMessage(e){try{this.windowChannel&&this.windowChannel.postMessage(e)}catch{}}_onWindowMessage(e){if(!(!e||typeof e!="object")){if(this.isSoloWindow){if(e.type==="roll-call"){this._postWindowMessage({type:"detached",id:this.soloSessionId});return}if(e.id!==this.soloSessionId)return;if(e.type==="close-request")try{window.close()}catch{}else if(e.type==="focus-request")try{window.focus()}catch{}return}e.type==="detached"&&e.id?(this._cancelPendingRedock(e.id),this._detachPingPending?.delete(e.id),this._detachOrphanStrikes.delete(e.id),this._markDetached(e.id,!0)):e.type==="redocked"&&e.id?this._scheduleRedock(e.id):e.type==="detach-request"&&e.id&&this.detachSession(e.id)}}_startDetachLiveness(){this._detachLivenessTimer||(this._detachLivenessTimer=setInterval(()=>this._pingDetached(),5e3))}_pingDetached(){const e=[];for(const t of this.detachedSessions){const s=this.detachedWindows.get(t);s?s.closed&&this._redock(t):e.push(t)}e.length&&(this._detachPingPending=new Set(e),this._postWindowMessage({type:"roll-call"}),setTimeout(()=>{if(this._detachPingPending){for(const t of this._detachPingPending){const s=(this._detachOrphanStrikes.get(t)||0)+1;s>=2?(this._detachOrphanStrikes.delete(t),this._redock(t)):this._detachOrphanStrikes.set(t,s)}this._detachPingPending=null}},1200))}_applySoloMode(){document.body.classList.add("solo-mode");const e=this.sessions.get(this.soloSessionId);if(!e){this._showSoloSessionGone();return}this.activeSessionId=null,this.selectSession(this.soloSessionId);const t=this.getSessionName(e)||"Session",s=document.getElementById("soloSessionTitle");s&&(s.textContent=t,s.style.display="");const i=document.getElementById("soloRedockBtn");i&&(i.style.display=""),document.title=t+" \u2014 Codeman",this.notificationManager&&(this.notificationManager.originalTitle=document.title);const n=document.querySelector(".header-brand .logo");n&&(n.onclick=o=>{o.preventDefault()})}_showSoloSessionGone(){if(document.body.classList.add("solo-mode"),document.querySelector(".solo-gone-overlay"))return;const e=document.createElement("div");e.className="solo-gone-overlay",e.innerHTML='<h2>Session unavailable</h2><p>This session has ended or is no longer available.</p><button class="btn-primary" onclick="window.close()">Close window</button>',document.body.appendChild(e),document.title="Session ended \u2014 Codeman"}connectSSE(){if(!navigator.onLine){this.setConnectionStatus("offline");return}this._clearTimer("sseReconnectTimeout"),this._sseListenerCleanup&&(this._sseListenerCleanup(),this._sseListenerCleanup=null),this.eventSource&&(this.eventSource.close(),this.eventSource=null),this.reconnectAttempts===0?this.setConnectionStatus("connecting"):this.setConnectionStatus("reconnecting");const e=new URLSearchParams({clientId:this._clientId});this.activeSessionId&&e.set("sessions",this.activeSessionId),this.eventSource=new EventSource(`/api/events?${e.toString()}`);const t=[],s=(i,n)=>{this.eventSource.addEventListener(i,n),t.push({event:i,handler:n})};if(this._sseListenerCleanup=()=>{for(const{event:i,handler:n}of t)this.eventSource&&this.eventSource.removeEventListener(i,n);t.length=0},this.eventSource.onopen=()=>{this.reconnectAttempts=0,this.setConnectionStatus("connected")},this.eventSource.onerror=()=>{this.reconnectAttempts++,this.reconnectAttempts>=this.maxReconnectAttempts?this.setConnectionStatus("disconnected"):this.setConnectionStatus("reconnecting"),this.eventSource&&(this.eventSource.close(),this.eventSource=null),this._clearTimer("sseReconnectTimeout");const i=this.reconnectAttempts<=1?200:Math.min(500*Math.pow(2,this.reconnectAttempts-2),3e4);this.sseReconnectTimeout=setTimeout(()=>this.connectSSE(),i)},!this._sseHandlerWrappers){this._sseHandlerWrappers=new Map;for(const[i,n]of _SSE_HANDLER_MAP){const o=this[n];this._sseHandlerWrappers.set(i,a=>{try{o.call(this,a.data?JSON.parse(a.data):{})}catch(r){console.error(`[SSE] Error handling ${i}:`,r)}})}}for(const[i]of _SSE_HANDLER_MAP)s(i,this._sseHandlerWrappers.get(i))}_onInit(e){_crashDiag.log(`INIT: ${e.sessions?.length||0} sessions`),this.handleInit(e)}_onSessionCreated(e){this.sessions.set(e.id,e),this.sessionOrder.includes(e.id)||(this.sessionOrder.push(e.id),this.saveSessionOrder()),this.renderSessionTabs(),this.updateCost(),this.sessions.size===1&&this.startSystemStatsPolling()}_onSessionUpdated(e){const t=e.session||e,s=this.sessions.get(t.id),i=t.claudeSessionId&&(!s||!s.claudeSessionId);this.sessions.set(t.id,t),this.renderSessionTabs(),this.updateCost(),t.id===this.activeSessionId&&t.tokens&&this.updateRespawnTokens(t.tokens),this.updateSubagentParentNames(t.id),i&&(this.recheckOrphanSubagents(),requestAnimationFrame(()=>{this.updateConnectionLines()}))}_onSessionDeleted(e){if(this._wsSessionId===e.id&&this._disconnectWs(),this.isSoloWindow&&e.id===this.soloSessionId&&this._showSoloSessionGone(),this.detachedSessions.has(e.id)&&this._redock(e.id),this._cleanupSessionData(e.id),this.activeSessionId===e.id){this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.terminal.clear(),this.showWelcome()}this.renderSessionTabs(),this.renderRalphStatePanel(),this.renderProjectInsightsPanel(),this.sessions.size===0&&this.stopSystemStatsPolling()}_onSSETerminal(e){this._wsReady&&this._wsSessionId===e.id||this._onSessionTerminal(e)}_onSSENeedsRefresh(e){this._wsReady&&this._wsSessionId===e?.id||this._onSessionNeedsRefresh(e)}_onSSEClearTerminal(e){this._wsReady&&this._wsSessionId===e?.id||this._onSessionClearTerminal(e)}_onSessionTerminal(e){if(e.id===this.activeSessionId){if(e.data.length>32768&&_crashDiag.log(`TERMINAL: ${(e.data.length/1024).toFixed(0)}KB`),(this.pendingWrites?.reduce((s,i)=>s+i.length,0)||0)+(this.flickerFilterBuffer?.length||0)>131072){this._clientDropRecoveryTimer||(this._clientDropRecoveryTimer=setTimeout(()=>{this._clientDropRecoveryTimer=null,this._onSessionNeedsRefresh()},2e3));return}this.batchTerminalWrite(e.data)}}_sanitizeHtml(e){return typeof window<"u"&&typeof window.sanitizeMarkdownHtml=="function"?window.sanitizeMarkdownHtml(e):String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}_cleanTerminalBuffer(e){const t=e.replace(/\x1b\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]/g,"").replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g,"").replace(/\x1b[PX^_][^\x1b]*\x1b\\/g,"").replace(/\x1b[NO()][A-Z0-9]?/g,"").replace(/\x1b[>=<78cDEHM]/g,"").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g,"").replace(/\r\n/g,`
8
+ `).replace(/\r/g,`
9
+ `),s=[/^\s*❯\s*/,/^\s*[⏵⏺⏸⏹]+\s*/,/^\s*✻\s*(Crunching|Crunched|Thinking)/i,/bypass permissions/i,/\bshift\+tab to cycle\b/i,/^\s*focus\s*$/,/^\s*new task\?/i,/\/clear to save/i,/^\s*─{5,}\s*$/,/\[(Opus|Sonnet|Haiku|GPT|Claude)[\s\S]*(tokens?|\$|¥|%|↑|↓)/i,/^\s*\[\d+[km]?\/\d+[km]?\]/i,/[█░▓▒]{3,}/,/^\s*\(.*\s*(tokens?|context).*\)\s*$/i];return t.split(`
10
+ `).filter(o=>o.trim()?!s.some(r=>r.test(o)):!0).join(`
11
+ `).replace(/[ \t]+$/gm,"").replace(/\n{4,}/g,`
12
+
13
+
14
+ `).trim()}_preprocessAsciiArt(e){const t=/[─-╿▀-▟]/,s=/```[\s\S]*?```/g,i=[];return e.replace(s,a=>(i.push(a),`__CODEMAN_FENCE_${i.length-1}__`)).split(/(\n{2,})/).map(a=>/^\n{2,}$/.test(a)||!a.trim()||a.includes("__CODEMAN_FENCE_")?a:t.test(a)?"\n```\n"+a+"\n```\n":a).join("").replace(/__CODEMAN_FENCE_(\d+)__/g,(a,r)=>i[Number(r)])}_renderMarkdown(e){const t=e||"";if(typeof marked<"u"&&marked.parse)try{const i=this._preprocessAsciiArt(t);let n=this._sanitizeHtml(marked.parse(i,{breaks:!0,gfm:!0}));n=n.replace(/<table>/g,'<div class="rv-table-wrap"><table>').replace(/<\/table>/g,"</table></div>");const o=/[─-╿▀-▟]/,a=document.createElement("template");return a.innerHTML=n,a.content.querySelectorAll("pre > code").forEach(r=>{const l=r.parentElement,d=o.test(r.textContent||""),c=document.createElement("div");c.className=d?"rv-code-wrap rv-diagram-wrap":"rv-code-wrap";const p=document.createElement("div");p.className="rv-code-actions";const g=document.createElement("button");if(g.className="rv-copy-btn",g.type="button",g.setAttribute("aria-label","Copy code"),g.setAttribute("title","Copy code"),p.appendChild(g),d){l.classList.add("rv-diagram");const _=document.createElement("button");_.className="rv-wrap-toggle",_.type="button",_.setAttribute("aria-label","Toggle line wrapping"),_.setAttribute("title","Toggle line wrapping"),p.appendChild(_)}l.parentNode.insertBefore(c,l),c.appendChild(p),c.appendChild(l)}),a.innerHTML}catch{}return`<pre style="white-space:pre-wrap;word-break:break-word">${t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}</pre>`}_bindResponseViewerInteractions(e){!e||e.dataset.rvBound==="1"||(e.dataset.rvBound="1",e.addEventListener("click",async t=>{const s=t.target.closest(".rv-copy-btn");if(s){t.preventDefault(),t.stopPropagation();const r=s.closest(".rv-code-wrap")?.querySelector("pre code"),l=r?await this._copyText(r.textContent||""):!1;s.classList.remove("rv-copied","rv-copy-failed"),s.classList.add(l?"rv-copied":"rv-copy-failed"),clearTimeout(s._resetTimer),s._resetTimer=setTimeout(()=>{s.classList.remove("rv-copied","rv-copy-failed")},1500);return}const i=t.target.closest(".rv-wrap-toggle");if(!i)return;t.preventDefault(),t.stopPropagation();const n=i.closest(".rv-diagram-wrap"),o=n?.querySelector("pre.rv-diagram");if(!o||!n)return;const a=o.classList.toggle("rv-nowrap");n.classList.toggle("rv-wrap-nowrap",a)}))}async _copyText(e){if(!e)return!1;try{if(navigator.clipboard?.writeText)return await navigator.clipboard.writeText(e),!0}catch{}try{const t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.cssText="position:fixed;top:0;left:0;opacity:0;pointer-events:none",document.body.appendChild(t),t.select();const s=document.execCommand("copy");return document.body.removeChild(t),s}catch{return!1}}async toggleResponseViewer(){const e=document.getElementById("responseViewer"),t=document.getElementById("responseViewerBackdrop");if(!e)return;if(e.classList.contains("visible")){e.classList.remove("visible"),t.classList.remove("visible");return}if(this.activeSessionId)try{let o=((await(await fetch(`/api/sessions/${this.activeSessionId}/last-response`)).json())?.data??{}).text||"";if(!o){const c=(await(await fetch(`/api/sessions/${this.activeSessionId}/terminal`)).json())?.data??{};c.terminalBuffer&&(o=this._cleanTerminalBuffer(c.terminalBuffer))}const a=document.getElementById("responseViewerBody");a.innerHTML=this._renderMarkdown(o),this._bindResponseViewerInteractions(a);const r=document.getElementById("responseViewerTitle"),l=document.getElementById("responseViewerMore");r&&(r.textContent="Last Response"),l&&(l.style.display="",l.textContent="More"),e.classList.add("visible"),t.classList.add("visible"),a.scrollTop=0}catch(i){console.error("Failed to load response:",i)}}async loadFullContext(){if(!this.activeSessionId)return;const e=document.getElementById("responseViewerMore");e&&(e.textContent="...");try{const i=((await(await fetch(`/api/sessions/${this.activeSessionId}/last-response?context=full`)).json())?.data??{}).messages||[],n=document.getElementById("responseViewerBody"),o=document.getElementById("responseViewerTitle");if(!n)return;if(i.length===0){n.textContent="No conversation history available";return}n.innerHTML="";for(const a of i){const r=document.createElement("div"),l=a.role==="user";r.className="rv-message "+(l?"rv-msg-user":"rv-msg-assistant");const d=document.createElement("div");d.className="rv-role "+(l?"rv-role-user":"rv-role-assistant"),d.textContent=l?"You":"Claude",r.appendChild(d);const c=document.createElement("div");c.className="rv-text",c.innerHTML=this._renderMarkdown(a.text),r.appendChild(c),n.appendChild(r)}this._bindResponseViewerInteractions(n),o&&(o.textContent=`Conversation (${i.length} messages)`),e&&(e.style.display="none"),n.scrollTop=n.scrollHeight}catch(t){console.error("Failed to load context:",t)}finally{e&&(e.textContent="More")}}async _onSessionNeedsRefresh(){if(!(!this.activeSessionId||!this.terminal)&&!this._isLoadingBuffer)try{const t=(await(await fetch(`/api/sessions/${this.activeSessionId}/terminal?tail=${TERMINAL_TAIL_SIZE}`)).json())?.data??{};t.terminalBuffer&&(this.terminal.clear(),this.terminal.reset(),await this.chunkedTerminalWrite(t.terminalBuffer),this.terminal.scrollToBottom(),this._localEchoOverlay?.rerender(),this.activeSessionId&&this.sendResize(this.activeSessionId))}catch(e){console.error("needsRefresh reload failed:",e)}}async _onSessionClearTerminal(e){if(e.id===this.activeSessionId){if(this._isLoadingBuffer)return;try{const s=(await(await fetch(`/api/sessions/${e.id}/terminal`)).json())?.data??{};if(this.terminal.clear(),this.terminal.reset(),s.terminalBuffer){const i=s.terminalBuffer.replace(DEC_SYNC_STRIP_RE,"");await this.chunkedTerminalWrite(i)}this.sendResize(e.id),this._localEchoOverlay?.rerender()}catch(t){console.error("clearTerminal refresh failed:",t)}}}_onSessionCompletion(e){this.totalCost+=e.cost||0,this.updateCost(),e.id===this.activeSessionId&&(this.terminal.writeln(""),this.terminal.writeln(`\x1B[1;32m Done (Cost: $${(e.cost||0).toFixed(4)})\x1B[0m`))}_onSessionError(e){e.id===this.activeSessionId&&this.terminal.writeln(`\x1B[1;31m Error: ${e.error}\x1B[0m`),this._notifySession(e.id,"critical","session-error","Session Error",e.error||"Unknown error")}_onSessionExit(e){this._wsSessionId===e.id&&this._disconnectWs();const t=this.sessions.get(e.id);t&&(t.status="stopped",this.renderSessionTabs(),e.id===this.activeSessionId&&this._updateLocalEchoState()),e.code&&e.code!==0&&this._notifySession(e.id,"critical","session-crash","Session Crashed",`Exited with code ${e.code}`)}_onSessionIdle(e){const t=this.sessions.get(e.id);if(t&&(t.status="idle",this.renderSessionTabs(),this.sendPendingCtrlL(e.id),e.id===this.activeSessionId&&this._updateLocalEchoState()),!this.respawnStatus[e.id]?.enabled){const s=this.notificationManager?.preferences?.stuckThresholdMs||6e5;clearTimeout(this.idleTimers.get(e.id)),this.idleTimers.set(e.id,setTimeout(()=>{this._notifySession(e.id,"warning","session-stuck","Session Idle",`Idle for ${Math.round(s/6e4)}+ minutes`),this.idleTimers.delete(e.id)},s))}}_onSessionWorking(e){const t=this.sessions.get(e.id);t&&(t.status="busy",this.pendingHooks.has(e.id)||this.tabAlerts.delete(e.id),this.renderSessionTabs(),this.sendPendingCtrlL(e.id),e.id===this.activeSessionId&&this._updateLocalEchoState());const s=this.idleTimers.get(e.id);s&&(clearTimeout(s),this.idleTimers.delete(e.id))}_onSessionAutoClear(e){e.sessionId===this.activeSessionId&&(this.showToast(`Auto-cleared at ${e.tokens.toLocaleString()} tokens`,"info"),this.updateRespawnTokens(0)),this._notifySession(e.sessionId,"info","auto-clear","Auto-Cleared",`Context reset at ${(e.tokens||0).toLocaleString()} tokens`)}_onSessionLimitPauseScheduled(e){const t=this.sessions.get(e.sessionId);t&&(t.autoResumeAt=e.resumeAt);const s=new Date(e.resumeAt).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});e.sessionId===this.activeSessionId&&this.showToast(`Usage limit reached \u2014 auto-resume at ${s}`,"warning"),this._notifySession(e.sessionId,"warning","limit-pause","Usage Limit Reached",`Auto-resume scheduled for ${s}`),this.updateAutoResumeStatus(e.sessionId)}_onSessionLimitResume(e){const t=this.sessions.get(e.sessionId);t&&(t.autoResumeAt=void 0),e.sessionId===this.activeSessionId&&this.showToast("Usage limit reset \u2014 work resumed automatically","success"),this._notifySession(e.sessionId,"info","limit-resume","Auto-Resumed","Usage limit reset \u2014 continuing work"),this.updateAutoResumeStatus(e.sessionId)}_onSessionLimitResumeCancelled(e){const t=this.sessions.get(e.sessionId);t&&(t.autoResumeAt=void 0),this.updateAutoResumeStatus(e.sessionId)}_onSessionCliInfo(e){const t=this.sessions.get(e.sessionId);t&&(e.version&&(t.cliVersion=e.version),e.model&&(t.cliModel=e.model),e.accountType&&(t.cliAccountType=e.accountType),e.latestVersion&&(t.cliLatestVersion=e.latestVersion)),e.sessionId===this.activeSessionId&&this.updateCliInfoDisplay()}_onSessionStatusTelemetry(e){this.updatePlanUsageChip(e);try{localStorage.setItem("codeman:planUsage",JSON.stringify({t:Date.now(),data:e}))}catch{}}restorePlanUsageChip(){try{const e=localStorage.getItem("codeman:planUsage");if(!e)return;const t=JSON.parse(e);t?.data&&Date.now()-(t.t||0)<12*3600*1e3&&this.updatePlanUsageChip(t.data)}catch{}}updatePlanUsageChip(e){const t=document.getElementById("planUsageChip");if(!t||!e)return;const s=l=>l&&typeof l.usedPercentage=="number"?Math.round(l.usedPercentage):null,i=s(e.fiveHour),n=s(e.sevenDay);if(i===null&&n===null)return;const o=l=>l>=85?"pu-red":l>=60?"pu-yellow":"pu-green",a=(l,d)=>{if(d===null)return"";const c=Math.round(Number(d));return Number.isFinite(c)?`<span class="pu-win"><span class="pu-label">${l}</span><span class="pu-val ${o(c)}">${c}%</span></span>`:""};t.innerHTML=[a("5h",i),a("7d",n)].filter(Boolean).join('<span class="pu-sep">\xB7</span>');const r=l=>l&&l.resetAt?new Date(l.resetAt).toLocaleString():"\u2014";t.title=`Claude plan usage
15
+ 5-hour limit: ${i??"\u2014"}% used (resets ${r(e.fiveHour)})
16
+ Weekly limit: ${n??"\u2014"}% used (resets ${r(e.sevenDay)})`}_onScheduledCreated(e){this.currentRun=e,this.showTimer()}_onScheduledUpdated(e){this.currentRun=e,this.updateTimer()}_onScheduledCompleted(e){this.currentRun=e,this.hideTimer(),this.showToast("Scheduled run completed!","success")}_onScheduledStopped(){this.currentRun=null,this.hideTimer()}setConnectionStatus(e){this._connectionStatus=e,this._updateConnectionIndicator(),e==="connected"&&this._redeliverSweep()}_connectWs(e){this._disconnectWs();const s=`${location.protocol==="https:"?"wss:":"ws:"}//${location.host}/ws/sessions/${e}/terminal`,i=new WebSocket(s);this._ws=i,this._wsSessionId=e,i.onopen=()=>{this._ws===i&&(this._wsReady=!0,this._wsReconnectAttempts=0,this.sendResize(e)?.catch?.(()=>{}),this._startMobileResizeRetry(e),this._onWsReady(e))},i.onmessage=n=>{if(this._ws===i)try{const o=JSON.parse(n.data);o.t==="o"?this._onSessionTerminal({id:e,data:o.d}):o.t==="c"?this._onSessionClearTerminal({id:e}):o.t==="r"?this._onSessionNeedsRefresh({id:e}):o.t==="ia"&&this._onWsInputAck(o.seq)}catch{}},i.onclose=n=>{if(this._ws===i&&(this._ws=null,this._wsSessionId=null,this._wsReady=!1,this._stopMobileResizeRetry(),n.code<4004&&this.activeSessionId===e)){const o=Math.min(1e3*Math.pow(2,this._wsReconnectAttempts||0),1e4);this._wsReconnectAttempts=(this._wsReconnectAttempts||0)+1,this._wsReconnectTimer=setTimeout(()=>{this._wsReconnectTimer=null,this.activeSessionId===e&&this._connectWs(e)},o)}},i.onerror=()=>{}}_disconnectWs(){this._clearTimer("_wsReconnectTimer"),this._wsReconnectAttempts=0,this._stopMobileResizeRetry(),this._ws&&(this._ws.onclose=null,this._ws.close(),this._ws=null,this._wsSessionId=null,this._wsReady=!1)}_startMobileResizeRetry(e){this._stopMobileResizeRetry(),(typeof MobileDetection<"u"&&MobileDetection.getDeviceType?MobileDetection.getDeviceType():"desktop")!=="desktop"&&(this._mobileResizeRetryTimer=setInterval(()=>{document.visibilityState==="visible"&&(!this._wsReady||this._wsSessionId!==e||typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible||this.sendResize(e)?.catch?.(()=>{}))},MOBILE_RESIZE_RETRY_MS))}_stopMobileResizeRetry(){this._mobileResizeRetryTimer&&(clearInterval(this._mobileResizeRetryTimer),this._mobileResizeRetryTimer=null)}_sendInputAsync(e,t,s){!e||!t||this._reliableSend(e,t,s?.useMux===!0)}_reliableSend(e,t,s){const n={seq:this._nextSeq(e),data:t,useMux:!!s,ts:Date.now(),tries:0,sentAt:0};let o=this._pendingDeliveries.get(e);o||(o=[],this._pendingDeliveries.set(e,o)),o.push(n),this._persistReliableState(),this._updateConnectionIndicator(),this._drainSession(e)}_nextSeq(e){const t=(this._seqCounters.get(e)||0)+1;return this._seqCounters.set(e,t),t}_drainSession(e){const t=this._pendingDeliveries.get(e);if(!(!t||t.length===0)){if(this._ws&&this._ws.readyState===WebSocket.OPEN&&this._wsSessionId===e){for(const s of t)if(s.sentAt===0)try{this._ws.send(JSON.stringify({t:"i",d:s.data,seq:s.seq,cid:this._clientId})),s.sentAt=Date.now(),s.tries++}catch{break}return}this._postDraining.has(e)||(this._postDraining.add(e),(async()=>{try{for(;;){const s=this._pendingDeliveries.get(e);if(!s||s.length===0||this._ws&&this._ws.readyState===WebSocket.OPEN&&this._wsSessionId===e)break;const i=s[0];i.tries++,i.sentAt=Date.now();let n=null;try{const o={input:i.data,seq:i.seq,clientId:this._clientId};i.useMux&&(o.useMux=!0),n=await fetch(`/api/sessions/${e}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o),keepalive:i.data.length<65536})}catch{n=null}if(n&&n.ok)this._ackDelivery(e,i.seq);else if(n&&(n.status===404||n.status===410))this._ackDelivery(e,i.seq);else break}}finally{this._postDraining.delete(e)}})())}}_ackDelivery(e,t){const s=this._pendingDeliveries.get(e);if(s){const i=s.findIndex(n=>n.seq===t);i!==-1&&(s.splice(i,1),s.length===0&&this._pendingDeliveries.delete(e),this._pendingDeliveries.size===0?this._persistReliableNow():this._persistReliableState(),this._updateConnectionIndicator())}this.clearPendingHooks?.(e)}_onWsInputAck(e){this._wsSessionId&&Number.isInteger(e)&&this._ackDelivery(this._wsSessionId,e)}_onWsReady(e){const t=this._pendingDeliveries.get(e);if(t)for(const s of t)s.sentAt=0;this._drainSession(e)}_redeliverSweep(){if(this._pendingDeliveries.size!==0)for(const e of[...this._pendingDeliveries.keys()]){const t=this._pendingDeliveries.get(e);if(!t||t.length===0)continue;if(this._ws&&this._ws.readyState===WebSocket.OPEN&&this._wsSessionId===e){const i=t[0];if(i&&i.sentAt&&Date.now()-i.sentAt>this._reliableAckTimeoutMs){try{this._ws.close()}catch{}continue}}this._drainSession(e)}}_pendingBytes(){let e=0,t=0;for(const s of this._pendingDeliveries.values())for(const i of s)e+=i.data.length,t++;return{bytes:e,count:t}}_loadReliableState(){try{this._clientId=localStorage.getItem("codeman:clientId")||""}catch{this._clientId=""}if(!this._clientId){this._clientId="c-"+Math.random().toString(36).slice(2)+"-"+Date.now().toString(36);try{localStorage.setItem("codeman:clientId",this._clientId)}catch{}}try{const e=localStorage.getItem("codeman:pendingInput");if(!e)return;const t=JSON.parse(e);if(t&&t.seqs)for(const[s,i]of Object.entries(t.seqs))Number.isFinite(i)&&this._seqCounters.set(s,i);if(t&&t.pending)for(const[s,i]of Object.entries(t.pending))Array.isArray(i)&&i.length&&this._pendingDeliveries.set(s,i.filter(n=>n&&typeof n.data=="string"&&Number.isInteger(n.seq)).map(n=>({seq:n.seq,data:n.data,useMux:!!n.useMux,ts:n.ts||Date.now(),tries:0,sentAt:0})))}catch{}}_persistReliableState(){this._persistReliableTimer||(this._persistReliableTimer=setTimeout(()=>{this._persistReliableTimer=null,this._persistReliableNow()},250))}_persistReliableNow(){this._persistReliableTimer&&(clearTimeout(this._persistReliableTimer),this._persistReliableTimer=null);try{const e={};for(const[n,o]of this._seqCounters)e[n]=o;const t={};let s=0;for(const[n,o]of this._pendingDeliveries)if(o.length){t[n]=o.map(a=>({seq:a.seq,data:a.data,useMux:a.useMux,ts:a.ts,tries:a.tries}));for(const a of o)s+=a.data.length}const i=s>this._reliableMaxBytes?{seqs:e}:{seqs:e,pending:t};localStorage.setItem("codeman:pendingInput",JSON.stringify(i))}catch{}}_updateConnectionIndicator(){const e=this.$("connectionIndicator"),t=this.$("connectionDot"),s=this.$("connectionText");if(!e||!t||!s)return;const{bytes:i,count:n}=this._pendingBytes(),o=this._connectionStatus,a=n>0;if((o==="connected"||o==="connecting")&&!a){e.style.display="none";return}e.style.display="flex",t.className="connection-dot";const r=l=>l<1024?`${l}B`:`${(l/1024).toFixed(1)}KB`;o==="connected"&&a?(t.classList.add("draining"),s.textContent=`Sending ${r(i)}...`):o==="reconnecting"?(t.classList.add("reconnecting"),s.textContent=a?`Reconnecting (${r(i)} queued)`:"Reconnecting..."):(t.classList.add("offline"),s.textContent=a?`Offline (${r(i)} queued)`:"Offline")}setupOnlineDetection(){window.addEventListener("online",()=>{this.isOnline=!0,this.reconnectAttempts=0,this.connectSSE(),this._redeliverSweep()}),window.addEventListener("offline",()=>{this.isOnline=!1,this.setConnectionStatus("offline")})}_updateCjkInputState(){const e=document.getElementById("cjkInput");if(!e)return;const t=this.loadAppSettingsFromStorage(),s=this.getDefaultSettings?.()||{},n=(this._serverCjkOverride||(t.cjkInputEnabled??s.cjkInputEnabled??!1))&&!!this.activeSessionId;e.classList.toggle("cjk-input-visible",!!n),document.body.classList.toggle("cjk-input-visible",!!n),e.style.display=n?"block":"none",e.setAttribute("aria-hidden",n?"false":"true"),n||(window.cjkActive=!1),typeof KeyboardHandler<"u"&&KeyboardHandler.updateLayoutForKeyboard()}_resetAllAppState(){this.sessions.clear(),this.ralphStates.clear(),this.terminalBuffers.clear(),this.terminalBufferCache.clear(),this._xtermSnapshots?.clear(),this.projectInsights.clear(),this.teams.clear(),this.teamTasks.clear();for(const e of this.idleTimers.values())clearTimeout(e);if(this.idleTimers.clear(),this._clearTimer("flickerFilterTimeout"),this.flickerFilterBuffer="",this.flickerFilterActive=!1,this._clearTimer("syncWaitTimeout"),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._bufferLoadOwner=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1,this._localEchoOverlay?.rerender(),this.pendingHooks.clear(),this._parentNameCache&&this._parentNameCache.clear(),this.subagentActivity.clear(),this.subagentToolResults.clear(),this.workflowRuns&&this.workflowRuns.clear(),this.workflowRunDetails&&this.workflowRunDetails.clear(),this.activeWorkflowRunId=null,this.activeWorkflowPhaseIndex=null,MobileDetection.cleanup(),KeyboardHandler.cleanup(),MobileDetection.init(),KeyboardHandler.init(),this.tabAlerts.clear(),this.attachmentHistoryCounts.clear(),this._shownCompletions&&this._shownCompletions.clear(),this.notificationManager?.titleFlashInterval&&(clearInterval(this.notificationManager.titleFlashInterval),this.notificationManager.titleFlashInterval=null),this.notificationManager?.groupingMap){for(const{timeout:e}of this.notificationManager.groupingMap.values())clearTimeout(e);this.notificationManager.groupingMap.clear()}this.terminalResizeObserver&&(this.terminalResizeObserver.disconnect(),this.terminalResizeObserver=null),this.planLoadingTimer&&(clearInterval(this.planLoadingTimer),this.planLoadingTimer=null),this.timerCountdownInterval&&(clearInterval(this.timerCountdownInterval),this.timerCountdownInterval=null),this.runSummaryAutoRefreshTimer&&(clearInterval(this.runSummaryAutoRefreshTimer),this.runSummaryAutoRefreshTimer=null)}handleInit(e){this._clearTimer("_initFallbackTimer");const t=++this._initGeneration;if(this._serverCjkOverride=e.inputCjkForm||!1,this._updateCjkInputState(),e.planUsage&&this.updatePlanUsageChip(e.planUsage),e.version){const n=this.$("versionDisplay"),o=this.$("headerVersion");n&&(n.textContent=`v${e.version}`,n.title=`Codeman v${e.version}`),o&&(o.textContent=`v${e.version}`,o.title=`Codeman v${e.version}`)}VoiceInput.cleanup(),this._resetAllAppState(),e.sessions.forEach(n=>{this.sessions.set(n.id,n),(n.ralphLoop||n.ralphTodos)&&!this.ralphClosedSessions.has(n.id)&&this.ralphStates.set(n.id,{loop:n.ralphLoop||null,todos:n.ralphTodos||[]})});try{localStorage.removeItem("codeman-tab-meta")}catch{}this.syncSessionOrder(),e.respawnStatus?this.respawnStatus=e.respawnStatus:this.respawnStatus={},this.respawnTimers={},this.respawnCountdownTimers={},this.respawnActionLogs={},e.globalStats&&(this.globalStats=e.globalStats),this.totalCost=e.sessions.reduce((n,o)=>n+(o.totalCost||0),0),this.totalCost+=e.scheduledRuns.reduce((n,o)=>n+(o.totalCost||0),0);const s=e.scheduledRuns.find(n=>n.status==="running");if(s&&(this.currentRun=s,this.showTimer()),this.updateCost(),this.renderSessionTabs(),this.sessions.size>0?this.startSystemStatsPolling():this.stopSystemStatsPolling(),this.cleanupAllFloatingWindows(),e.subagents&&(this.subagents.clear(),this.subagentActivity.clear(),this.subagentToolResults.clear(),e.subagents.forEach(n=>{this.subagents.set(n.agentId,n)}),this.renderSubagentPanel(),this.subagentParentMap.clear(),this.loadSubagentParentMap().then(()=>{for(const[n,o]of this.subagentParentMap){const a=this.subagents.get(n);if(a&&this.sessions.has(o)){a.parentSessionId=o;const r=this.sessions.get(o);r&&(a.parentSessionName=this.getSessionName(r)),this.subagents.set(n,a)}}for(const[n]of this.subagents)this.subagentParentMap.has(n)||this.findParentSessionForSubagent(n);this.restoreSubagentWindowStates()})),e.workflowRuns&&this.seedWorkflowRuns(e.workflowRuns),t!==this._initGeneration)return;if(this.isSoloWindow){this._applySoloMode();return}const i=this.activeSessionId;if(this.activeSessionId=null,this.sessionOrder.length>0){let n=i;if(!n||!this.sessions.has(n))try{n=localStorage.getItem("codeman-active-session")}catch{}n&&this.sessions.has(n)?this.selectSession(n):this.selectSession(this.sessionOrder[0])}}async loadState(){try{const t=await(await fetch("/api/status")).json();this.handleInit(t?.data??{})}catch(e){console.error("Failed to load state:",e)}}_debouncedCall(e,t,s=100){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{this._debounceTimers[e]=null,t.call(this)},s)}renderSessionTabs(){this._inlineRenameActive||this._debouncedCall("sessionTabs",this._renderSessionTabsImmediate)}_updateActiveTabImmediate(e){const t=this.$("sessionTabs");if(!t)return;const s=t.querySelectorAll(".session-tab[data-id]");for(const i of s)i.dataset.id===e?i.classList.add("active"):i.classList.remove("active")}_setTerminalLoadState(e,t,s){this.terminalLoadStates.set(e,{generation:t,phase:s}),this._updateTerminalLoadTab(e)}_clearTerminalLoadState(e,t){const s=this.terminalLoadStates.get(e);s&&s.generation!==t||(this.terminalLoadStates.delete(e),this._updateTerminalLoadTab(e))}_updateTerminalLoadTab(e){const t=this.$("sessionTabs")?.querySelector(`.session-tab[data-id="${e}"]`);if(!t)return;const s=this.terminalLoadStates.get(e);if(t.classList.toggle("tab-loading",!!s),s){if(t.setAttribute("aria-busy","true"),t.dataset.loadPhase=s.phase,!t.querySelector(".tab-load-spinner")){const i=document.createElement("span");i.className="tab-load-spinner",i.setAttribute("aria-hidden","true");const n=t.querySelector(".tab-number");n?n.insertAdjacentElement("afterend",i):t.insertBefore(i,t.firstChild)}}else t.setAttribute("aria-busy","false"),delete t.dataset.loadPhase,t.querySelector(".tab-load-spinner")?.remove()}_renderSessionTabsImmediate(){const e=this.$("sessionTabs"),t=e.querySelectorAll(".session-tab[data-id]"),s=new Set([...t].map(o=>o.dataset.id)),i=new Set(this.sessions.keys());if(s.size===i.size&&[...s].every(o=>i.has(o)))for(const[o,a]of this.sessions){const r=e.querySelector(`.session-tab[data-id="${o}"]`);if(!r)continue;const l=o===this.activeSessionId,d=a.status||"idle",c=this.getSessionName(a),p=a.taskStats||{running:0,total:0},g=p.running>0,_=this.terminalLoadStates.get(o);if(l&&!r.classList.contains("active")?r.classList.add("active"):!l&&r.classList.contains("active")&&r.classList.remove("active"),r.classList.toggle("tab-loading",!!_),_){if(r.setAttribute("aria-busy","true"),r.dataset.loadPhase=_.phase,!r.querySelector(".tab-load-spinner")){const S=document.createElement("span");S.className="tab-load-spinner",S.setAttribute("aria-hidden","true");const b=r.querySelector(".tab-number");b?b.insertAdjacentElement("afterend",S):r.insertBefore(S,r.firstChild)}}else r.setAttribute("aria-busy","false"),delete r.dataset.loadPhase,r.querySelector(".tab-load-spinner")?.remove();const w=this.tabAlerts.get(o),y=w==="action",R=w==="idle",E=r.classList.contains("tab-alert-action"),A=r.classList.contains("tab-alert-idle");if(y&&!E?(r.classList.add("tab-alert-action"),r.classList.remove("tab-alert-idle")):R&&!A?(r.classList.add("tab-alert-idle"),r.classList.remove("tab-alert-action")):!w&&(E||A)&&r.classList.remove("tab-alert-action","tab-alert-idle"),!r.querySelector(".tab-number")){const S=this.sessionOrder.indexOf(o);if(S>=0&&S<9){const b=document.createElement("span");b.className="tab-number",b.textContent=String(S+1),r.insertBefore(b,r.firstChild)}}const u=r.querySelector(".tab-status");u&&!u.classList.contains(d)&&(u.className=`tab-status ${d}`);const m=r.querySelector(".tab-name");if(m&&m.textContent!==c){const S=parseSessionPrefix(c);S&&S.suffix?m.innerHTML='<span class="tab-prefix">'+escapeHtml(S.prefix)+'</span><span class="tab-suffix">: '+escapeHtml(S.suffix)+"</span>":m.textContent=c}const C=r.querySelector(".tab-badge");if(g)if(C)C.textContent!==String(p.running)&&(C.textContent=p.running);else{this._fullRenderSessionTabs();return}else if(C){this._fullRenderSessionTabs();return}const f=r.querySelector(".tab-subagent-badge"),T=this.minimizedSubagents.get(o),v=T?.size||0;if(v>0&&f){const S=f.querySelector(".subagent-label"),b=v===1?"AGENT":`AGENTS (${v})`;S&&S.textContent!==b&&(S.textContent=b);const O=f.querySelector(".subagent-dropdown");if(O){const I=this.renderSubagentTabBadge(o,T),L=document.createElement("div");L.innerHTML=I;const k=L.querySelector(".subagent-dropdown");k&&(O.innerHTML=k.innerHTML)}}else if(v>0&&!f){const S=this.renderSubagentTabBadge(o,T),b=r.querySelector(".tab-gear");b&&b.insertAdjacentHTML("beforebegin",S)}else v===0&&f&&f.remove()}else this._fullRenderSessionTabs();this.updateTabOverflowMode()}updateTabOverflowMode(){const e=this.$("sessionTabs");if(!e)return;const t=MobileDetection.getDeviceType(),s=this.loadAppSettingsFromStorage(),i=this.getDefaultSettings(),n=t==="desktop"?s.tabTwoRows??i.tabTwoRows??!1:!1;if(n||t!=="desktop"){e.classList.remove("tabs-auto-wrap");return}e.classList.remove("tabs-auto-wrap");const o=window.CodemanTabOverflow?.shouldAutoWrapTabs?window.CodemanTabOverflow.shouldAutoWrapTabs({deviceType:t,manualTwoRows:n,tabCount:this.sessions.size,scrollWidth:e.scrollWidth,clientWidth:e.clientWidth}):e.scrollWidth>e.clientWidth+1;e.classList.toggle("tabs-auto-wrap",o)}_fullRenderSessionTabs(){if(this._inlineRenameActive)return;const e=this.$("sessionTabs");document.querySelectorAll("body > .subagent-dropdown").forEach(n=>n.remove()),this.cancelHideSubagentDropdown();const t=[];let s=this.sessionOrder;MobileDetection.getDeviceType()==="mobile"&&this.activeSessionId&&(s=[this.activeSessionId,...this.sessionOrder.filter(n=>n!==this.activeSessionId)]);let i=0;for(const n of s){const o=this.sessions.get(n);if(!o)continue;const a=n===this.activeSessionId,r=o.status||"idle",l=this.getSessionName(o),d=o.mode||"claude",c=o.color||"default",p=o.taskStats||{running:0,total:0},g=p.running>0,_=this.tabAlerts.get(n),w=_==="action"?" tab-alert-action":_==="idle"?" tab-alert-idle":"",y=this.terminalLoadStates.get(n),R=this.minimizedSubagents.get(n),A=(R?.size||0)>0?this.renderSubagentTabBadge(n,R):"",u=this.renderUltracodeTabBadge?this.renderUltracodeTabBadge(n):"",m=o.workingDir&&o.workingDir.split("/").pop()||"",f=(this._tallTabsEnabled??!1)&&o.name&&m&&m!==l;t.push(`<div class="session-tab ${a?"active":""}${w}${y?" tab-loading":""}" data-id="${n}" data-color="${c}" ${y?`data-load-phase="${escapeHtml(y.phase)}"`:""} onclick="app.handleSessionTabClick(event, ${escapeHtml(JSON.stringify(n))})" oncontextmenu="event.preventDefault(); app.startInlineRename(${escapeHtml(JSON.stringify(n))})" tabindex="0" role="tab" aria-selected="${a?"true":"false"}" aria-busy="${y?"true":"false"}" aria-label="${escapeHtml(l)} session" ${o.workingDir?`title="${escapeHtml(o.workingDir)}"`:""}>
17
+ ${i<9?'<span class="tab-number">'+(i+1)+"</span>":""}
18
+ ${y?'<span class="tab-load-spinner" aria-hidden="true"></span>':""}
19
+ <span class="tab-status ${r}" aria-hidden="true"></span>
20
+ <span class="tab-info">
21
+ <span class="tab-name-row">
22
+ ${d==="shell"?'<span class="tab-mode shell" aria-hidden="true">sh</span>':d==="opencode"?'<span class="tab-mode opencode" aria-hidden="true">oc</span>':d==="codex"?'<span class="tab-mode codex" aria-hidden="true">cx</span>':""}
23
+ <span class="tab-name" data-session-id="${n}">${(()=>{const T=parseSessionPrefix(l);return T&&T.suffix?'<span class="tab-prefix">'+escapeHtml(T.prefix)+'</span><span class="tab-suffix">: '+escapeHtml(T.suffix)+"</span>":escapeHtml(l)})()}</span>
24
+ <span class="tab-detached-badge" aria-hidden="true">detached</span>
25
+ </span>
26
+ ${f?`<span class="tab-folder">\u{1F4C1} ${escapeHtml(m)}</span>`:""}
27
+ </span>
28
+ ${g?`<span class="tab-badge" onclick="event.stopPropagation(); app.toggleTaskPanel()" aria-label="${p.running} running tasks">${p.running}</span>`:""}
29
+ ${A}
30
+ ${u}
31
+ <span class="tab-gear" onclick="event.stopPropagation(); app.openSessionOptions(${escapeHtml(JSON.stringify(n))})" title="Session options" aria-label="Session options" tabindex="0">&#x2699;</span>
32
+ <span class="tab-detach" onclick="event.stopPropagation(); app.detachSession(${escapeHtml(JSON.stringify(n))})" title="Open in a new window" aria-label="Open session in a new window" tabindex="0">&#x29C9;</span>
33
+ <span class="tab-close" onclick="event.stopPropagation(); app.requestCloseSession(${escapeHtml(JSON.stringify(n))})" title="Close session" aria-label="Close session" tabindex="0">&times;</span>
34
+ </div>`),i++}e.innerHTML=t.join(""),this.setupTabDragHandlers(),this.setupTabKeyboardNavigation(e),this.updateConnectionLines(),this.updateTabOverflowMode()}setupTabKeyboardNavigation(e){this._tabKeydownHandler&&e.removeEventListener("keydown",this._tabKeydownHandler),this._tabKeydownHandler=t=>{if(!["ArrowLeft","ArrowRight","Home","End","Enter"," "].includes(t.key))return;const s=[...e.querySelectorAll(".session-tab")],i=s.indexOf(document.activeElement);if((t.key==="Enter"||t.key===" ")&&i>=0){t.preventDefault();const o=s[i].dataset.id;this.selectSession(o,{forceReload:!0});return}if(i<0)return;let n;switch(t.key){case"ArrowLeft":n=i>0?i-1:s.length-1;break;case"ArrowRight":n=i<s.length-1?i+1:0;break;case"Home":n=0;break;case"End":n=s.length-1;break;default:return}t.preventDefault(),s[n]?.focus()},e.addEventListener("keydown",this._tabKeydownHandler)}handleSessionTabClick(e,t){return e?.preventDefault?.(),!(typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible===!0)&&MobileDetection.isTouchDevice()&&document.activeElement?.blur?.(),this.selectSession(t,{forceReload:!0})}syncSessionOrder(){const e=new Set(this.sessions.keys()),s=this.loadSessionOrder().filter(o=>e.has(o)),i=new Set(s),n=[...e].filter(o=>!i.has(o));this.sessionOrder=[...s,...n]}loadSessionOrder(){try{const e=localStorage.getItem("codeman-session-order");return e?JSON.parse(e):[]}catch{return[]}}saveSessionOrder(){try{localStorage.setItem("codeman-session-order",JSON.stringify(this.sessionOrder))}catch{}}setupTabDragHandlers(){const e=this.$("sessionTabs");e.querySelectorAll(".session-tab[data-id]").forEach(s=>{s.setAttribute("draggable","true"),s.addEventListener("dragstart",i=>{this.draggedTabId=s.dataset.id,s.classList.add("dragging"),i.dataTransfer.effectAllowed="move",i.dataTransfer.setData("text/plain",s.dataset.id)}),s.addEventListener("dragend",()=>{s.classList.remove("dragging"),this.draggedTabId=null,e.querySelectorAll(".session-tab").forEach(i=>{i.classList.remove("drag-over-left","drag-over-right")})}),s.addEventListener("dragover",i=>{if(i.preventDefault(),!this.draggedTabId||this.draggedTabId===s.dataset.id)return;i.dataTransfer.dropEffect="move";const n=s.getBoundingClientRect(),o=n.left+n.width/2,a=i.clientX<o;s.classList.toggle("drag-over-left",a),s.classList.toggle("drag-over-right",!a)}),s.addEventListener("dragleave",()=>{s.classList.remove("drag-over-left","drag-over-right")}),s.addEventListener("drop",i=>{if(i.preventDefault(),s.classList.remove("drag-over-left","drag-over-right"),!this.draggedTabId||this.draggedTabId===s.dataset.id)return;const n=s.dataset.id,o=this.draggedTabId,a=s.getBoundingClientRect(),r=a.left+a.width/2,l=i.clientX<r,d=this.sessionOrder.indexOf(o);let c=this.sessionOrder.indexOf(n);d===-1||c===-1||(this.sessionOrder.splice(d,1),c=this.sessionOrder.indexOf(n),c!==-1&&(l?this.sessionOrder.splice(c,0,o):this.sessionOrder.splice(c+1,0,o),this.saveSessionOrder(),this._fullRenderSessionTabs()))})})}moveActiveTabLeft(){if(!this.activeSessionId)return;const e=this.sessionOrder.indexOf(this.activeSessionId);e<=0||([this.sessionOrder[e-1],this.sessionOrder[e]]=[this.sessionOrder[e],this.sessionOrder[e-1]],this.saveSessionOrder(),this._fullRenderSessionTabs())}moveActiveTabRight(){if(!this.activeSessionId)return;const e=this.sessionOrder.indexOf(this.activeSessionId);e===-1||e>=this.sessionOrder.length-1||([this.sessionOrder[e],this.sessionOrder[e+1]]=[this.sessionOrder[e+1],this.sessionOrder[e]],this.saveSessionOrder(),this._fullRenderSessionTabs())}getShortId(e){if(!e)return"";let t=this._shortIdCache.get(e);return t||(t=e.slice(0,8),this._shortIdCache.set(e,t)),t}getSessionName(e){return e.name?e.name:e.workingDir?e.workingDir.split("/").pop()||e.workingDir:this.getShortId(e.id)}_notifySession(e,t,s,i,n){const o=this.sessions.get(e);this.notificationManager?.notify({urgency:t,category:s,sessionId:e,sessionName:o?.name||this.getShortId(e),title:i,message:n})}_isUsableXtermSnapshot(e){return!e||typeof e!="string"||e.length<8?!1:e.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g,"").replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g,"").replace(/\x1b[()][0-2A-Z]/g,"").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g,"").trim().length>=3}_persistXtermSnapshot(e,t){const s="codeman-xs-",n=()=>Object.keys(localStorage).filter(o=>o.startsWith(s)&&o!==e);try{if(localStorage.getItem(e)===null){const o=new Set(Array.from(this.sessions?.keys?.()||[])),a=n().sort((r,l)=>Number(o.has(r.slice(s.length)))-Number(o.has(l.slice(s.length))));for(;a.length>=10;)localStorage.removeItem(a.shift())}try{localStorage.setItem(e,t)}catch{for(const a of n()){localStorage.removeItem(a);try{localStorage.setItem(e,t);return}catch{}}try{localStorage.removeItem(e)}catch{}}}catch{}}_cleanupPreviousSession(e){const t=this.activeSessionId?this.sessions?.get?.(this.activeSessionId):null;if(this.activeSessionId&&t?.mode!=="shell"&&this._serializeAddon&&this._xtermSnapshots)try{const i=this._serializeAddon.serialize({scrollback:1e3});if(this._isUsableXtermSnapshot(i)){if(this._xtermSnapshots.delete(this.activeSessionId),this._xtermSnapshots.set(this.activeSessionId,i),this._xtermSnapshots.size>20){const n=this._xtermSnapshots.keys().next().value;this._xtermSnapshots.delete(n)}i.length<256*1024&&this._persistXtermSnapshot(`codeman-xs-${this.activeSessionId}`,i)}else{this._xtermSnapshots.delete(this.activeSessionId);try{localStorage.removeItem(`codeman-xs-${this.activeSessionId}`)}catch{}}}catch{}this._disconnectWs();const s=document.getElementById("cjkInput");s&&(s.value=""),this._clearTimer("flickerFilterTimeout"),this.flickerFilterBuffer="",this.flickerFilterActive=!1,this._tabCompletionSessionId=null,this._tabCompletionRetries=0,this._tabCompletionBaseText=null,this._clearTimer("_tabCompletionFallback"),this._clearTimer("_clientDropRecoveryTimer"),this._clearTimer("syncWaitTimeout"),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._bufferLoadOwner=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1;try{const i=this.terminal?._core?._compositionHelper;if(i?._isComposing){i._isComposing=!1;const n=this.terminal?.element?.querySelector(".xterm-helper-textarea");n&&n.dispatchEvent(new CompositionEvent("compositionend",{data:""}))}}catch{}if(this.activeSessionId){const i=this._localEchoOverlay?.pendingText||"",n=this._localEchoOverlay?.getFlushed()?.count||0,o=this._localEchoOverlay?.getFlushed()?.text||"";i&&this._sendInputAsync(this.activeSessionId,i);const a=n+i.length;a>0&&(this._flushedOffsets||(this._flushedOffsets=new Map),this._flushedTexts||(this._flushedTexts=new Map),this._flushedOffsets.set(this.activeSessionId,a),this._flushedTexts.set(this.activeSessionId,o+i))}this._localEchoOverlay?.clear(),this._localEchoOverlay&&!this._flushedOffsets?.has(e)&&this._localEchoOverlay.suppressBufferDetection()}_resetTerminalForReplay(){this.terminal.reset(),this.terminal.write("\x1B[3J\x1B[H\x1B[2J")}_shouldFocusTerminalForTabSwitch(){return typeof MobileDetection>"u"||!MobileDetection.isTouchDevice()?!0:typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible}async selectSession(e,t={}){if(!this.isSoloWindow&&this.detachedSessions.has(e)&&this._raiseDetached(e))return;const s=t?.forceReload===!0;if(this.activeSessionId===e&&!s)return;if(this.activeSessionId===e&&s){this.terminalBufferCache?.delete(e),this._xtermSnapshots?.delete(e);try{localStorage.removeItem(`codeman-xs-${e}`)}catch{}this._clearTimer("syncWaitTimeout"),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1,this.activeSessionId=null}const i=this._shouldFocusTerminalForTabSwitch();i&&this.terminal&&this.terminal.focus();const n=performance.now(),o=this.sessions.get(e)?.name||e.slice(0,8);_crashDiag.log(`SELECT: ${o}`),console.log(`[CRASH-DIAG] selectSession START: ${e.slice(0,8)}`);const a=++this._selectGeneration;if(this._setTerminalLoadState(e,a,"resizing"),a!==this._selectGeneration){this._clearTerminalLoadState(e,a);return}this._cleanupPreviousSession(e),this.activeSessionId=e;try{localStorage.setItem("codeman-active-session",e)}catch{}this._updateSseSubscription(e),this.hideWelcome(),this.clearPendingHooks(e,"idle_prompt"),this._updateActiveTabImmediate(e),this.renderSessionTabs(),this.updateAttachmentHistoryBadge?.(),this.attachmentHistoryDrawerOpen&&this.loadAttachmentHistory?.(e),this._updateLocalEchoState(),this._flushedOffsets?.has(e)&&this._localEchoOverlay&&this._localEchoOverlay.setFlushed(this._flushedOffsets.get(e),this._flushedTexts?.get(e)||"",!1);const r=document.querySelector(`.session-tab.active[data-id="${e}"]`);r&&(r.classList.add("tab-glow"),r.addEventListener("animationend",()=>r.classList.remove("tab-glow"),{once:!0}));const l=this.sessions.get(e);if(this.currentSessionWorkingDir=l?.workingDir||null,l&&l.pid===null)try{const c=l.mode==="shell"?`/api/sessions/${e}/shell`:`/api/sessions/${e}/interactive`;await fetch(c,{method:"POST"}),l.status="busy"}catch(c){console.error("Failed to attach to restored session:",c)}this._restoringFlushedState=!0;const d=this._beginBufferLoad(a);try{this.fitAddon&&this.fitAddon.fit();const c=await this.sendResize(e,{forceHttp:!0}).catch(()=>!1);if(this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}let p=this._xtermSnapshots?.get(e);if(p&&!this._isUsableXtermSnapshot(p)&&(this._xtermSnapshots?.delete(e),p=null),!p)try{const u=localStorage.getItem(`codeman-xs-${e}`);u&&this._isUsableXtermSnapshot(u)?(p=u,this._xtermSnapshots?.delete(e),this._xtermSnapshots?.set(e,u)):u&&localStorage.removeItem(`codeman-xs-${e}`)}catch{}const g=l&&(l.status==="busy"||l.status==="working");let _=!1;if(p&&!g&&l?.mode!=="shell"){if(_crashDiag.log(`SNAPSHOT_RESTORE: ${(p.length/1024).toFixed(0)}KB`),this._setTerminalLoadState(e,a,"replaying"),this._resetTerminalForReplay(),await new Promise(u=>this.terminal.write(p,u)),this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}this.scrollToLastNonEmptyLine(),_crashDiag.log("SNAPSHOT_RESTORE_DONE"),_=!0}const w=this.terminalBufferCache.get(e);let y=!1;if(w&&!g&&!_){if(_crashDiag.log(`CACHE_WRITE: ${(w.length/1024).toFixed(0)}KB`),this._setTerminalLoadState(e,a,"replaying"),this._resetTerminalForReplay(),await this.chunkedTerminalWrite(w,TERMINAL_CHUNK_SIZE,d),this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}this.terminal.scrollToBottom(),_crashDiag.log("CACHE_DONE")}else g&&(this._resetTerminalForReplay(),y=!0,_crashDiag.log("CACHE_SKIP_BUSY"));if(l?.mode!=="shell"&&c&&(await new Promise(u=>setTimeout(u,TUI_REDRAW_SETTLE_MS)),this._isStaleSelect(a))){this._clearTerminalLoadState(e,a);return}this._setTerminalLoadState(e,a,"fetching"),_crashDiag.log("FETCH_START");const R=await fetch(`/api/sessions/${e}/terminal?tail=${TERMINAL_TAIL_SIZE}`);if(this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}const E=(await R.json())?.data??{};if(_crashDiag.log(`FETCH_DONE: ${E.terminalBuffer?(E.terminalBuffer.length/1024).toFixed(0)+"KB":"empty"} truncated=${E.truncated}`),E.terminalBuffer){if(_||y||E.terminalBuffer!==w){if(_crashDiag.log(`REWRITE: ${(E.terminalBuffer.length/1024).toFixed(0)}KB`),this._setTerminalLoadState(e,a,"replaying"),this._resetTerminalForReplay(),E.truncated&&this.terminal.write(`\x1B[90m... (earlier output truncated for performance) ...\x1B[0m\r
35
+ \r
36
+ `),await this.chunkedTerminalWrite(E.terminalBuffer,TERMINAL_CHUNK_SIZE,d),this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}this.terminal.scrollToBottom()}if(this.terminalBufferCache.set(e,E.terminalBuffer),this.terminalBufferCache.size>20){const m=this.terminalBufferCache.keys().next().value;this.terminalBufferCache.delete(m)}}else w||this._resetTerminalForReplay();if(this._isLoadingBuffer&&this._finishBufferLoad(d),this._restoringFlushedState=!1,this._flushedOffsets?.has(e)&&this._localEchoOverlay){this._localEchoOverlay.setFlushed(this._flushedOffsets.get(e),this._flushedTexts?.get(e)||"",!1);const u=this._localEchoOverlay;this.terminal.write("",()=>{u.hasPending&&u.rerender()})}this.sendResize(e),(typeof requestIdleCallback=="function"?requestIdleCallback:u=>setTimeout(u,16))(()=>{if(a!==this._selectGeneration)return;this.respawnStatus[e]?(this.showRespawnBanner(),this.updateRespawnBanner(this.respawnStatus[e].state),document.getElementById("respawnCycleCount").textContent=this.respawnStatus[e].cycleCount||0,this.updateCountdownTimerDisplay(),this.updateActionLogDisplay(),Object.keys(this.respawnCountdownTimers[e]||{}).length>0&&this.startCountdownInterval()):(this.hideRespawnBanner(),this.stopCountdownInterval());const u=document.getElementById("taskPanel");u&&u.classList.contains("open")&&this.renderTaskPanel();const m=this.sessions.get(e);if(m&&(m.ralphLoop||m.ralphTodos)&&this.updateRalphState(e,{loop:m.ralphLoop,todos:m.ralphTodos}),this.renderRalphStatePanel(),this.updateCliInfoDisplay(),this.renderProjectInsightsPanel(),this.updateSubagentWindowVisibility(),this.loadAppSettingsFromStorage().showFileBrowser){const f=this.$("fileBrowserPanel");if(f&&(f.classList.add("visible"),this.loadFileBrowser(e),!this.fileBrowserDragListeners)){const T=f.querySelector(".file-browser-header");if(T){const v=()=>{if(!f.style.left){const S=f.getBoundingClientRect();f.style.left=`${S.left}px`,f.style.top=`${S.top}px`,f.style.right="auto"}};T.addEventListener("mousedown",v),T.addEventListener("touchstart",v,{passive:!0}),this.fileBrowserDragListeners=this.makeWindowDraggable(f,T),this.fileBrowserDragListeners._onFirstDrag=v}}}}),this._connectWs(e),_crashDiag.log("FOCUS"),i&&this.terminal&&this.terminal.focus(),this.scrollToLastNonEmptyLine(),this._clearTerminalLoadState(e,a),_crashDiag.log(`SELECT_DONE: ${(performance.now()-n).toFixed(0)}ms`),console.log(`[CRASH-DIAG] selectSession DONE: ${e.slice(0,8)} in ${(performance.now()-n).toFixed(0)}ms`)}catch(c){this._isLoadingBuffer&&this._finishBufferLoad(d),this._restoringFlushedState=!1,this._setTerminalLoadState(e,a,"failed"),console.error("Failed to load session terminal:",c)}}_cleanupSessionData(e){this._activeRename?.sessionId===e&&this._activeRename.cancel(),this.sessions.delete(e);const t=this.sessionOrder.indexOf(e);t!==-1&&(this.sessionOrder.splice(t,1),this.saveSessionOrder()),this.terminalBuffers.delete(e),this.terminalBufferCache.delete(e),this._xtermSnapshots?.delete(e);try{localStorage.removeItem(`codeman-xs-${e}`)}catch{}this._flushedOffsets?.delete(e),this._flushedTexts?.delete(e),this._pendingDeliveries?.delete(e),this._seqCounters?.delete(e),this._postDraining?.delete(e),this._persistReliableState(),this.ralphStates.delete(e),this.ralphClosedSessions.delete(e),this.projectInsights.delete(e),this.pendingHooks.delete(e),this.tabAlerts.delete(e),this.attachmentHistoryCounts.delete(e),this.attachmentHistoryDrawerOpen&&this.activeSessionId===e&&this.closeAttachmentHistory?.(),this.terminalLoadStates.delete(e),this.clearCountdownTimers(e),this.closeSessionLogViewerWindows(e),this.closeSessionImagePopups(e),this.closeSessionAttachmentCards(e),this.closeSessionSubagentWindows(e,!0);const s=this.idleTimers.get(e);s&&(clearTimeout(s),this.idleTimers.delete(e)),delete this.respawnStatus[e],delete this.respawnTimers[e],delete this.respawnCountdownTimers[e],delete this.respawnActionLogs[e]}async closeSession(e,t=!0){try{if(await this._apiDelete(`/api/sessions/${e}?killMux=${t}`),this._cleanupSessionData(e),this.activeSessionId===e){this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}if(this.sessionOrder.length>0&&this.sessions.size>0){const s=this.sessionOrder[0];this.selectSession(s)}else this.terminal.clear(),this.showWelcome(),this.renderRalphStatePanel()}this.renderSessionTabs(),t?this.showToast("Session closed and tmux killed","success"):this.showToast("Tab hidden, tmux still running","info")}catch{this.showToast("Failed to close session","error")}}requestCloseSession(e){const t=this.sessions.get(e);if(!t)return;this.pendingCloseSessionId=e;const s=this.getSessionName(t),i=document.getElementById("closeConfirmSessionName");i.textContent=s;const n=document.getElementById("closeConfirmKillTitle");n&&(n.textContent=t.mode==="opencode"?"Kill Tmux & OpenCode":t.mode==="codex"?"Kill Tmux & Codex":"Kill Tmux & Claude Code"),document.getElementById("closeConfirmModal").classList.add("active")}cancelCloseSession(){this.pendingCloseSessionId=null,document.getElementById("closeConfirmModal").classList.remove("active")}async confirmCloseSession(e=!0){const t=this.pendingCloseSessionId;this.cancelCloseSession(),t&&await this.closeSession(t,e)}nextSession(){if(this.sessionOrder.length<=1)return;const t=(this.sessionOrder.indexOf(this.activeSessionId)+1)%this.sessionOrder.length;this.selectSession(this.sessionOrder[t])}prevSession(){if(this.sessionOrder.length<=1)return;const t=(this.sessionOrder.indexOf(this.activeSessionId)-1+this.sessionOrder.length)%this.sessionOrder.length;this.selectSession(this.sessionOrder[t])}goHome(){this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.terminal.clear(),this.showWelcome(),this.renderSessionTabs(),this.renderRalphStatePanel()}ralphWizardStep=1;ralphWizardConfig={taskDescription:"",completionPhrase:"COMPLETE",maxIterations:10,caseName:"testcase",enableRespawn:!1,generatedPlan:null,planGenerated:!1,skipPlanGeneration:!1,planDetailLevel:"detailed",existingPlan:null,useExistingPlan:!1};planLoadingTimer=null;planLoadingStartTime=null;async killActiveSession(){if(!this.activeSessionId){this.showToast("No active session","warning");return}await this.closeSession(this.activeSessionId)}async killAllSessions(){if(this.sessions.size!==0&&confirm(`Kill all ${this.sessions.size} session(s)?`))try{await this._apiDelete("/api/sessions"),this.sessions.clear(),this.terminalBuffers.clear(),this.terminalBufferCache.clear(),this.terminalLoadStates.clear(),this._xtermSnapshots?.clear();try{for(const e of Object.keys(localStorage))e.startsWith("codeman-xs-")&&localStorage.removeItem(e)}catch{}this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.respawnStatus={},this.respawnCountdownTimers={},this.respawnActionLogs={},this.stopCountdownInterval(),this.hideRespawnBanner(),this.renderSessionTabs(),this.terminal.clear(),this.showWelcome(),this.showToast("All sessions killed","success")}catch{this.showToast("Failed to kill sessions","error")}}showTimer(){document.getElementById("timerBanner").style.display="flex",this.updateTimer(),this.timerInterval=setInterval(()=>this.updateTimer(),1e3)}hideTimer(){document.getElementById("timerBanner").style.display="none",this.timerInterval&&(clearInterval(this.timerInterval),this.timerInterval=null)}updateTimer(){if(!this.currentRun||this.currentRun.status!=="running")return;const e=Date.now(),t=Math.max(0,this.currentRun.endAt-e),s=this.currentRun.endAt-this.currentRun.startedAt,i=e-this.currentRun.startedAt,n=Math.min(100,i/s*100);document.getElementById("timerValue").textContent=this.formatTime(t),document.getElementById("timerProgress").style.width=`${n}%`,document.getElementById("timerMeta").textContent=`${this.currentRun.completedTasks} tasks | $${this.currentRun.totalCost.toFixed(2)}`}async stopCurrentRun(){if(this.currentRun)try{await fetch(`/api/scheduled/${this.currentRun.id}`,{method:"DELETE"})}catch{this.showToast("Failed to stop run","error")}}formatTime(e){const t=Math.floor(e/1e3),s=Math.floor(t/3600),i=Math.floor(t%3600/60),n=t%60;return`${s.toString().padStart(2,"0")}:${i.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}`}updateCost(){this.updateTokens()}updateTokens(){this._clearTimer("_updateTokensTimeout"),this._updateTokensTimeout=setTimeout(()=>{this._updateTokensTimeout=null,this._updateTokensImmediate()},200)}_updateTokensImmediate(){let e=0,t=0;this.globalStats?(e=this.globalStats.totalInputTokens||0,t=this.globalStats.totalOutputTokens||0):this.sessions.forEach(a=>{a.tokens&&(e+=a.tokens.input||0,t+=a.tokens.output||0)});const s=e+t;this.totalTokens=s;const i=this.formatTokens(s),n=this.estimateCost(e,t),o=this.$("headerTokens");if(o){const r=this.loadAppSettingsFromStorage().showCost??!1;o.textContent=s>0?r?`${i} tokens \xB7 $${n.toFixed(2)}`:`${i} tokens`:"0 tokens",o.title=this.globalStats?`Lifetime: ${this.globalStats.totalSessionsCreated} sessions created${r?`
37
+ Estimated cost based on Claude Opus pricing`:""}`:`Token usage across active sessions${r?`
38
+ Estimated cost based on Claude Opus pricing`:""}`}}}try{for(let h=0;h<localStorage.length;h++){const e=localStorage.key(h);if(e&&(e.startsWith("claudeman-")||e.startsWith("claudeman_"))){const t=e.replace(/^claudeman[-_]/,s=>"codeman"+s.charAt(s.length-1));localStorage.getItem(t)===null&&localStorage.setItem(t,localStorage.getItem(e))}}}catch{}let app;document.addEventListener("DOMContentLoaded",()=>{app=new CodemanApp,window.app=app}),window.MobileDetection=MobileDetection;
@@ -4449,6 +4449,7 @@ var GestureController = class {
4449
4449
  // packages/gesture-control/src/codeman/entry.ts
4450
4450
  var TAB_SELECTOR = ".session-tab";
4451
4451
  var PANEL_SELECTOR = ".cg-float";
4452
+ var WINDOW_SELECTOR = ".subagent-window, .ultracode-window";
4452
4453
  var DOCK_SELECTOR = ".session-tabs";
4453
4454
  var CLICK_SELECTOR = "#runBtn, .btn-shell";
4454
4455
  var Z2 = 2147483e3;
@@ -4476,6 +4477,8 @@ var GestureBridge = class {
4476
4477
  __publicField(this, "taps", /* @__PURE__ */ new Map());
4477
4478
  /** Live floating panels, keyed by session id (idempotent per id). */
4478
4479
  __publicField(this, "floats", /* @__PURE__ */ new Map());
4480
+ /** rAF coalescing for connector-line redraws while dragging an agent window. */
4481
+ __publicField(this, "connectorRedrawScheduled", false);
4479
4482
  injectStyles();
4480
4483
  this.surface = el("div", "cg-surface");
4481
4484
  this.canvas = el("canvas", "cg-canvas");
@@ -4530,7 +4533,7 @@ var GestureBridge = class {
4530
4533
  await this.gc.start();
4531
4534
  this.running = true;
4532
4535
  this.button.classList.add("on");
4533
- this.status.textContent = "on \u2014 pinch a tab or button";
4536
+ this.status.textContent = "on \u2014 pinch a tab, window, or button";
4534
4537
  } catch (err) {
4535
4538
  const msg = describeError(err);
4536
4539
  this.status.textContent = `failed: ${msg}`;
@@ -4575,6 +4578,16 @@ var GestureBridge = class {
4575
4578
  return;
4576
4579
  }
4577
4580
  }
4581
+ const win = this.hitClosest(x2, y2, WINDOW_SELECTOR);
4582
+ if (win) {
4583
+ const rect = win.getBoundingClientRect();
4584
+ win.style.bottom = "auto";
4585
+ win.classList.add("cg-win-grabbed");
4586
+ this.bringWindowToFront(win);
4587
+ this.grabs.set(hand, { kind: "window", el: win, dx: x2 - rect.left, dy: y2 - rect.top });
4588
+ this.status.textContent = "moving window";
4589
+ return;
4590
+ }
4578
4591
  const tab = this.hitClosest(x2, y2, TAB_SELECTOR);
4579
4592
  const id = tab?.dataset.id;
4580
4593
  if (tab && id) {
@@ -4620,11 +4633,15 @@ var GestureBridge = class {
4620
4633
  }
4621
4634
  return;
4622
4635
  }
4636
+ if (grab?.kind === "window") {
4637
+ this.moveWindow(grab.el, x2 - grab.dx, y2 - grab.dy);
4638
+ return;
4639
+ }
4623
4640
  const tap = this.taps.get(hand);
4624
4641
  if (tap && Math.hypot(x2 - tap.ox, y2 - tap.oy) > TAP_CANCEL_PX) {
4625
4642
  tap.el.classList.remove("cg-tap-armed");
4626
4643
  this.taps.delete(hand);
4627
- this.status.textContent = "on \u2014 pinch a tab or button";
4644
+ this.status.textContent = "on \u2014 pinch a tab, window, or button";
4628
4645
  }
4629
4646
  }
4630
4647
  onDrop(hand, x2, y2) {
@@ -4645,6 +4662,18 @@ var GestureBridge = class {
4645
4662
  else this.flash("placed");
4646
4663
  return;
4647
4664
  }
4665
+ if (grab?.kind === "window") {
4666
+ this.grabs.delete(hand);
4667
+ grab.el.classList.remove("cg-win-grabbed");
4668
+ this.connectorRedrawScheduled = false;
4669
+ this.redrawWindowConnectors();
4670
+ try {
4671
+ window.app?.saveSubagentWindowStates?.();
4672
+ } catch {
4673
+ }
4674
+ this.flash("placed window");
4675
+ return;
4676
+ }
4648
4677
  const tap = this.taps.get(hand);
4649
4678
  if (tap) {
4650
4679
  this.taps.delete(hand);
@@ -4694,6 +4723,54 @@ var GestureBridge = class {
4694
4723
  float.el.style.left = `${l}px`;
4695
4724
  float.el.style.top = `${t2}px`;
4696
4725
  }
4726
+ /** Move a dashboard-owned agent window by its top-left, clamped on-screen, then
4727
+ * redraw its connector line. The window self-positions via `style.left/top` and
4728
+ * app.js's connector redraw reads live rects, so this tracks without touching
4729
+ * app.js internals. Guards on `isConnected`: ultracode windows can be torn down
4730
+ * (SSE reconnect / auto-close) while still held. Clamps to `innerWidth/Height`,
4731
+ * which equals the *spanned* viewport in a multi-monitor window — so the window
4732
+ * can still travel across the physical monitor seam, just not off-screen. */
4733
+ moveWindow(el2, left, top) {
4734
+ if (!el2.isConnected) return;
4735
+ const w2 = el2.offsetWidth || 380;
4736
+ const h2 = el2.offsetHeight || 320;
4737
+ const l = Math.min(Math.max(4, left), Math.max(4, window.innerWidth - w2 - 4));
4738
+ const t2 = Math.min(Math.max(4, top), Math.max(4, window.innerHeight - h2 - 4));
4739
+ el2.style.left = `${l}px`;
4740
+ el2.style.top = `${t2}px`;
4741
+ this.redrawWindowConnectors();
4742
+ }
4743
+ /** Ask app.js to redraw all connector lines (subagent + ultracode), coalesced to
4744
+ * one per frame so per-frame drags don't thrash. `updateConnectionLines()` is
4745
+ * itself debounced in app.js, but we rAF-gate too in case an older dashboard
4746
+ * build isn't, and to no-op cleanly when app.js isn't present (standalone). */
4747
+ redrawWindowConnectors() {
4748
+ if (this.connectorRedrawScheduled) return;
4749
+ this.connectorRedrawScheduled = true;
4750
+ requestAnimationFrame(() => {
4751
+ this.connectorRedrawScheduled = false;
4752
+ try {
4753
+ window.app?.updateConnectionLines?.();
4754
+ } catch {
4755
+ }
4756
+ });
4757
+ }
4758
+ /** Pop a grabbed window above its siblings using app.js's own z-counter, so a
4759
+ * picked-up window comes to the front like a real focus. Cosmetic + best-effort. */
4760
+ bringWindowToFront(el2) {
4761
+ const app = window.app;
4762
+ if (!app) return;
4763
+ try {
4764
+ if (el2.classList.contains("ultracode-window")) {
4765
+ app.ultracodeWindowZIndex = (app.ultracodeWindowZIndex ?? 1e3) + 1;
4766
+ el2.style.zIndex = String(app.ultracodeWindowZIndex);
4767
+ } else {
4768
+ app.subagentWindowZIndex = (app.subagentWindowZIndex ?? 1e3) + 1;
4769
+ el2.style.zIndex = String(app.subagentWindowZIndex);
4770
+ }
4771
+ } catch {
4772
+ }
4773
+ }
4697
4774
  positionGhost(ghost, x2, y2) {
4698
4775
  ghost.style.left = `${x2}px`;
4699
4776
  ghost.style.top = `${y2}px`;
@@ -4703,15 +4780,17 @@ var GestureBridge = class {
4703
4780
  if (grab.kind === "tab") {
4704
4781
  grab.ghost.remove();
4705
4782
  grab.tab.classList.remove("cg-grabbed");
4706
- } else {
4783
+ } else if (grab.kind === "panel") {
4707
4784
  grab.panel.el.style.pointerEvents = "";
4708
4785
  grab.panel.el.classList.remove("cg-float-grabbed", "cg-redock");
4786
+ } else {
4787
+ grab.el.classList.remove("cg-win-grabbed");
4709
4788
  }
4710
4789
  }
4711
4790
  this.grabs.clear();
4712
4791
  for (const tap of this.taps.values()) tap.el.classList.remove("cg-tap-armed");
4713
4792
  this.taps.clear();
4714
- document.querySelectorAll(`${TAB_SELECTOR}.cg-grabbed, .cg-tap-armed`).forEach((t2) => t2.classList.remove("cg-grabbed", "cg-tap-armed"));
4793
+ document.querySelectorAll(`${TAB_SELECTOR}.cg-grabbed, .cg-tap-armed, .cg-win-grabbed`).forEach((t2) => t2.classList.remove("cg-grabbed", "cg-tap-armed", "cg-win-grabbed"));
4715
4794
  }
4716
4795
  onStatus(fps, hands) {
4717
4796
  const { width, height } = this.canvas;
@@ -4797,6 +4876,10 @@ function injectStyles() {
4797
4876
  .cg-status { color: #9aa0a6; max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4798
4877
  .session-tab.cg-grabbed { opacity: .35; outline: 2px dashed #4ade80; outline-offset: -2px; }
4799
4878
  .cg-tap-armed { outline: 2px solid #4ade80 !important; outline-offset: 2px; box-shadow: 0 0 0 4px rgba(74,222,128,.25) !important; }
4879
+ .subagent-window.cg-win-grabbed, .ultracode-window.cg-win-grabbed {
4880
+ outline: 2px solid #4ade80 !important; outline-offset: -2px;
4881
+ box-shadow: 0 12px 48px rgba(74,222,128,.5) !important;
4882
+ }
4800
4883
  .cg-float {
4801
4884
  position: fixed; left: 0; top: 0; width: ${FLOAT_W}px; height: ${FLOAT_H}px;
4802
4885
  z-index: ${Z2}; display: flex; flex-direction: column; overflow: hidden;
@@ -104,34 +104,78 @@ Object.assign(CodemanApp.prototype, {
104
104
  document.execCommand('paste');
105
105
  },
106
106
 
107
- async _uploadAndInsertImages(files) {
107
+ // Max images accepted in one batch (paste / drop / mobile picker). Each is
108
+ // uploaded as its own request, so 20 stays under the server's 30 uploads/min
109
+ // rate limit while covering "select a bunch of photos at once".
110
+ _maxBatchImages: 20,
111
+ // How many uploads to run concurrently. Small enough that decoding several
112
+ // large images through <canvas> at once won't OOM a phone, large enough that
113
+ // 20 photos don't crawl through serially.
114
+ _uploadConcurrency: 3,
115
+
116
+ async _uploadAndInsertImages(fileList) {
108
117
  const sessionId = this.activeSessionId;
109
118
  if (!sessionId) return;
110
119
 
111
- this.showToast('Uploading ' + files.length + ' image' + (files.length > 1 ? 's' : '') + '...', 'info');
112
-
113
- const paths = [];
114
- for (const file of files) {
115
- try {
116
- // Re-encode to a standard JPEG/PNG before upload. Galleries on some
117
- // phones (notably Android/MIUI) hand back a WebP/HEIF whose filename and
118
- // MIME claim "image/jpeg", which passes the server's extension allowlist
119
- // but fails its magic-byte check ("bytes do not match declared type").
120
- // Decoding through the browser and re-encoding guarantees the bytes
121
- // match the extension we send.
122
- const normalized = await this._normalizeImageForUpload(file);
123
- const path = await this._uploadPasteImage(sessionId, normalized);
124
- paths.push(path);
125
- } catch (err) {
126
- this.showToast('Upload failed: ' + (err.message || 'unknown error'), 'error');
127
- }
120
+ let files = Array.from(fileList || []);
121
+ if (files.length === 0) return;
122
+
123
+ // Cap the batch and tell the user what got dropped (no silent truncation).
124
+ let capped = false;
125
+ if (files.length > this._maxBatchImages) {
126
+ files = files.slice(0, this._maxBatchImages);
127
+ capped = true;
128
128
  }
129
129
 
130
+ const total = files.length;
131
+ let done = 0;
132
+ let failed = 0;
133
+ const results = new Array(total); // preserve selection order for insertion
134
+ const progress = () =>
135
+ this.showToast(`Uploading ${Math.min(done + 1, total)}/${total} image${total > 1 ? 's' : ''}…`, 'info');
136
+ progress();
137
+
138
+ // Bounded-concurrency worker pool over the file list.
139
+ let next = 0;
140
+ const worker = async () => {
141
+ for (;;) {
142
+ const i = next++;
143
+ if (i >= total) return;
144
+ try {
145
+ // Re-encode to a standard JPEG/PNG (and downscale very large images)
146
+ // before upload. Galleries on some phones (notably Android/MIUI) hand
147
+ // back a WebP/HEIF whose filename and MIME claim "image/jpeg", which
148
+ // passes the server's extension allowlist but fails its magic-byte
149
+ // check. Decoding through the browser and re-encoding guarantees the
150
+ // bytes match the extension we send — and shrinks huge photos so they
151
+ // fit the upload limit and iOS's <canvas> area cap.
152
+ const normalized = await this._normalizeImageForUpload(files[i]);
153
+ results[i] = await this._uploadPasteImage(sessionId, normalized);
154
+ } catch (err) {
155
+ failed++;
156
+ console.warn('Image upload failed:', err);
157
+ results[i] = null;
158
+ } finally {
159
+ done++;
160
+ if (done < total) progress();
161
+ }
162
+ }
163
+ };
164
+ await Promise.all(Array.from({ length: Math.min(this._uploadConcurrency, total) }, () => worker()));
165
+
166
+ const paths = results.filter(Boolean);
130
167
  if (paths.length > 0) {
131
- const pathStr = paths.join(' ');
132
- await this.sendInput(pathStr);
133
- this.showToast(paths.length + ' image' + (paths.length > 1 ? 's' : '') + ' ready', 'success');
168
+ // Insert all paths in one shot, space-separated, in selection order.
169
+ await this.sendInput(paths.join(' '));
134
170
  }
171
+
172
+ // Final status: successes, plus any failures / cap so nothing is silent.
173
+ const parts = [];
174
+ if (paths.length > 0) parts.push(`${paths.length} image${paths.length > 1 ? 's' : ''} ready`);
175
+ if (failed > 0) parts.push(`${failed} failed`);
176
+ if (capped) parts.push(`max ${this._maxBatchImages} per batch`);
177
+ const tone = paths.length > 0 ? (failed > 0 || capped ? 'info' : 'success') : 'error';
178
+ this.showToast(parts.join(' · ') || 'No images uploaded', tone);
135
179
  },
136
180
 
137
181
  async _uploadPasteImage(sessionId, file) {
@@ -176,12 +220,24 @@ Object.assign(CodemanApp.prototype, {
176
220
  const height = img.naturalHeight;
177
221
  if (!width || !height) return file;
178
222
 
223
+ // Downscale very large images. Two reasons: (1) iOS Safari refuses to
224
+ // render a <canvas> larger than ~16.7M px (it returns a blank/null
225
+ // blob), so a 48MP photo would otherwise fail to re-encode and fall back
226
+ // to the original — which then trips the server's magic-byte check for
227
+ // HEIF mislabeled as JPEG. (2) It keeps multi-photo uploads fast and well
228
+ // under the size limit. Cap the longest edge so area stays safely below
229
+ // the canvas limit while still uploading a large, high-quality image.
230
+ const MAX_EDGE = 4096;
231
+ const scale = Math.min(1, MAX_EDGE / Math.max(width, height));
232
+ const w = Math.max(1, Math.round(width * scale));
233
+ const h = Math.max(1, Math.round(height * scale));
234
+
179
235
  const canvas = document.createElement('canvas');
180
- canvas.width = width;
181
- canvas.height = height;
236
+ canvas.width = w;
237
+ canvas.height = h;
182
238
  const ctx = canvas.getContext('2d');
183
239
  if (!ctx) return file;
184
- ctx.drawImage(img, 0, 0);
240
+ ctx.drawImage(img, 0, 0, w, h);
185
241
 
186
242
  const mime = toPng ? 'image/png' : 'image/jpeg';
187
243
  const blob = await new Promise((resolve) => canvas.toBlob(resolve, mime, 0.92));
@@ -25,7 +25,7 @@
25
25
  instead of waiting until <script> tags at bottom-of-body are reached. -->
26
26
  <link rel="preload" href="vendor/xterm.min.js" as="script">
27
27
  <link rel="preload" href="constants.1c779517.js" as="script">
28
- <link rel="preload" href="app.61e2966f.js" as="script">
28
+ <link rel="preload" href="app.e485a055.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>
@@ -1966,8 +1966,8 @@
1966
1966
  <script defer src="input-cjk.4e950e0a.js"></script>
1967
1967
  <!-- Hardened markdown HTML sanitizer (wires DOMPurify). Must precede app.js. -->
1968
1968
  <script defer src="sanitize-html.bc7078d6.js"></script>
1969
- <script defer src="app.61e2966f.js"></script>
1970
- <script defer src="terminal-ui.70596bbd.js"></script>
1969
+ <script defer src="app.e485a055.js"></script>
1970
+ <script defer src="terminal-ui.f4f3b686.js"></script>
1971
1971
  <script defer src="respawn-ui.2d249da9.js"></script>
1972
1972
  <script defer src="ralph-panel.6de2d0f8.js"></script>
1973
1973
  <script defer src="orchestrator-panel.js"></script>
@@ -1979,6 +1979,6 @@
1979
1979
  <script defer src="api-client.c9b1cddc.js"></script>
1980
1980
  <script defer src="subagent-windows.69b60c35.js"></script>
1981
1981
  <script defer src="ultracode-windows.js"></script>
1982
- <script defer src="image-input.0ea86695.js"></script>
1982
+ <script defer src="image-input.ee16ad88.js"></script>
1983
1983
  </body>
1984
1984
  </html>
Binary file
Binary file
Binary file