aicodeman 1.18.3 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +15 -0
  2. package/dist/attachment-registry.d.ts +25 -0
  3. package/dist/attachment-registry.d.ts.map +1 -1
  4. package/dist/attachment-registry.js +45 -1
  5. package/dist/attachment-registry.js.map +1 -1
  6. package/dist/hooks-config.d.ts +22 -16
  7. package/dist/hooks-config.d.ts.map +1 -1
  8. package/dist/hooks-config.js +20 -14
  9. package/dist/hooks-config.js.map +1 -1
  10. package/dist/types/tools.d.ts +1 -1
  11. package/dist/types/tools.d.ts.map +1 -1
  12. package/dist/web/ports/config-port.d.ts +2 -0
  13. package/dist/web/ports/config-port.d.ts.map +1 -1
  14. package/dist/web/public/admin-ui.js.gz +0 -0
  15. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  16. package/dist/web/public/app.a8b08065.js +34 -0
  17. package/dist/web/public/app.a8b08065.js.br +0 -0
  18. package/dist/web/public/app.a8b08065.js.gz +0 -0
  19. package/dist/web/public/approvals-ui.js +24 -15
  20. package/dist/web/public/approvals-ui.js.br +0 -0
  21. package/dist/web/public/approvals-ui.js.gz +0 -0
  22. package/dist/web/public/{constants.822a486d.js → constants.35c7f91c.js} +167 -20
  23. package/dist/web/public/constants.35c7f91c.js.br +0 -0
  24. package/dist/web/public/constants.35c7f91c.js.gz +0 -0
  25. package/dist/web/public/cron-ui.js.gz +0 -0
  26. package/dist/web/public/entrance-animations.js.gz +0 -0
  27. package/dist/web/public/home-sessions.js +79 -35
  28. package/dist/web/public/home-sessions.js.br +0 -0
  29. package/dist/web/public/home-sessions.js.gz +0 -0
  30. package/dist/web/public/i18n.bb8949de.js +1 -0
  31. package/dist/web/public/i18n.bb8949de.js.br +0 -0
  32. package/dist/web/public/i18n.bb8949de.js.gz +0 -0
  33. package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
  34. package/dist/web/public/index.html +109 -18
  35. package/dist/web/public/index.html.br +0 -0
  36. package/dist/web/public/index.html.gz +0 -0
  37. package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
  38. package/dist/web/public/keyboard-accessory.0454a3e9.js.gz +0 -0
  39. package/dist/web/public/{mobile-handlers.bc45f976.js → mobile-handlers.05b89237.js} +16 -0
  40. package/dist/web/public/mobile-handlers.05b89237.js.br +0 -0
  41. package/dist/web/public/mobile-handlers.05b89237.js.gz +0 -0
  42. package/dist/web/public/mobile-overview.js +17 -15
  43. package/dist/web/public/mobile-overview.js.br +0 -0
  44. package/dist/web/public/mobile-overview.js.gz +0 -0
  45. package/dist/web/public/mobile.c40d28a6.css +1 -0
  46. package/dist/web/public/mobile.c40d28a6.css.br +0 -0
  47. package/dist/web/public/mobile.c40d28a6.css.gz +0 -0
  48. package/dist/web/public/notification-manager.5d229063.js.gz +0 -0
  49. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  50. package/dist/web/public/{panels-ui.1e4cb4b2.js → panels-ui.c5a82b6e.js} +48 -46
  51. package/dist/web/public/panels-ui.c5a82b6e.js.br +0 -0
  52. package/dist/web/public/panels-ui.c5a82b6e.js.gz +0 -0
  53. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  54. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  55. package/dist/web/public/readmymind-ui.js.gz +0 -0
  56. package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
  57. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  58. package/dist/web/public/session-lineage.js +47 -2
  59. package/dist/web/public/session-lineage.js.br +0 -0
  60. package/dist/web/public/session-lineage.js.gz +0 -0
  61. package/dist/web/public/{session-ui.20be58fa.js → session-ui.71a0ce9a.js} +3 -3
  62. package/dist/web/public/session-ui.71a0ce9a.js.br +0 -0
  63. package/dist/web/public/session-ui.71a0ce9a.js.gz +0 -0
  64. package/dist/web/public/{settings-ui.eacb4f92.js → settings-ui.6cb12d4b.js} +2 -2
  65. package/dist/web/public/settings-ui.6cb12d4b.js.br +0 -0
  66. package/dist/web/public/settings-ui.6cb12d4b.js.gz +0 -0
  67. package/dist/web/public/styles.325eb4f3.css +1 -0
  68. package/dist/web/public/styles.325eb4f3.css.br +0 -0
  69. package/dist/web/public/styles.325eb4f3.css.gz +0 -0
  70. package/dist/web/public/{subagent-windows.2a192c3e.js → subagent-windows.e6ca799f.js} +24 -12
  71. package/dist/web/public/subagent-windows.e6ca799f.js.br +0 -0
  72. package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
  73. package/dist/web/public/sw.js.gz +0 -0
  74. package/dist/web/public/terminal-ui.02b89e2c.js +4 -0
  75. package/dist/web/public/terminal-ui.02b89e2c.js.br +0 -0
  76. package/dist/web/public/terminal-ui.02b89e2c.js.gz +0 -0
  77. package/dist/web/public/ultracode-panel.js.gz +0 -0
  78. package/dist/web/public/ultracode-windows.js +15 -19
  79. package/dist/web/public/ultracode-windows.js.br +0 -0
  80. package/dist/web/public/ultracode-windows.js.gz +0 -0
  81. package/dist/web/public/upload.html.gz +0 -0
  82. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  83. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  84. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  85. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  86. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  87. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  88. package/dist/web/public/vendor/xterm-predictive-echo.6cec0fba.js.gz +0 -0
  89. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  90. package/dist/web/public/vendor/xterm.css.gz +0 -0
  91. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  92. package/dist/web/public/voice-input.4ec670b0.js.gz +0 -0
  93. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  94. package/dist/web/public/webview-tabs.js +3 -0
  95. package/dist/web/public/webview-tabs.js.br +0 -0
  96. package/dist/web/public/webview-tabs.js.gz +0 -0
  97. package/dist/web/routes/file-routes.d.ts.map +1 -1
  98. package/dist/web/routes/file-routes.js +48 -6
  99. package/dist/web/routes/file-routes.js.map +1 -1
  100. package/dist/web/routes/session-routes.d.ts.map +1 -1
  101. package/dist/web/routes/session-routes.js +50 -12
  102. package/dist/web/routes/session-routes.js.map +1 -1
  103. package/dist/web/schemas.d.ts +5 -0
  104. package/dist/web/schemas.d.ts.map +1 -1
  105. package/dist/web/schemas.js +13 -0
  106. package/dist/web/schemas.js.map +1 -1
  107. package/dist/web/sensitive-path.d.ts.map +1 -1
  108. package/dist/web/sensitive-path.js +17 -0
  109. package/dist/web/sensitive-path.js.map +1 -1
  110. package/dist/web/server.d.ts +20 -0
  111. package/dist/web/server.d.ts.map +1 -1
  112. package/dist/web/server.js +51 -0
  113. package/dist/web/server.js.map +1 -1
  114. package/dist/web/session-listener-wiring.d.ts.map +1 -1
  115. package/dist/web/session-listener-wiring.js +5 -0
  116. package/dist/web/session-listener-wiring.js.map +1 -1
  117. package/package.json +1 -1
  118. package/skills/codeman/SKILL.md +41 -26
  119. package/skills/codeman/preamble.sh +8 -8
  120. package/skills/codeman/reference/endpoints.md +28 -34
  121. package/skills/codeman/reference/messaging.md +8 -6
  122. package/skills/codeman/reference/recipes.md +3 -3
  123. package/skills/codeman/reference/verbs.md +39 -24
  124. package/dist/web/public/app.445f1584.js +0 -34
  125. package/dist/web/public/app.445f1584.js.br +0 -0
  126. package/dist/web/public/app.445f1584.js.gz +0 -0
  127. package/dist/web/public/constants.822a486d.js.br +0 -0
  128. package/dist/web/public/constants.822a486d.js.gz +0 -0
  129. package/dist/web/public/i18n.86757605.js +0 -1
  130. package/dist/web/public/i18n.86757605.js.br +0 -0
  131. package/dist/web/public/i18n.86757605.js.gz +0 -0
  132. package/dist/web/public/mobile-handlers.bc45f976.js.br +0 -0
  133. package/dist/web/public/mobile-handlers.bc45f976.js.gz +0 -0
  134. package/dist/web/public/mobile.1eef31fe.css +0 -1
  135. package/dist/web/public/mobile.1eef31fe.css.br +0 -0
  136. package/dist/web/public/mobile.1eef31fe.css.gz +0 -0
  137. package/dist/web/public/panels-ui.1e4cb4b2.js.br +0 -0
  138. package/dist/web/public/panels-ui.1e4cb4b2.js.gz +0 -0
  139. package/dist/web/public/session-ui.20be58fa.js.br +0 -0
  140. package/dist/web/public/session-ui.20be58fa.js.gz +0 -0
  141. package/dist/web/public/settings-ui.eacb4f92.js.br +0 -0
  142. package/dist/web/public/settings-ui.eacb4f92.js.gz +0 -0
  143. package/dist/web/public/styles.70bf9c2a.css +0 -1
  144. package/dist/web/public/styles.70bf9c2a.css.br +0 -0
  145. package/dist/web/public/styles.70bf9c2a.css.gz +0 -0
  146. package/dist/web/public/subagent-windows.2a192c3e.js.br +0 -0
  147. package/dist/web/public/subagent-windows.2a192c3e.js.gz +0 -0
  148. package/dist/web/public/terminal-ui.c182a68d.js +0 -4
  149. package/dist/web/public/terminal-ui.c182a68d.js.br +0 -0
  150. package/dist/web/public/terminal-ui.c182a68d.js.gz +0 -0
@@ -0,0 +1,34 @@
1
+ "use strict";const _crashDiag={_entries:[],_maxEntries:50,_pageId:Date.now().toString(36)+"-"+Math.random().toString(36).slice(2,8),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),navigator.sendBeacon("/api/crash-diag",JSON.stringify({data:h,id:_crashDiag._pageId+"-prev"})))}catch{}_crashDiag.log("PAGE LOAD");function _crashDiagBeacon(){try{_crashDiag._entries.length>0&&navigator.sendBeacon("/api/crash-diag",JSON.stringify({data:_crashDiag._entries.join(`
4
+ `),id:_crashDiag._pageId}))}catch{}}if(setInterval(()=>{try{localStorage.setItem("codeman-crash-heartbeat",String(Date.now()))}catch{}_crashDiagBeacon()},2e3),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&_crashDiagBeacon()}),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_RESPAWN_BREAKER_TRIPPED,"_onSessionRespawnBreakerTripped"],[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.CRON_JOBS_CHANGED,"_onCronJobsChanged"],[SSE_EVENTS.CRON_JOB_DELETED,"_onCronJobsChanged"],[SSE_EVENTS.CRON_RUN_CREATED,"_onCronRunChanged"],[SSE_EVENTS.CRON_RUN_UPDATED,"_onCronRunChanged"],[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.REMOTE_SESSION_RECONNECTED,"_onRemoteSessionReconnected"],[SSE_EVENTS.REMOTE_RECONNECT_EXHAUSTED,"_onRemoteReconnectExhausted"],[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_ELICITATION_COMPLETE,"_onHookElicitationComplete"],[SSE_EVENTS.HOOK_ELICITATION_RESPONSE,"_onHookElicitationResponse"],[SSE_EVENTS.HOOK_STOP,"_onHookStop"],[SSE_EVENTS.HOOK_TEAMMATE_IDLE,"_onHookTeammateIdle"],[SSE_EVENTS.HOOK_TASK_COMPLETED,"_onHookTaskCompleted"],[SSE_EVENTS.APPROVAL_PENDING,"_onApprovalPending"],[SSE_EVENTS.APPROVAL_UPDATED,"_onApprovalUpdated"],[SSE_EVENTS.APPROVAL_RESOLVED,"_onApprovalResolved"],[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"],[SSE_EVENTS.SESSION_ORDER_CHANGED,"_onSessionOrderChanged"],[SSE_EVENTS.WEBVIEW_CHANGED,"_onWebviewChanged"]];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}const DEFAULT_SHORTCUTS=[{id:"show-shortcuts",group:"Panels",label:"Show Shortcuts",bindings:[{modifiers:["ctrl"],key:"?",code:"Slash"},{modifiers:["ctrl","shift"],key:"?"},{modifiers:["alt"],key:"?",code:"Slash"}],action:"showShortcutOverlay"},{id:"close-session",group:"Session",label:"Close Session",bindings:[{modifiers:["ctrl"],key:"w"}],action:"killActiveSession"},{id:"next-session",group:"Session",label:"Next Session",bindings:[{modifiers:["ctrl"],key:"Tab"}],action:"nextSession"},{id:"clear-terminal",group:"Terminal",label:"Clear Terminal",bindings:[{modifiers:["ctrl"],key:"l"}],action:"clearTerminal"},{id:"copy-selection",group:"Terminal",label:"Copy Selection",bindings:[{modifiers:["ctrl"],key:"c"},{modifiers:["ctrl","shift"],key:"C"}],action:"copyTerminalSelection"},{id:"increase-font",group:"Terminal",label:"Increase Font",bindings:[{modifiers:["ctrl"],key:"=",code:"Equal"},{modifiers:["ctrl"],key:"+",code:"Equal"}],action:"increaseFontSize"},{id:"decrease-font",group:"Terminal",label:"Decrease Font",bindings:[{modifiers:["ctrl"],key:"-",code:"Minus"}],action:"decreaseFontSize"},{id:"voice-input",group:"Terminal",label:"Voice Input",bindings:[{modifiers:["ctrl","shift"],key:"V"}],action:"toggleVoiceInput"},{id:"restore-terminal-size",group:"Terminal",label:"Restore Terminal Size",bindings:[{modifiers:["ctrl","shift"],key:"R"}],action:"restoreTerminalSize"},{id:"move-tab-left",group:"Tabs",label:"Move Active Tab Left",bindings:[{modifiers:["ctrl","shift"],key:"{",code:"BracketLeft"}],action:"moveActiveTabLeft"},{id:"move-tab-right",group:"Tabs",label:"Move Active Tab Right",bindings:[{modifiers:["ctrl","shift"],key:"}",code:"BracketRight"}],action:"moveActiveTabRight"},{id:"command-palette",group:"Session",label:"Find Open Session",bindings:[{modifiers:["ctrl"],key:"k",code:"KeyK"},{modifiers:["meta"],key:"k",code:"KeyK"},{modifiers:["alt"],key:"k",code:"KeyK"}],action:"openCommandPalette"},{id:"toggle-session-sidebar",group:"Session",label:"Toggle Session Sidebar",bindings:[{modifiers:["alt"],key:"b",code:"KeyB"}],action:"toggleSessionSidebar"},{id:"previous-next-session",group:"Session",label:"Previous / Next Session",displayBindings:["Alt/Option+[","Alt/Option+]"]},{id:"switch-tab-n",group:"Session",label:"Switch to Tab N",displayBindings:["Alt/Option+1-9"]},{id:"focus-tabs",group:"Tabs",label:"Focus Tabs",displayBindings:["ArrowLeft","ArrowRight","Home","End"]},{id:"activate-focused-tab",group:"Tabs",label:"Activate Focused Tab",displayBindings:["Enter","Space"]},{id:"insert-newline",group:"Terminal",label:"Insert Newline",displayBindings:["Shift+Enter","Ctrl+Enter"]},{id:"close-panels",group:"Panels",label:"Close Panels",displayBindings:["Escape"]}];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._wsTabNonce=this._clientId,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._fullHistoryLoaded=new Set,this._fullHistoryRepullAt=new Map,this._fullHistoryRepullInFlight=!1,this._fullHistoryRepullUseless=new Set,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.fileBrowserShowHidden=this._loadFileBrowserShowHidden?.()??!1,this.filePreviewContent="",this._toastContainer=null,this._tunnelUrl=null,this.tabAlerts=new Map,this.pendingHooks=new Map,this.approvals=new Map,this._ws=null,this._wsSessionId=null,this._wsReady=!1,this._wsState="disconnected",this._wsLastRecvAt=0,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._sseLastMessageAt=0,this._sseStaleTimeoutMs=window.CodemanSseStale?.TIMEOUT_MS??45e3,this._sseStaleWatchdog=null,this._connDownSince=null,this._nextSseRetryAt=null,this._offlineOverlayDismissed=!1,this._offlineRetryPending=!1,this._offlineUiTicker=null,this._lastOfflineUiKey="",this._connectionStatus="connected",this._clientId="",this._seqCounters=new Map,this._pendingDeliveries=new Map,this._lastIndicatorDescriptor=null,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._checkSseStale()}),this._localEchoOverlay=null,this._localEchoEnabled=!1,this._predictiveEcho=null,this._localEchoPolicy="off",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,n=t/1e6*75;return s+n}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();const e=this.loadAppSettingsFromStorage();KeyboardAccessoryBar.setMode(e.extendedKeyboardBar?"extended":"simple"),this.applyHeaderVisibilitySettings(),this.restorePlanUsageChip(),this.applySkin(),this.applyLocalization(),this.applySessionListLayout(),this.applyMonitorVisibility(),this.applyLineageLineSettings?.(),this._installLineageStripScrollListener?.(),this._setupTabMiddleClickClose(),this.initEntranceAnimations?.(),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.initWebviews?.(),this.setupEventListeners(),MobileDetection.isTouchDevice()){const s=n=>{n&&n.addEventListener("touchstart",i=>{if(!KeyboardHandler.keyboardVisible)return;const o=i.target.closest("button");o&&(i.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.applyLocalization(),this.applySessionListLayout(),this.applyMonitorVisibility(),this.applyLineageLineSettings?.(),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 n=performance.now();n-e<WEBGL_FALLBACK.GRACE_MS||evaluateWebGLLongTaskTrip(t,s.getEntries(),n)&&(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={showShortcutOverlay:()=>this.showShortcutOverlay(),killActiveSession:()=>this.killActiveSession(),nextSession:()=>this.nextSession(),clearTerminal:()=>this.clearTerminal(),restoreTerminalSize:()=>this.restoreTerminalSize(),increaseFontSize:()=>this.increaseFontSize(),decreaseFontSize:()=>this.decreaseFontSize(),toggleVoiceInput:()=>VoiceInput.toggle(),moveActiveTabLeft:()=>this.moveActiveTabLeft(),moveActiveTabRight:()=>this.moveActiveTabRight(),toggleSessionSidebar:()=>this.toggleSessionSidebar()};document.addEventListener("keydown",s=>{if(!(s.isComposing||s.keyCode===229)){if(this.shouldOpenCommandPaletteFromShortcut?.(s)){s.preventDefault(),this.openCommandPalette();return}if(s.key==="Escape"&&(this.closeAllPanels(),this.closeHelp(),this.attachmentHistoryDrawerOpen&&this.closeAttachmentHistory(),this.closeSessionManager(),this.closeCommandPalette?.(),this.closeShortcutOverlay?.(),this._isSessionSidebarOverlay()&&this.isSessionSidebarActive()&&!this.isSessionSidebarCollapsed()&&(this.toggleSessionSidebar(),document.getElementById("sidebarToggleBtn")?.focus())),s.altKey&&!s.ctrlKey&&!s.shiftKey){const i=(s.code||"").match(/^Digit([1-9])$/);if(i){const o=parseInt(i[1],10)-1;if(o<this.sessionOrder.length)s.preventDefault(),this.selectSession(this.sessionOrder[o]);else{const a=o-this.sessionOrder.length,l=(this.webviewOrder||[])[a];l&&(s.preventDefault(),this.openWebview(l))}return}if(s.code==="BracketLeft"){s.preventDefault(),this.prevSession();return}if(s.code==="BracketRight"){s.preventDefault(),this.nextSession();return}}if(!(!s.ctrlKey&&!s.metaKey&&!s.altKey))for(const n of this.getShortcutRegistry()){if(n.disabled||!n.action)continue;const i=e[n.action];if(i&&this.matchesShortcutEvent(s,n)){s.preventDefault(),i();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"),n=s&&s.querySelector(`.session-tab[data-id="${e}"]`);n&&n.classList.toggle("detached",t)}_watchDetachedWindow(e,t){const s=this._detachWatchTimers.get(e);s&&clearInterval(s);const n=setInterval(()=>{(!t||t.closed)&&(clearInterval(n),this._detachWatchTimers.delete(e),this._redock(e))},800);this._detachWatchTimers.set(e,n)}_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 n=document.getElementById("soloRedockBtn");n&&(n.style.display=""),document.title=t+" \u2014 "+(window.CodemanI18n?.displayName||"Codeman"),this.notificationManager&&(this.notificationManager.originalTitle=document.title);const i=document.querySelector(".header-brand .logo");i&&(i.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=(window.codemanT?.("Session ended")||"Session ended")+" \u2014 "+(window.CodemanI18n?.displayName||"Codeman")}connectSSE(){if(!navigator.onLine){this.setConnectionStatus("offline");return}this._clearTimer("sseReconnectTimeout"),this._sseStaleWatchdog&&(clearInterval(this._sseStaleWatchdog),this._sseStaleWatchdog=null),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=(n,i)=>{const o=a=>{this._sseLastMessageAt=Date.now(),i(a)};this.eventSource.addEventListener(n,o),t.push({event:n,handler:o})};if(this._sseListenerCleanup=()=>{for(const{event:n,handler:i}of t)this.eventSource&&this.eventSource.removeEventListener(n,i);t.length=0},this.eventSource.onopen=()=>{this.reconnectAttempts=0,this._sseLastMessageAt=Date.now(),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 n=this.reconnectAttempts<=1?200:Math.min(500*Math.pow(2,this.reconnectAttempts-2),3e4);this._nextSseRetryAt=Date.now()+n,this._updateConnectionLossUi(),this.sseReconnectTimeout=setTimeout(()=>this.connectSSE(),n)},!this._sseHandlerWrappers){this._sseHandlerWrappers=new Map;for(const[n,i]of _SSE_HANDLER_MAP){const o=this[i];this._sseHandlerWrappers.set(n,a=>{try{o.call(this,a.data?JSON.parse(a.data):{})}catch(l){console.error(`[SSE] Error handling ${n}:`,l)}})}}for(const[n]of _SSE_HANDLER_MAP)s(n,this._sseHandlerWrappers.get(n));for(const n of[SSE_EVENTS.SESSION_CREATED,SSE_EVENTS.SESSION_DELETED])s(n,()=>this._onSessionListMaybeChanged());s(SSE_EVENTS.DOCKER_EXPORT_COMPLETE,n=>{try{const i=n.data?JSON.parse(n.data):{};this.showToast(`Docker export ready: ${i.bundle} (${Math.round((i.sizeBytes||0)/1e6)} MB)`,"success"),this.refreshDockerExports?.()}catch(i){console.error("[SSE] docker export complete:",i)}}),s(SSE_EVENTS.DOCKER_EXPORT_FAILED,n=>{try{const i=n.data?JSON.parse(n.data):{};this.showToast(`Docker export failed: ${i.error||"unknown error"}`,"error")}catch(i){console.error("[SSE] docker export failed:",i)}}),s(SSE_EVENTS.DOCKER_IMPORT_COMPLETE,n=>{try{const i=n.data?JSON.parse(n.data):{};this.showToast(`Docker bundle imported as case "${i.name}"`,"success"),this.loadQuickStartCases?.(),this.refreshDockerExports?.()}catch(i){console.error("[SSE] docker import complete:",i)}}),s(SSE_EVENTS.DOCKER_CONTAINER_RECREATED,n=>{try{const i=n.data?JSON.parse(n.data):{};this.showToast(`Container for "${i.name}" removed \u2014 next launch recreates it with the new config`,"info")}catch(i){console.error("[SSE] docker container recreated:",i)}}),s(SSE_EVENTS.ADMIN_USERS_CHANGED,()=>{window.codemanAdmin?.onUsersChanged?.()}),s(SSE_EVENTS.DOCKER_IMAGE_BUILD_STARTED,()=>{this.showToast("Building the Codeman agent image (first Docker case, a few minutes)...","info",{duration:8e3})}),s(SSE_EVENTS.DOCKER_IMAGE_BUILD_COMPLETE,n=>{try{const i=n.data?JSON.parse(n.data):{};i.error?this.showToast(`Agent image build failed: ${i.error}`,"error"):this.showToast("Agent image ready. Starting the container...","success")}catch(i){console.error("[SSE] docker image build complete:",i)}}),s(SSE_EVENTS.DOCKER_IMAGE_BUILD_FAILED,n=>{try{const i=n.data?JSON.parse(n.data):{};this.showToast(`Agent image build failed: ${i.error||"unknown error"}`,"error")}catch(i){console.error("[SSE] docker image build failed:",i)}}),s(SSE_EVENTS.SESSION_PINNED,n=>{let i=null;try{i=JSON.parse(n.data)}catch{}if(i&&i.id){const o=this.sessions.get(i.id);o&&(o.pinned=i.pinned===!0,o.pinnedAt=i.pinned?i.pinnedAt:void 0)}this._onSessionListMaybeChanged()}),s(SSE_EVENTS.HEARTBEAT,()=>{}),this._sseStaleWatchdog=setInterval(()=>this._checkSseStale(),5e3)}_checkSseStale(){const e=window.CodemanSseStale;if(!e)return;const t=Date.now();e.compute({lastMessageAt:this._sseLastMessageAt,now:t,status:this._connectionStatus,isOnline:this.isOnline,timeoutMs:this._sseStaleTimeoutMs})&&(console.log(`[SSE] stream stale: no frame for ${t-this._sseLastMessageAt}ms (threshold ${this._sseStaleTimeoutMs}ms), forcing reconnect`),this.connectSSE())}_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.markSessionTabEntering?.(e.id),this.markTerminalEntering?.(e.id),e.parentSessionId&&this.markConnectionLineEntering?.("lineage:"+e.id),this.renderSessionTabs(),this.updateCost(),this.sessions.size===1&&this.startSystemStatsPolling()}_onSessionUpdated(e){const t=e.session||e,s=this.sessions.get(t.id),n=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),n&&(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,n)=>s+n.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(l=>l.test(o)):!0).join(`
11
+ `).replace(/[ \t]+$/gm,"").replace(/\n{4,}/g,`
12
+
13
+
14
+ `).trim()}_preprocessAsciiArt(e){const t=/[─-╿▀-▟]/,s=/```[\s\S]*?```/g,n=[];return e.replace(s,a=>(n.push(a),`__CODEMAN_FENCE_${n.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,l)=>n[Number(l)])}_renderMarkdown(e){const t=e||"";if(typeof marked<"u"&&marked.parse)try{const n=this._preprocessAsciiArt(t);let i=this._sanitizeHtml(marked.parse(n,{breaks:!0,gfm:!0}));i=i.replace(/<table>/g,'<div class="rv-table-wrap"><table>').replace(/<\/table>/g,"</table></div>");const o=/[─-╿▀-▟]/,a=document.createElement("template");return a.innerHTML=i,a.content.querySelectorAll("pre > code").forEach(l=>{const r=l.parentElement,d=o.test(l.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 u=document.createElement("button");if(u.className="rv-copy-btn",u.type="button",u.setAttribute("aria-label","Copy code"),u.setAttribute("title","Copy code"),p.appendChild(u),d){r.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(_)}r.parentNode.insertBefore(c,r),c.appendChild(p),c.appendChild(r)}),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("a.rv-path");if(s){t.preventDefault(),t.stopPropagation();const r=s.dataset.path;r&&this.openFilePreview(r,this.activeSessionId);return}const n=t.target.closest(".rv-copy-btn");if(n){t.preventDefault(),t.stopPropagation();const r=n.closest(".rv-code-wrap")?.querySelector("pre code"),d=r?await this._copyText(r.textContent||""):!1;n.classList.remove("rv-copied","rv-copy-failed"),n.classList.add(d?"rv-copied":"rv-copy-failed"),clearTimeout(n._resetTimer),n._resetTimer=setTimeout(()=>{n.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 o=i.closest(".rv-diagram-wrap"),a=o?.querySelector("pre.rv-diagram");if(!a||!o)return;const l=a.classList.toggle("rv-nowrap");o.classList.toggle("rv-wrap-nowrap",l)}))}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}}_buildResponseViewerMessage(e,t,s){const n=document.createElement("div"),i=t==="user";n.className="rv-message "+(i?"rv-msg-user":"rv-msg-assistant");const o=document.createElement("div");o.className="rv-role "+(i?"rv-role-user":"rv-role-assistant"),o.textContent=i?"You":s,n.appendChild(o);const a=document.createElement("div");return a.className="rv-text",a.innerHTML=this._renderMarkdown(e),this._linkifyFilePaths(a),n.appendChild(a),n}_linkifyFilePaths(e){if(!e||typeof document>"u"||typeof absoluteFilePathPattern!="function")return;const t=absoluteFilePathPattern(),s=document.createTreeWalker(e,NodeFilter.SHOW_TEXT),n=[];for(let i=s.nextNode();i;i=s.nextNode())i.parentElement?.closest("a")||(t.lastIndex=0,t.test(i.nodeValue||"")&&n.push(i));for(const i of n){const o=i.nodeValue,a=document.createDocumentFragment();let l=0,r;for(t.lastIndex=0;(r=t.exec(o))!==null;){const d=r[1];r.index>l&&a.appendChild(document.createTextNode(o.slice(l,r.index)));const c=document.createElement("a");c.className="rv-path",c.href="#",c.dataset.path=d,c.title=d,c.textContent=d,a.appendChild(c),l=r.index+d.length}l<o.length&&a.appendChild(document.createTextNode(o.slice(l))),i.parentNode?.replaceChild(a,i)}}_getResponseViewerAgentLabel(){const e=this.sessions.get(this.activeSessionId)?.mode;return e==="codex"?"Codex":e==="gemini"?"Gemini":e==="antigravity"?"Antigravity":e==="pi"?"Pi":e==="opencode"?"OpenCode":"Claude"}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||"";const a=this.sessions.get(this.activeSessionId)?.mode||"claude";if(!o&&(a==="claude"||a==="shell")){const p=(await(await fetch(`/api/sessions/${this.activeSessionId}/terminal`)).json())?.data??{};p.terminalBuffer&&(o=this._cleanTerminalBuffer(p.terminalBuffer))}const l=document.getElementById("responseViewerBody");o?(l.innerHTML="",l.appendChild(this._buildResponseViewerMessage(o,"assistant",this._getResponseViewerAgentLabel())),this._bindResponseViewerInteractions(l)):l.textContent=window.codemanT?.("No response yet \u2014 send a message in this session first.")||"No response yet \u2014 send a message in this session first.";const r=document.getElementById("responseViewerTitle"),d=document.getElementById("responseViewerMore");r&&(r.textContent="Last Response"),d&&(d.style.display="",d.textContent="More"),e.classList.add("visible"),t.classList.add("visible"),l.scrollTop=0}catch(n){console.error("Failed to load response:",n)}}async loadFullContext(){if(!this.activeSessionId)return;const e=document.getElementById("responseViewerMore");e&&(e.textContent="...");try{const n=((await(await fetch(`/api/sessions/${this.activeSessionId}/last-response?context=full`)).json())?.data??{}).messages||[],i=document.getElementById("responseViewerBody"),o=document.getElementById("responseViewerTitle");if(!i)return;if(n.length===0){i.textContent="No conversation history available";return}const a=this._getResponseViewerAgentLabel();i.innerHTML="";for(const l of n)i.appendChild(this._buildResponseViewerMessage(l.text,l.role,a));this._bindResponseViewerInteractions(i),o&&(o.textContent=`Conversation (${n.length} messages)`),e&&(e.style.display="none"),i.scrollTop=i.scrollHeight}catch(t){console.error("Failed to load context:",t)}finally{e&&(e.textContent="More")}}async _onSessionNeedsRefresh(){if(!this.activeSessionId||!this.terminal||this._isLoadingBuffer)return;const e=this.activeSessionId;try{let t=await fetch(`/api/sessions/${e}/terminal?full=1`),s=(await t.json())?.data??{};if(s.terminalBuffer&&this._replayWouldShrinkBuffer(s.terminalBuffer)&&(t=await fetch(`/api/sessions/${e}/terminal?tail=${TERMINAL_TAIL_SIZE}`),s=(await t.json())?.data??{}),this.activeSessionId!==e)return;if(s.terminalBuffer){const n=this.terminal.buffer?.active,i=n?Math.max(0,(n.baseY||0)-(n.viewportY||0)):0;this.terminal.clear(),this.terminal.reset(),await this.chunkedTerminalWrite(s.terminalBuffer),this._setHistoryTruncation(e,s);const o=computeRewriteScrollLine({linesFromBottom:i,baseY:this.terminal.buffer?.active?.baseY??0});o===null||typeof this.terminal.scrollToLine!="function"?this.terminal.scrollToBottom():this.terminal.scrollToLine(o),this._localEchoOverlay?.rerender(),this.activeSessionId&&this.sendResize(this.activeSessionId)}}catch(t){console.error("needsRefresh reload failed:",t)}}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 n=s.terminalBuffer.replace(DEC_SYNC_STRIP_RE,"");await this.chunkedTerminalWrite(n)}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)}_onSessionRespawnBreakerTripped(e){const s=this.sessions.get(e.sessionId)?.name||"Session";this.showToast?.(`${s} stopped: repeated crashes detected. Restart to retry.`,"error")}_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=r=>r&&typeof r.usedPercentage=="number"?Math.round(r.usedPercentage):null,n=s(e.fiveHour),i=s(e.sevenDay);if(n===null&&i===null)return;const o=r=>r>=85?"pu-red":r>=60?"pu-yellow":"pu-green",a=(r,d)=>{if(d===null)return"";const c=Math.round(Number(d));return Number.isFinite(c)?`<span class="pu-win"><span class="pu-label">${r}</span><span class="pu-val ${o(c)}">${c}%</span></span>`:""};t.innerHTML=[a("5h",n),a("7d",i)].filter(Boolean).join('<span class="pu-sep">\xB7</span>');const l=r=>r&&r.resetAt?new Date(r.resetAt).toLocaleString():"\u2014";t.title=`Claude plan usage
15
+ 5-hour limit: ${n??"\u2014"}% used (resets ${l(e.fiveHour)})
16
+ Weekly limit: ${i??"\u2014"}% used (resets ${l(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,e==="connected"?(this._connDownSince=null,this._nextSseRetryAt=null,this._offlineOverlayDismissed=!1):this._connDownSince===null&&(this._connDownSince=Date.now()),this._updateConnectionIndicator(),this._updateConnectionLossUi(),e==="connected"&&this._redeliverSweep()}_connectWs(e){this._disconnectWs(),this._wsState="connecting",this._updateConnectionIndicator();const t=location.protocol==="https:"?"wss:":"ws:",s=this._clientId?`${this._clientId}:${this._wsTabNonce}`:"",n=s?`?cid=${encodeURIComponent(s)}`:"",i=`${t}//${location.host}/ws/sessions/${e}/terminal${n}`,o=new WebSocket(i);this._ws=o,this._wsSessionId=e,o.onopen=()=>{this._ws===o&&(this._wsReady=!0,this._wsState="connected",this._wsReconnectAttempts=0,this._updateConnectionIndicator(),this.sendResize(e)?.catch?.(()=>{}),this._startMobileResizeRetry(e),this._onWsReady(e))},o.onmessage=a=>{if(this._ws===o){this._wsLastRecvAt=Date.now();try{const l=JSON.parse(a.data);l.t==="o"?this._onSessionTerminal({id:e,data:l.d}):l.t==="c"?this._onSessionClearTerminal({id:e}):l.t==="r"?this._onSessionNeedsRefresh({id:e}):l.t==="ia"&&this._onWsInputAck(l.seq)}catch{}}},o.onclose=a=>{if(this._ws!==o)return;this._ws=null,this._wsSessionId=null,this._wsReady=!1,this._stopMobileResizeRetry();const l=window.CodemanWsReconnect.plan(a.code,this._wsReconnectAttempts||0);_crashDiag.log(`WS CLOSE code=${a.code} reason=${a.reason||""} action=${l.action} attempts=${this._wsReconnectAttempts||0}`);const r=this.activeSessionId===e;if(l.action==="give-up")this._wsState=r?"fallback":"disconnected",this._updateConnectionIndicator();else if(l.action==="reconnect")if(r){this._wsState="reconnecting",this._updateConnectionIndicator();const d=l.delayMs+Math.floor(Math.random()*250);this._wsReconnectAttempts=(this._wsReconnectAttempts||0)+1,this._wsReconnectTimer=setTimeout(()=>{this._wsReconnectTimer=null,this.activeSessionId===e&&this._connectWs(e)},d)}else this._wsState="disconnected",this._updateConnectionIndicator();else this._wsState=r?"fallback":"disconnected",this._updateConnectionIndicator(),r&&(this._wsReconnectAttempts=(this._wsReconnectAttempts||0)+1,this._wsReconnectTimer=setTimeout(()=>{this._wsReconnectTimer=null,this.activeSessionId===e&&this._connectWs(e)},l.delayMs))},o.onerror=()=>{}}_disconnectWs(){this._clearTimer("_wsReconnectTimer"),this._wsState="disconnected",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)}_sendInputEphemeral(e,t){if(!(!e||!t)){if(this._ws&&this._ws.readyState===WebSocket.OPEN&&this._wsSessionId===e)try{this._ws.send(JSON.stringify({t:"i",d:t}));return}catch{}try{fetch(`/api/sessions/${e}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:t}),keepalive:!0}).catch(()=>{})}catch{}}}_reliableSend(e,t,s){const i={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(i),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 n=s[0];n.tries++,n.sentAt=Date.now();let i=null;try{const o={input:n.data,seq:n.seq,clientId:this._clientId};n.useMux&&(o.useMux=!0),i=await fetch(`/api/sessions/${e}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o),keepalive:n.data.length<65536})}catch{i=null}if(i&&i.ok)this._ackDelivery(e,n.seq);else if(i&&(i.status===404||i.status===410))this._ackDelivery(e,n.seq);else break}}finally{this._postDraining.delete(e)}})())}}_ackDelivery(e,t){const s=this._pendingDeliveries.get(e);if(s){const n=s.findIndex(i=>i.seq===t);n!==-1&&(s.splice(n,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 n=t[0],i=n&&n.sentAt&&Date.now()-n.sentAt>this._reliableAckTimeoutMs,o=Date.now()-this._wsLastRecvAt>this._reliableAckTimeoutMs;if(i&&o){try{this._ws.close()}catch{}continue}if(i)for(const a of t)a.sentAt&&Date.now()-a.sentAt>this._reliableAckTimeoutMs&&(a.sentAt=0)}this._drainSession(e)}}_pendingBytes(){let e=0,t=0;for(const s of this._pendingDeliveries.values())for(const n of s)e+=n.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,n]of Object.entries(t.seqs))Number.isFinite(n)&&this._seqCounters.set(s,n);if(t&&t.pending)for(const[s,n]of Object.entries(t.pending))Array.isArray(n)&&n.length&&this._pendingDeliveries.set(s,n.filter(i=>i&&typeof i.data=="string"&&Number.isInteger(i.seq)).map(i=>({seq:i.seq,data:i.data,useMux:!!i.useMux,ts:i.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[i,o]of this._seqCounters)e[i]=o;const t={};let s=0;for(const[i,o]of this._pendingDeliveries)if(o.length){t[i]=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 n=s>this._reliableMaxBytes?{seqs:e}:{seqs:e,pending:t};localStorage.setItem("codeman:pendingInput",JSON.stringify(n))}catch{}}_computeConnectionDescriptor(){const{bytes:e,count:t}=this._pendingBytes(),s=t>0,i=e>4,o=l=>l<1024?`${l}B`:`${(l/1024).toFixed(1)}KB`,a=i?` \xB7 ${o(e)} queued`:"";if(!this.isOnline||this._connectionStatus==="offline")return{display:"flex",dotClass:"connection-dot offline",text:i?`Offline (${o(e)} queued)`:"Offline",title:"No network connection"};if(this.activeSessionId){let l,r,d;switch(this._wsState){case"connected":l="connected",r="WS",d="Terminal connected over WebSocket";break;case"fallback":l="fallback",r="HTTP",d="WebSocket unavailable \u2014 input sent over HTTP";break;case"reconnecting":l="reconnecting",r="WS\u2026",d="Reconnecting WebSocket";break;default:l="reconnecting",r="WS\u2026",d="Connecting WebSocket";break}return{display:"flex",dotClass:`connection-dot ${l}`,text:`${r}${a}`,title:d}}return this._connectionStatus==="reconnecting"||this._connectionStatus==="disconnected"?{display:"flex",dotClass:"connection-dot reconnecting",text:i?`Reconnecting (${o(e)} queued)`:"Reconnecting...",title:"Reconnecting to server"}:s?{display:"flex",dotClass:"connection-dot draining",text:i?`Sending ${o(e)}...`:"Sending...",title:"Delivering queued input"}:{display:"none",dotClass:"",text:"",title:""}}_updateConnectionIndicator(){const e=this.$("connectionIndicator"),t=this.$("connectionDot"),s=this.$("connectionText");if(!e||!t||!s)return;const n=this._computeConnectionDescriptor(),i=this._lastIndicatorDescriptor;i&&i.display===n.display&&i.dotClass===n.dotClass&&i.text===n.text&&i.title===n.title||(this._lastIndicatorDescriptor=n,e.style.display=n.display,n.display!=="none"&&(t.className=n.dotClass,s.textContent=n.text,e.title=n.title))}setupOnlineDetection(){window.addEventListener("online",()=>{this.isOnline=!0,this.reconnectAttempts=0,this._connDownSince=Date.now(),this.connectSSE(),this._redeliverSweep()}),window.addEventListener("offline",()=>{this.isOnline=!1,this.setConnectionStatus("offline")})}_updateConnectionLossUi(){const e=window.CodemanConnectionLoss,t=this.$("offlineBanner"),s=this.$("offlineOverlay");if(!e||!t||!s)return;const n=e.compute({isOnline:this.isOnline,status:this._connectionStatus,everLoaded:this._initGeneration>0,downSince:this._connDownSince,now:Date.now(),nextRetryAt:this._nextSseRetryAt,overlayDismissed:this._offlineOverlayDismissed,retryPending:this._offlineRetryPending});this._connDownSince===null?this._stopOfflineTicker():this._startOfflineTicker();const i=this._offlineRetryPending?"Reconnecting\u2026":n.retryInSec!=null&&n.retryInSec>0?`Retrying in ${n.retryInSec}s`:"Retrying\u2026",o=`${n.mode}|${n.kind}|${i}`;if(o!==this._lastOfflineUiKey){if(this._lastOfflineUiKey=o,t.hidden=n.mode!=="banner",s.hidden=n.mode!=="overlay",document.body.classList.toggle("connection-lost",n.mode!=="hidden"),n.mode==="banner"){const a=this.$("offlineBannerText"),l=this.$("offlineBannerDetail");a&&(a.textContent=n.title),l&&(l.textContent=i)}else if(n.mode==="overlay"){const a=this.$("offlineOverlayTitle"),l=this.$("offlineOverlayBody"),r=this.$("offlineOverlayHost"),d=this.$("offlineOverlayStatus");a&&(a.textContent=n.title),l&&(l.textContent=n.detail),r&&(r.textContent=location.host),d&&(d.textContent=i)}}}_startOfflineTicker(){this._offlineUiTicker||(this._offlineUiTicker=setInterval(()=>this._updateConnectionLossUi(),1e3))}_stopOfflineTicker(){this._offlineUiTicker&&(clearInterval(this._offlineUiTicker),this._offlineUiTicker=null)}retryConnection(){this._offlineRetryPending=!0,this._nextSseRetryAt=null,this.reconnectAttempts=0,this._clearTimer("sseReconnectTimeout"),this.isOnline=navigator.onLine,this._lastOfflineUiKey="",this._updateConnectionLossUi(),this.connectSSE(),this.activeSessionId&&this._wsState!=="connected"&&(this._wsReconnectAttempts=0,this._connectWs(this.activeSessionId)),this._clearTimer("_offlineRetryTimer"),this._offlineRetryTimer=setTimeout(()=>{this._offlineRetryPending=!1,this._lastOfflineUiKey="",this._updateConnectionLossUi()},1500)}dismissOfflineOverlay(){this._offlineOverlayDismissed=!0,this._lastOfflineUiKey="",this._updateConnectionLossUi()}_updateCjkInputState(){const e=document.getElementById("cjkInput");if(!e)return;const t=this.loadAppSettingsFromStorage(),s=this.getDefaultSettings?.()||{},i=(this._serverCjkOverride||(t.cjkInputEnabled??s.cjkInputEnabled??!1))&&!!this.activeSessionId;e.classList.toggle("cjk-input-visible",!!i),document.body.classList.toggle("cjk-input-visible",!!i),e.style.display=i?"block":"none",e.setAttribute("aria-hidden",i?"false":"true"),i||(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._predictiveEcho?.clearPredictions(),this.pendingHooks.clear(),this.approvals?.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 i=this.$("versionDisplay"),o=this.$("headerVersion");i&&(i.textContent=`v${e.version}`,i.title=`Codeman v${e.version}`),o&&(o.textContent=`v${e.version}`,o.title=`Codeman v${e.version}`)}VoiceInput.cleanup(),this._resetAllAppState(),e.sessions.forEach(i=>{this.sessions.set(i.id,i),(i.ralphLoop||i.ralphTodos)&&!this.ralphClosedSessions.has(i.id)&&this.ralphStates.set(i.id,{loop:i.ralphLoop||null,todos:i.ralphTodos||[]})});try{localStorage.removeItem("codeman-tab-meta")}catch{}if(Array.isArray(e.sessionOrder)&&e.sessionOrder.length)try{localStorage.setItem("codeman-session-order",JSON.stringify(e.sessionOrder))}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((i,o)=>i+(o.totalCost||0),0),this.totalCost+=e.scheduledRuns.reduce((i,o)=>i+(o.totalCost||0),0);const s=e.scheduledRuns.find(i=>i.status==="running");if(s&&(this.currentRun=s,this.showTimer()),this.updateCost(),this.renderSessionTabs(),this.seedApprovals?.(),this.sessions.size>0?this.startSystemStatsPolling():this.stopSystemStatsPolling(),this.cleanupAllFloatingWindows(),e.subagents&&(this.subagents.clear(),this.subagentActivity.clear(),this.subagentToolResults.clear(),e.subagents.forEach(i=>{this.subagents.set(i.agentId,i)}),this.renderSubagentPanel(),this.subagentParentMap.clear(),this.loadSubagentParentMap().then(()=>{for(const[i,o]of this.subagentParentMap){const a=this.subagents.get(i);if(a&&this.sessions.has(o)){a.parentSessionId=o;const l=this.sessions.get(o);l&&(a.parentSessionName=this.getSessionName(l)),this.subagents.set(i,a)}}for(const[i]of this.subagents)this.subagentParentMap.has(i)||this.findParentSessionForSubagent(i);this.restoreSubagentWindowStates()})),e.workflowRuns&&this.seedWorkflowRuns(e.workflowRuns),t!==this._initGeneration)return;if(this.isSoloWindow){this._applySoloMode();return}const n=this.activeSessionId;if(this.activeSessionId=null,this.sessionOrder.length>0){let i=n;if(!i||!this.sessions.has(i))try{i=localStorage.getItem("codeman-active-session")}catch{}i&&this.sessions.has(i)?this.selectSession(i):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)}getSessionListLayout(){if(this.soloSessionId)return"header";const e=this.loadAppSettingsFromStorage(),t=this.getDefaultSettings();return(e.sessionListLayout??t.sessionListLayout??"header")==="sidebar"?"sidebar":"header"}isSessionSidebarActive(){return document.documentElement.dataset.sessionList==="sidebar"}_isSessionSidebarOverlay(){return window.innerWidth<1024}isSessionSidebarCollapsed(){if(this._sidebarCollapsedOverride!==void 0)return this._sidebarCollapsedOverride;let e=null;try{e=localStorage.getItem("codeman-sidebar-collapsed")}catch{}return e===null?this._isSessionSidebarOverlay():e==="1"}shouldToggleSessionSidebarFromShortcut(e){if(!e||!e.ctrlKey&&!e.metaKey&&!e.altKey||!this.isSessionSidebarActive()||typeof this.getShortcutRegistry!="function"||typeof this.matchesShortcutEvent!="function")return!1;const t=this.getShortcutRegistry().find(s=>s.id==="toggle-session-sidebar");return!t||t.disabled?!1:this.matchesShortcutEvent(e,t)}applySessionListLayout(){const e=this.getSessionListLayout(),t=this.isSessionSidebarCollapsed(),s=document.documentElement.dataset.sessionList,n=document.getElementById("sessionTabs"),i=document.getElementById("sessionTabsHost"),o=document.getElementById("sessionSidebarList");if(!n||!i||!o)return;const a=e==="sidebar"?o:i;n.parentElement!==a&&a.appendChild(n),document.documentElement.dataset.sessionList=e,document.documentElement.dataset.sidebar=t?"collapsed":"expanded",n.setAttribute("aria-orientation",e==="sidebar"?"vertical":"horizontal");const l=document.getElementById("sidebarToggleBtn");if(l){l.classList.toggle("btn-sidebar-toggle--hidden",e!=="sidebar");const c=t?"Expand session sidebar":"Collapse session sidebar";l.setAttribute("aria-expanded",t?"false":"true"),l.setAttribute("aria-label",c),l.setAttribute("title",c)}const r=document.getElementById("sessionSidebar");if(r){r.classList.toggle("open",e==="sidebar"&&!t);const c=e==="sidebar"&&t&&this._isSessionSidebarOverlay();r.toggleAttribute("inert",c),c?r.setAttribute("aria-hidden","true"):r.removeAttribute("aria-hidden")}if(e!=="sidebar"){this._sidebarFilter="";const c=document.getElementById("sessionSidebarFilter");c&&(c.value="")}const d=this._tallTabsEnabled;this.applyTabWrapSettings(),s!==e&&d===this._tallTabsEnabled&&this._fullRenderSessionTabs(),e==="sidebar"&&n.classList.remove("tabs-auto-wrap"),this.applySidebarFilter(this._sidebarFilter),this.updateSidebarCount(),this.updateConnectionLines(),document.getElementById("welcomeOverlay")?.classList.contains("visible")&&this.showHomeSessions?.()}toggleSessionSidebar(){if(!this.isSessionSidebarActive())return;const e=!this.isSessionSidebarCollapsed();this._sidebarCollapsedOverride=e;try{localStorage.setItem("codeman-sidebar-collapsed",e?"1":"0")}catch{}e&&this.$("sessionSidebar")?.contains(document.activeElement)&&document.getElementById("sidebarToggleBtn")?.focus(),this.applySessionListLayout(),!e&&this._isSessionSidebarOverlay()&&this.$("sessionTabs")?.querySelector(".session-tab.active")?.focus()}closeSessionSidebarOnHandheld(){this._isSessionSidebarOverlay()&&(!this.isSessionSidebarActive()||this.isSessionSidebarCollapsed()||this.toggleSessionSidebar())}updateSidebarCount(){const e=document.getElementById("sessionSidebarCount");e&&(e.textContent=String(this.sessions?.size??0))}applySidebarFilter(e){this._sidebarFilter=(e??"").trim().toLowerCase();const t=this.$("sessionTabs");if(!t)return;const n=this.isSessionSidebarActive()&&document.documentElement.dataset.sidebar!=="collapsed"?this._sidebarFilter:"";for(const i of t.querySelectorAll(".session-tab")){if(!n){i.classList.remove("tab-filtered-out");continue}const o=`${i.getAttribute("aria-label")||""} ${i.getAttribute("title")||""}`.toLowerCase();i.classList.toggle("tab-filtered-out",!o.includes(n))}}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 n of s)n.dataset.id===e?n.classList.add("active"):n.classList.remove("active");this._scrollActiveTabIntoView(e)}_scrollActiveTabIntoView(e,t="smooth"){const s=this.$("sessionTabs");if(!s)return;const n=e&&s.querySelector(`.session-tab[data-id="${e}"]`)||s.querySelector(".session-tab.active");if(!n)return;if(this.isSessionSidebarActive()){n.scrollIntoView({block:"nearest"});return}const i=window.CodemanTabOverflow?.computeTabScrollLeft;if(!i)return;const o=s.getBoundingClientRect(),a=n.getBoundingClientRect(),l=i({scrollLeft:s.scrollLeft,clientWidth:s.clientWidth,scrollWidth:s.scrollWidth,tabLeft:a.left-o.left+s.scrollLeft,tabWidth:a.width});if(Math.abs(l-s.scrollLeft)<1)return;const r=window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches;typeof s.scrollTo=="function"?s.scrollTo({left:l,behavior:r?"auto":t}):s.scrollLeft=l}_tabAnchor(e){return this.isSessionSidebarActive()?{x:e.right,y:e.top+e.height/2,spawnLeft:e.right+14,spawnTop:e.top,vertical:!1}:{x:e.left+e.width/2,y:e.bottom,spawnLeft:e.left,spawnTop:e.bottom,vertical:!0}}_tabConnectorPath(e,t){if(e.vertical){const o=t.left+t.width/2,a=t.top,l=(e.y+a)/2;return`M ${e.x} ${e.y} C ${e.x} ${l}, ${o} ${l}, ${o} ${a}`}const s=t.left,n=t.top+t.height/2,i=(e.x+s)/2;return`M ${e.x} ${e.y} C ${i} ${e.y}, ${i} ${n}, ${s} ${n}`}_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 n=document.createElement("span");n.className="tab-load-spinner",n.setAttribute("aria-hidden","true");const i=t.querySelector(".tab-number");i?i.insertAdjacentElement("afterend",n):t.insertBefore(n,t.firstChild)}}else t.setAttribute("aria-busy","false"),delete t.dataset.loadPhase,t.querySelector(".tab-load-spinner")?.remove()}_renderSessionTabsImmediate(){if(this._inlineRenameActive)return;const e=this.$("sessionTabs"),t=e.querySelectorAll(".session-tab[data-id]"),s=new Set([...t].map(r=>r.dataset.id)),n=new Set(this.sessions.keys()),i=[...e.querySelectorAll(".session-tab[data-webview-id]")].map(r=>r.dataset.webviewId),o=(this.webviewOrder||[]).filter(r=>this.webviews?.has(r)),a=i.length===o.length&&i.every((r,d)=>r===o[d]);if(s.size===n.size&&[...s].every(r=>n.has(r))&&a)for(const[r,d]of this.sessions){const c=e.querySelector(`.session-tab[data-id="${r}"]`);if(!c)continue;const p=r===this.activeSessionId&&!this.activeWebviewId,u=d.status||"idle",_=this.getSessionName(d),v=d.taskStats||{running:0,total:0},w=v.running>0,R=this.terminalLoadStates.get(r);if(p&&!c.classList.contains("active")?c.classList.add("active"):!p&&c.classList.contains("active")&&c.classList.remove("active"),c.classList.toggle("tab-loading",!!R),R){if(c.setAttribute("aria-busy","true"),c.dataset.loadPhase=R.phase,!c.querySelector(".tab-load-spinner")){const S=document.createElement("span");S.className="tab-load-spinner",S.setAttribute("aria-hidden","true");const g=c.querySelector(".tab-number");g?g.insertAdjacentElement("afterend",S):c.insertBefore(S,c.firstChild)}}else c.setAttribute("aria-busy","false"),delete c.dataset.loadPhase,c.querySelector(".tab-load-spinner")?.remove();const C=this.tabAlerts.get(r),k=C==="action",E=C==="idle",O=c.classList.contains("tab-alert-action"),f=c.classList.contains("tab-alert-idle");if(k&&!O?(c.classList.add("tab-alert-action"),c.classList.remove("tab-alert-idle")):E&&!f?(c.classList.add("tab-alert-idle"),c.classList.remove("tab-alert-action")):!C&&(O||f)&&c.classList.remove("tab-alert-action","tab-alert-idle"),!c.querySelector(".tab-number")){const S=this.sessionOrder.indexOf(r);if(S>=0&&S<9){const g=document.createElement("span");g.className="tab-number",g.textContent=String(S+1),c.insertBefore(g,c.firstChild)}}const b=c.querySelector(".tab-status");b&&!b.classList.contains(u)&&(b.className=`tab-status ${u}`);const L=c.querySelector(".tab-name");if(L){const S=parseSessionPrefix(_),g=S&&S.suffix?S.suffix:_;L.textContent!==g&&(L.textContent=g,c.title=S&&S.suffix?d.workingDir?`${S.prefix} (${d.workingDir})`:S.prefix:d.workingDir||"")}const m=c.querySelector(".tab-badge");if(w)if(m)m.textContent!==String(v.running)&&(m.textContent=v.running);else{this._fullRenderSessionTabs();return}else if(m){this._fullRenderSessionTabs();return}const y=c.querySelector(".tab-subagent-badge"),A=this.minimizedSubagents.get(r),T=A?.size||0;if(T>0&&y){const S=y.querySelector(".subagent-label"),g=T===1?"AGENT":`AGENTS (${T})`;S&&S.textContent!==g&&(S.textContent=g);const I=y.querySelector(".subagent-dropdown");if(I){const x=this.renderSubagentTabBadge(r,A),N=document.createElement("div");N.innerHTML=x;const D=N.querySelector(".subagent-dropdown");D&&(I.innerHTML=D.innerHTML)}}else if(T>0&&!y){const S=this.renderSubagentTabBadge(r,A),g=c.querySelector(".tab-actions");g&&g.insertAdjacentHTML("beforebegin",S)}else T===0&&y&&y.remove()}else this._fullRenderSessionTabs();this._lastRenderedActiveTabId=this.activeSessionId,this.updateTabOverflowMode(),this._applyTabEntrances?.(),this._refreshMobileOverviewIfVisible?.(),this._refreshHomeSessionsIfVisible?.(),this._lineageEdgeCount>0&&this.updateConnectionLines(),this.applySidebarFilter(this._sidebarFilter),this.updateSidebarCount()}updateTabOverflowMode(){const e=this.$("sessionTabs");if(!e)return;if(this.isSessionSidebarActive()){e.classList.remove("tabs-auto-wrap");return}const t=MobileDetection.getDeviceType(),s=this.loadAppSettingsFromStorage(),n=this.getDefaultSettings(),i=t==="desktop"?s.tabTwoRows??n.tabTwoRows??!1:!1;if(i||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:i,tabCount:this.sessions.size,scrollWidth:e.scrollWidth,clientWidth:e.clientWidth}):e.scrollWidth>e.clientWidth+1;e.classList.toggle("tabs-auto-wrap",o)}_setupTabMiddleClickClose(){const e=this.$("sessionTabs");!e||this._tabAuxClickBound||(this._tabAuxClickBound=!0,e.addEventListener("auxclick",t=>{if(t.button!==1)return;const s=t.target.closest?.(".session-tab");s&&(t.preventDefault(),t.stopPropagation(),s.dataset.id?this.requestCloseSession(s.dataset.id):s.dataset.webviewId&&this.closeWebviewTab?.(s.dataset.webviewId))}))}_fullRenderSessionTabs(){if(this._inlineRenameActive)return;const e=this.$("sessionTabs");this.isSessionSidebarActive()&&(this._tallTabsEnabled=!0,e.classList.add("tabs-show-folder"),e.classList.remove("tabs-two-rows","tabs-auto-wrap")),document.querySelectorAll("body > .subagent-dropdown").forEach(r=>r.remove()),this.cancelHideSubagentDropdown();const t=e.scrollLeft,s=e.scrollTop,n=this._lastRenderedActiveTabId,i=!e.querySelector(".session-tab"),o=[],a=this.sessionOrder;let l=0;for(const r of a){const d=this.sessions.get(r);if(!d)continue;const c=r===this.activeSessionId&&!this.activeWebviewId,p=d.status||"idle",u=this.getSessionName(d),_=d.mode||"claude",v=d.color||"default",w=d.taskStats||{running:0,total:0},R=w.running>0,C=this.tabAlerts.get(r),k=C==="action"?" tab-alert-action":C==="idle"?" tab-alert-idle":"",E=this.terminalLoadStates.get(r),O=this.minimizedSubagents.get(r),b=(O?.size||0)>0?this.renderSubagentTabBadge(r,O):"",L=this.renderUltracodeTabBadge?this.renderUltracodeTabBadge(r):"",m=d.workingDir&&d.workingDir.split("/").pop()||"",A=(this._tallTabsEnabled??!1)&&d.name&&m&&m!==u,T=parseSessionPrefix(u),S=T&&T.suffix?T.suffix:u,g=T&&T.suffix?d.workingDir?`${T.prefix} (${d.workingDir})`:T.prefix:d.workingDir||"";o.push(`<div class="session-tab ${c?"active":""}${k}${E?" tab-loading":""}${this.hasTabDetachOverride(r)?" tab-show-detach":""}" data-id="${r}" data-color="${v}" ${E?`data-load-phase="${escapeHtml(E.phase)}"`:""} onclick="app.handleSessionTabClick(event, ${escapeHtml(JSON.stringify(r))})" oncontextmenu="event.preventDefault(); app.startInlineRename(${escapeHtml(JSON.stringify(r))})" tabindex="0" role="tab" aria-selected="${c?"true":"false"}" aria-busy="${E?"true":"false"}" aria-label="${escapeHtml(u)} session" ${g?`title="${escapeHtml(g)}"`:""}>
17
+ ${l<9?'<span class="tab-number">'+(l+1)+"</span>":""}
18
+ ${E?'<span class="tab-load-spinner" aria-hidden="true"></span>':""}
19
+ <span class="tab-status ${p}" aria-hidden="true"></span>
20
+ <span class="tab-info">
21
+ <span class="tab-name-row">
22
+ ${_==="shell"?'<span class="tab-mode shell" aria-hidden="true">sh</span>':_==="opencode"?'<span class="tab-mode opencode" aria-hidden="true">oc</span>':_==="codex"?'<span class="tab-mode codex" aria-hidden="true">cx</span>':_==="gemini"?'<span class="tab-mode gemini" aria-hidden="true">gm</span>':_==="antigravity"?'<span class="tab-mode antigravity" aria-hidden="true">ag</span>':_==="pi"?'<span class="tab-mode pi" aria-hidden="true">pi</span>':""}
23
+ <span class="tab-name" data-session-id="${r}">${escapeHtml(S)}</span>
24
+ <span class="tab-detached-badge" aria-hidden="true">detached</span>
25
+ </span>
26
+ ${A?`<span class="tab-folder">\u{1F4C1} ${escapeHtml(m)}</span>`:""}
27
+ </span>
28
+ ${R?`<span class="tab-badge" onclick="event.stopPropagation(); app.toggleTaskPanel()" aria-label="${w.running} running tasks">${w.running}</span>`:""}
29
+ ${b}
30
+ ${L}
31
+ <span class="tab-actions"><span class="tab-gear" onclick="event.stopPropagation(); app.openSessionOptions(${escapeHtml(JSON.stringify(r))})" title="Session options" aria-label="Session options" tabindex="0">&#x2699;</span><span class="tab-detach" onclick="event.stopPropagation(); app.detachSession(${escapeHtml(JSON.stringify(r))})" title="Open in a new window" aria-label="Open session in a new window" tabindex="0">&#x29C9;</span><span class="tab-close" onclick="event.stopPropagation(); app.requestCloseSession(${escapeHtml(JSON.stringify(r))})" title="Close session" aria-label="Close session" tabindex="0">&times;</span></span>
32
+ </div>`),l++}o.push(this.renderWebviewTabs?this.renderWebviewTabs(l):""),e.innerHTML=o.join(""),e.scrollLeft=t,e.scrollTop=s,this._lastRenderedActiveTabId=this.activeSessionId,(i||n!==this.activeSessionId)&&this._scrollActiveTabIntoView(this.activeSessionId,i?"auto":"smooth"),this.setupTabDragHandlers(),this.setupTabKeyboardNavigation(e),this.updateConnectionLines(),this.updateTabOverflowMode(),this._applyTabEntrances?.(),this.applySidebarFilter(this._sidebarFilter),this.updateSidebarCount()}setupTabKeyboardNavigation(e){this._tabKeydownHandler&&e.removeEventListener("keydown",this._tabKeydownHandler),this._tabKeydownHandler=t=>{if(!["ArrowLeft","ArrowRight","ArrowUp","ArrowDown","Home","End","Enter"," "].includes(t.key))return;const s=[...e.querySelectorAll(".session-tab:not(.tab-filtered-out)")],n=s.indexOf(document.activeElement);if((t.key==="Enter"||t.key===" ")&&n>=0){t.preventDefault();const o=s[n].dataset.id;this.selectSession(o,{forceReload:!0});return}if(n<0)return;let i;switch(t.key){case"ArrowLeft":case"ArrowUp":i=n>0?n-1:s.length-1;break;case"ArrowRight":case"ArrowDown":i=n<s.length-1?n+1:0;break;case"Home":i=0;break;case"End":i=s.length-1;break;default:return}t.preventDefault(),s[i]?.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)),n=new Set(s),i=[...e].filter(o=>!n.has(o));this.sessionOrder=[...s,...i]}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{}const e=[...this.sessionOrder];this._debouncedCall("saveSessionOrderServer",()=>{fetch("/api/session-order",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({order:e})}).catch(()=>{})},400)}_onSessionOrderChanged(e){if(!e||!Array.isArray(e.order))return;try{localStorage.setItem("codeman-session-order",JSON.stringify(e.order))}catch{}const t=JSON.stringify(this.sessionOrder);this.syncSessionOrder(),JSON.stringify(this.sessionOrder)!==t&&this._fullRenderSessionTabs()}setupTabDragHandlers(){const e=this.$("sessionTabs");e.querySelectorAll(".session-tab[data-id]").forEach(s=>{s.setAttribute("draggable","true"),s.addEventListener("dragstart",n=>{this.draggedTabId=s.dataset.id,s.classList.add("dragging"),n.dataTransfer.effectAllowed="move",n.dataTransfer.setData("text/plain",s.dataset.id)}),s.addEventListener("dragend",()=>{s.classList.remove("dragging"),this.draggedTabId=null,e.querySelectorAll(".session-tab").forEach(n=>{n.classList.remove("drag-over-left","drag-over-right")})}),s.addEventListener("dragover",n=>{if(n.preventDefault(),!this.draggedTabId||this.draggedTabId===s.dataset.id)return;n.dataTransfer.dropEffect="move";const i=s.getBoundingClientRect(),o=this.isSessionSidebarActive()?n.clientY<i.top+i.height/2:n.clientX<i.left+i.width/2;s.classList.toggle("drag-over-left",o),s.classList.toggle("drag-over-right",!o)}),s.addEventListener("dragleave",()=>{s.classList.remove("drag-over-left","drag-over-right")}),s.addEventListener("drop",n=>{if(n.preventDefault(),s.classList.remove("drag-over-left","drag-over-right"),!this.draggedTabId||this.draggedTabId===s.dataset.id)return;const i=s.dataset.id,o=this.draggedTabId,a=s.getBoundingClientRect(),l=this.isSessionSidebarActive()?n.clientY<a.top+a.height/2:n.clientX<a.left+a.width/2,r=this.sessionOrder.indexOf(o);let d=this.sessionOrder.indexOf(i);r===-1||d===-1||(this.sessionOrder.splice(r,1),d=this.sessionOrder.indexOf(i),d!==-1&&(l?this.sessionOrder.splice(d,0,o):this.sessionOrder.splice(d+1,0,o),this.saveSessionOrder(),this._fullRenderSessionTabs()))})})}moveActiveTabLeft(){if(!this.activeSessionId)return;const e=this.sessionOrder.indexOf(this.activeSessionId);e<=0||([this.sessionOrder[e-1],this.sessionOrder[e]]=[this.sessionOrder[e],this.sessionOrder[e-1]],this.saveSessionOrder(),this._fullRenderSessionTabs())}moveActiveTabRight(){if(!this.activeSessionId)return;const e=this.sessionOrder.indexOf(this.activeSessionId);e===-1||e>=this.sessionOrder.length-1||([this.sessionOrder[e],this.sessionOrder[e+1]]=[this.sessionOrder[e+1],this.sessionOrder[e]],this.saveSessionOrder(),this._fullRenderSessionTabs())}getShortId(e){if(!e)return"";let t=this._shortIdCache.get(e);return t||(t=e.slice(0,8),this._shortIdCache.set(e,t)),t}getSessionName(e){return e.name?e.name:e.workingDir?e.workingDir.split("/").pop()||e.workingDir:this.getShortId(e.id)}_notifySession(e,t,s,n,i){const o=this.sessions.get(e);this.notificationManager?.notify({urgency:t,category:s,sessionId:e,sessionName:o?.name||this.getShortId(e),title:n,message:i})}_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-",i=()=>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=i().sort((l,r)=>Number(o.has(l.slice(s.length)))-Number(o.has(r.slice(s.length))));for(;a.length>=10;)localStorage.removeItem(a.shift())}try{localStorage.setItem(e,t)}catch{for(const a of i()){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 s=this._serializeAddon.serialize({scrollback:1e3});if(this._isUsableXtermSnapshot(s)){if(this._xtermSnapshots.delete(this.activeSessionId),this._xtermSnapshots.set(this.activeSessionId,s),this._xtermSnapshots.size>20){const n=this._xtermSnapshots.keys().next().value;this._xtermSnapshots.delete(n)}s.length<256*1024&&this._persistXtermSnapshot(`codeman-xs-${this.activeSessionId}`,s)}else{this._xtermSnapshots.delete(this.activeSessionId);try{localStorage.removeItem(`codeman-xs-${this.activeSessionId}`)}catch{}}}catch{}if(this._disconnectWs(),typeof CjkInput<"u")CjkInput.clear();else{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 s=this.terminal?._core?._compositionHelper;if(s?._isComposing){s._isComposing=!1;const n=this.terminal?.element?.querySelector(".xterm-helper-textarea");n&&n.dispatchEvent(new CompositionEvent("compositionend",{data:""}))}}catch{}if(this.activeSessionId){const s=this._localEchoOverlay?.pendingText||"",n=this._localEchoOverlay?.getFlushed()?.count||0,i=this._localEchoOverlay?.getFlushed()?.text||"";s&&this._sendInputAsync(this.activeSessionId,s);const o=n+s.length;o>0&&(this._flushedOffsets||(this._flushedOffsets=new Map),this._flushedTexts||(this._flushedTexts=new Map),this._flushedOffsets.set(this.activeSessionId,o),this._flushedTexts.set(this.activeSessionId,i+s))}this._localEchoOverlay?.clear(),this._predictiveEcho?.clearPredictions(),this._localEchoOverlay&&!this._flushedOffsets?.has(e)&&this._localEchoOverlay.suppressBufferDetection()}_resetTerminalForReplay(){this.terminal.reset(),this.terminal.write("\x1B[3J\x1B[H\x1B[2J")}async _maybeRefetchFullHistory({force:e=!1}={}){const t=this.activeSessionId;if(!t||this._fullHistoryRepullInFlight||this._isLoadingBuffer||this.detachedSessions?.has(t))return;const s=Date.now(),n=this._fullHistoryRepullUseless?.has(t)?6e4:4e3;if(!(!e&&s-(this._fullHistoryRepullAt.get(t)||0)<n)){this._fullHistoryRepullAt.set(t,s),this._fullHistoryRepullInFlight=!0;try{const o=(await(await fetch(`/api/sessions/${t}/terminal?full=1`)).json())?.data??{},a=o.terminalBuffer;if(!a||this.activeSessionId!==t)return;if(this._replayWouldShrinkBuffer(a)){(this._fullHistoryRepullUseless||=new Set).add(t),this._logScrollRouting?.("repull-refused-downgrade"),this._setHistoryTruncation(t,{...o,exhausted:!0});return}this._setHistoryTruncation(t,o),this._fullHistoryRepullUseless?.delete(t);const l=this.terminal.buffer.active.length;if(this._resetTerminalForReplay(),await this.chunkedTerminalWrite(a,TERMINAL_CHUNK_SIZE,t),this.activeSessionId!==t)return;this.terminalBufferCache.set(t,a);const r=this.terminal.buffer.active.length-l;r>0?this.terminal.scrollToLine(r):this.terminal.scrollToTop()}catch{}finally{this._fullHistoryRepullInFlight=!1}}}_setHistoryTruncation(e,t={}){e&&((this._historyTruncation||=new Map).set(e,{truncated:!!t.truncated,reason:t.truncationReason??null,source:t.source??null,fullSize:t.fullSize??0,retainedBytes:t.retainedBytes??0,exhausted:!!t.exhausted}),e===this.activeSessionId&&this._renderHistoryTruncationBanner())}_clearHistoryTruncation(e){this._historyTruncation?.delete(e),e===this.activeSessionId&&this._renderHistoryTruncationBanner()}_renderHistoryTruncationBanner(){const e=document.getElementById("historyTruncationBar");if(!e)return;const t=this.activeSessionId?this._historyTruncation?.get(this.activeSessionId):null,s=computeHistoryTruncationNotice(t||{});if(!s.visible){e.hidden=!0;return}e.textContent="";const n=document.createElement("span");if(n.className="history-trunc-text",n.textContent=s.message,e.appendChild(n),s.canLoadMore){const o=document.createElement("button");o.type="button",o.className="history-trunc-load",o.textContent="Load full history",o.onclick=()=>{o.disabled=!0,o.textContent="Loading\u2026",this._maybeRefetchFullHistory({force:!0}).finally(()=>{this._renderHistoryTruncationBanner()})},e.appendChild(o)}const i=document.createElement("button");i.type="button",i.className="history-trunc-dismiss",i.setAttribute("aria-label","Dismiss history notice"),i.textContent="\xD7",i.onclick=()=>{e.hidden=!0},e.appendChild(i),e.hidden=!1}_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 n=this._shouldFocusTerminalForTabSwitch();n&&this.terminal&&this.terminal.focus();const i=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._hideWebviewLayer?.(),this._cleanupPreviousSession(e),this.activeSessionId=e,this._renderHistoryTruncationBanner();try{localStorage.setItem("codeman-active-session",e)}catch{}this._updateSseSubscription(e),this.hideWelcome(),this.playTerminalEntrance?.(e),this.clearPendingHooks(e,"idle_prompt"),this._updateActiveTabImmediate(e),this.closeSessionSidebarOnHandheld(),this.renderSessionTabs(),this.updateAttachmentHistoryBadge?.(),this.attachmentHistoryDrawerOpen&&this.loadAttachmentHistory?.(e),this._updateLocalEchoState(),typeof KeyboardAccessoryBar<"u"&&KeyboardAccessoryBar.refreshForActiveSession(),this._flushedOffsets?.has(e)&&this._localEchoOverlay&&this._localEchoOverlay.setFlushed(this._flushedOffsets.get(e),this._flushedTexts?.get(e)||"",!1);const l=document.querySelector(`.session-tab.active[data-id="${e}"]`);l&&(l.classList.add("tab-glow"),l.addEventListener("animationend",()=>l.classList.remove("tab-glow"),{once:!0}));const r=this.sessions.get(e);if(this.currentSessionWorkingDir=r?.workingDir||null,r&&r.pid===null)if(r.respawnBlocked){const p=r.name||"Session";if(window.confirm(`${p} was stopped after crashing repeatedly. Restart it?`))try{await fetch(`/api/sessions/${e}/interactive`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({clearBreaker:!0})}),r.respawnBlocked=!1,r.status="busy"}catch(u){console.error("Failed to restart crash-looped session:",u)}}else try{const p=r.mode==="shell"?`/api/sessions/${e}/shell`:`/api/sessions/${e}/interactive`;await fetch(p,{method:"POST"}),r.status="busy"}catch(p){console.error("Failed to attach to restored session:",p)}this._restoringFlushedState=!0;const d=this._beginBufferLoad(a);let c=!1;try{this.fitAddon&&this.fitAddon.fit();const p=await this.sendResize(e,{forceHttp:!0}).catch(()=>!1);if(this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}let u=this._xtermSnapshots?.get(e);if(u&&!this._isUsableXtermSnapshot(u)&&(this._xtermSnapshots?.delete(e),u=null),!u)try{const f=localStorage.getItem(`codeman-xs-${e}`);f&&this._isUsableXtermSnapshot(f)?(u=f,this._xtermSnapshots?.delete(e),this._xtermSnapshots?.set(e,f)):f&&localStorage.removeItem(`codeman-xs-${e}`)}catch{}const _=r&&(r.status==="busy"||r.status==="working");let v=!1;if(u&&!_&&r?.mode!=="shell"){if(_crashDiag.log(`SNAPSHOT_RESTORE: ${(u.length/1024).toFixed(0)}KB`),this._setTerminalLoadState(e,a,"replaying"),this._resetTerminalForReplay(),await new Promise(f=>this.terminal.write(u,f)),this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}this.scrollToLastNonEmptyLine(),_crashDiag.log("SNAPSHOT_RESTORE_DONE"),v=!0}const w=this.terminalBufferCache.get(e);let R=!1;if(w&&!_&&!v){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 _&&(this._resetTerminalForReplay(),R=!0,_crashDiag.log("CACHE_SKIP_BUSY"));if(r?.mode!=="shell"&&p&&(await new Promise(f=>setTimeout(f,TUI_REDRAW_SETTLE_MS)),this._isStaleSelect(a))){this._clearTerminalLoadState(e,a);return}this._setTerminalLoadState(e,a,"fetching"),_crashDiag.log("FETCH_START");const C=!this._fullHistoryLoaded.has(e);C&&this._fullHistoryLoaded.add(e);const k=await fetch(C?`/api/sessions/${e}/terminal?full=1`:`/api/sessions/${e}/terminal?tail=${TERMINAL_TAIL_SIZE}`);if(this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}const E=(await k.json())?.data??{};if(_crashDiag.log(`FETCH_DONE: ${E.terminalBuffer?(E.terminalBuffer.length/1024).toFixed(0)+"KB":"empty"} truncated=${E.truncated}`),E.terminalBuffer){if(v||R||E.terminalBuffer!==w){if(_crashDiag.log(`REWRITE: ${(E.terminalBuffer.length/1024).toFixed(0)}KB`),this._setTerminalLoadState(e,a,"replaying"),this._resetTerminalForReplay(),this._setHistoryTruncation(e,E),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 b=this.terminalBufferCache.keys().next().value;this.terminalBufferCache.delete(b)}}else w||(this._resetTerminalForReplay(),c=!0);if(this._isLoadingBuffer&&this._finishBufferLoad(d,{flushQueued:c}),this._restoringFlushedState=!1,this._flushedOffsets?.has(e)&&this._localEchoOverlay){this._localEchoOverlay.setFlushed(this._flushedOffsets.get(e),this._flushedTexts?.get(e)||"",!1);const f=this._localEchoOverlay;this.terminal.write("",()=>{f.hasPending&&f.rerender()})}this.sendResize(e),(typeof requestIdleCallback=="function"?requestIdleCallback:f=>setTimeout(f,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 f=document.getElementById("taskPanel");f&&f.classList.contains("open")&&this.renderTaskPanel();const b=this.sessions.get(e);if(b&&(b.ralphLoop||b.ralphTodos)&&this.updateRalphState(e,{loop:b.ralphLoop,todos:b.ralphTodos}),this.renderRalphStatePanel(),this.updateCliInfoDisplay(),this.renderProjectInsightsPanel(),this.updateSubagentWindowVisibility(),this.loadAppSettingsFromStorage().showFileBrowser){const m=this.$("fileBrowserPanel");if(m&&(m.classList.add("visible"),this.loadFileBrowser(e),!this.fileBrowserDragListeners)){const y=m.querySelector(".file-browser-header");if(y){const A=()=>{if(!m.style.left){const T=m.getBoundingClientRect();m.style.left=`${T.left}px`,m.style.top=`${T.top}px`,m.style.right="auto"}};y.addEventListener("mousedown",A),y.addEventListener("touchstart",A,{passive:!0}),this.fileBrowserDragListeners=this.makeWindowDraggable(m,y),this.fileBrowserDragListeners._onFirstDrag=A}}}}),this._connectWs(e),_crashDiag.log("FOCUS"),n&&this.terminal&&this.terminal.focus(),this.scrollToLastNonEmptyLine(),typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible&&KeyboardHandler.onKeyboardShow(),this._clearTerminalLoadState(e,a),_crashDiag.log(`SELECT_DONE: ${(performance.now()-i).toFixed(0)}ms`),console.log(`[CRASH-DIAG] selectSession DONE: ${e.slice(0,8)} in ${(performance.now()-i).toFixed(0)}ms`)}catch(p){this._isLoadingBuffer&&this._finishBufferLoad(d),this._restoringFlushedState=!1,this._setTerminalLoadState(e,a,"failed"),console.error("Failed to load session terminal:",p)}}_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._clearHistoryTruncation(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),n=document.getElementById("closeConfirmSessionName");n.textContent=s;const i=document.getElementById("closeConfirmKillTitle");i&&(i.textContent=t.mode==="opencode"?"Kill Tmux & OpenCode":t.mode==="codex"?"Kill Tmux & Codex":t.mode==="gemini"?"Kill Tmux & Gemini":t.mode==="antigravity"?"Kill Tmux & Antigravity":t.mode==="pi"?"Kill Tmux & Pi":"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,n=e-this.currentRun.startedAt,i=Math.min(100,n/s*100);document.getElementById("timerValue").textContent=this.formatTime(t),document.getElementById("timerProgress").style.width=`${i}%`,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),n=Math.floor(t%3600/60),i=t%60;return`${s.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}:${i.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 n=this.formatTokens(s),i=this.estimateCost(e,t),o=this.$("headerTokens");if(o){const l=this.loadAppSettingsFromStorage().showCost??!1;o.textContent=s>0?l?`${n} tokens \xB7 $${i.toFixed(2)}`:`${n} tokens`:"0 tokens",o.title=this.globalStats?`Lifetime: ${this.globalStats.totalSessionsCreated} sessions created${l?`
33
+ Estimated cost based on Claude Opus pricing`:""}`:`Token usage across active sessions${l?`
34
+ Estimated cost based on Claude Opus pricing`:""}`}}getShortcutRegistry(){const t=this.loadAppSettingsFromStorage().shortcutOverrides||{};return DEFAULT_SHORTCUTS.map(s=>{const n=t[s.id];if(!n)return s;const i={...s};return Array.isArray(n.bindings)&&(i.bindings=n.bindings,delete i.displayBindings),typeof n.disabled=="boolean"&&(i.disabled=n.disabled),i})}matchesShortcutEvent(e,t){return!t||!Array.isArray(t.bindings)?!1:t.bindings.some(s=>{const n=s.modifiers||[];return(n.includes("ctrl")||n.includes("meta"))!==!!(e.ctrlKey||e.metaKey)||n.includes("shift")!==!!e.shiftKey||n.includes("alt")!==!!e.altKey?!1:!!(s.code&&e.code===s.code||s.key&&typeof e.key=="string"&&e.key.toLowerCase()===s.key.toLowerCase())})}showShortcutOverlay(){const e=document.getElementById("shortcutOverlayModal");e&&(this.renderShortcutOverlay(),e.classList.add("active"),e.focus?.())}renderShortcutOverlay(){const e=document.getElementById("shortcutOverlayList");if(!e)return;const t=this.getShortcutRegistry(),s={};for(const i of t){const o=i.group||"General";s[o]||(s[o]=[]),s[o].push(i)}const n=i=>i.displayBindings?i.displayBindings.map(o=>`<kbd>${escapeHtml(o)}</kbd>`).join(" / "):i.bindings?i.bindings.map(o=>{const a=[...(o.modifiers||[]).map(l=>l.charAt(0).toUpperCase()+l.slice(1)),o.key||o.code||""];return`<kbd>${escapeHtml(a.join("+"))}</kbd>`}).join(" / "):"";e.innerHTML=Object.entries(s).map(([i,o])=>`<div class="shortcut-overlay-group"><div class="shortcut-overlay-group-label">${escapeHtml(i)}</div>`+o.map(a=>`<div class="shortcut-overlay-row"><span class="shortcut-overlay-label">${escapeHtml(a.label)}</span><span class="shortcut-overlay-keys">${n(a)}</span></div>`).join("")+"</div>").join("")}closeShortcutOverlay(){const e=document.getElementById("shortcutOverlayModal");e&&e.classList.remove("active")}}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;
@@ -37,13 +37,21 @@ Object.assign(CodemanApp.prototype, {
37
37
  async seedApprovals() {
38
38
  if (!this.approvals) this.approvals = new Map();
39
39
  this.approvals.clear();
40
- if (this.approvalsInboxEnabled()) {
41
- const data = await this._apiJson('/api/approvals');
42
- for (const item of (data && data.approvals) || []) {
43
- this.approvals.set(item.id, item);
44
- // Re-arm the tab alert state machine (idempotent set-add).
45
- this.setPendingHook(item.sessionId, approvalKindToHook(item.kind));
46
- }
40
+ // ⚠ Fetch and re-arm the tab-alert state machine REGARDLESS of the inbox
41
+ // setting. The server-side approval store runs unconditionally (only the
42
+ // inbox SURFACES are opt-in), and the red/yellow tab alert predates the
43
+ // inbox: gating the seed on the setting meant that with the inbox off, a
44
+ // reload landed with every alert store empty while a permission dialog sat
45
+ // blocking a session (owner report 2026-08-15: rail said NEEDS YOU from
46
+ // the live SSE event, the reloaded-elsewhere tab showed a plain green
47
+ // dot). Only populating `this.approvals` (bell/drawer/answer strips) stays
48
+ // behind the setting.
49
+ const data = await this._apiJson('/api/approvals');
50
+ const inboxOn = this.approvalsInboxEnabled();
51
+ for (const item of (data && data.approvals) || []) {
52
+ if (inboxOn) this.approvals.set(item.id, item);
53
+ // Re-arm the tab alert state machine (idempotent set-add).
54
+ this.setPendingHook(item.sessionId, approvalKindToHook(item.kind));
47
55
  }
48
56
  this.renderApprovals();
49
57
  },
@@ -68,14 +76,15 @@ Object.assign(CodemanApp.prototype, {
68
76
  },
69
77
 
70
78
  _onApprovalResolved(info) {
71
- if (!info || !info.id || !this.approvals) return;
72
- if (this.approvals.delete(info.id)) {
73
- // Clear the matching tab alert: the inbox resolves on more signals than
74
- // the hook handlers do (superseded, expired, answered from another
75
- // device), and clearPendingHooks is a no-op when nothing is set.
76
- this.clearPendingHooks(info.sessionId, approvalKindToHook(info.kind));
77
- this.renderApprovals();
78
- }
79
+ if (!info || !info.id) return;
80
+ // Clear the matching tab alert UNCONDITIONALLY: the inbox resolves on more
81
+ // signals than the hook handlers do (superseded, expired, answered from
82
+ // another device), clearPendingHooks is a no-op when nothing is set, and
83
+ // with the inbox setting OFF the item was never stored in `this.approvals`
84
+ // even though seedApprovals armed the alert — gating the clear on a map hit
85
+ // would strand that alert forever.
86
+ this.clearPendingHooks(info.sessionId, approvalKindToHook(info.kind));
87
+ if (this.approvals?.delete(info.id)) this.renderApprovals();
79
88
  },
80
89
 
81
90
  // ─── Actions ─────────────────────────────────────────────────
Binary file
Binary file
@@ -222,23 +222,35 @@ function computeTabScrollLeft(input) {
222
222
  // endpoint scrolled outside the strip. `.session-tabs` is `overflow-x: auto`, so a
223
223
  // scrolled-out tab still HAS a rect — one lying over the logo or the header
224
224
  // buttons. Skipping is honest; clamping would point at a tab that isn't there.
225
- // ⚠ THE DIP IS WHAT MAKES THE ARC AN ARC, and the first shipped numbers were tuned
226
- // against two tabs sitting side by side. A worker the agent skill starts is appended
227
- // to the END of the strip, so the real span between a lead and its worker is 800-1500px,
228
- // not 200, and a 44px cap over 1300px of span is a 33px sag, i.e. a line that reads as
229
- // STRAIGHT and crosses the terminal instead of bracketing under the strip. The dip now
230
- // keeps growing with the span (0.085/px, ~3x steeper against the old cap) so the bracket
231
- // survives the distance the feature is actually used at. The ceiling is what keeps a
232
- // full-width pair out of the terminal's fourth line: 104 + the sibling step lands the
233
- // deepest sag around y=140 on a 1080 screen, the same proportion two adjacent tabs get.
225
+ // ⚠ THE DIP IS WHAT MAKES THE ARC AN ARC, and it has now been mis-tuned in BOTH
226
+ // directions, so treat these numbers as a corridor rather than a dial to crank:
227
+ // - Too shallow (the first ship, 44px cap): a skill worker is appended to the END of
228
+ // the strip, so a lead-to-worker span is 800-1500px, and a 44px cap over 1300px is
229
+ // a 33px sag, a line that reads as STRAIGHT across the terminal (#285).
230
+ // - Too deep (the 104px cap that replaced it): in the wrapped-strip case the cap and
231
+ // the FULL row offset stacked, bowing the bracket ~106px into the terminal text
232
+ // (owner screenshot 2026-08-15, "die Linien machen einen grossen Bogen nach unten").
233
+ // The dip is measured from the STRIP'S BOTTOM EDGE (falling back to the lower tab
234
+ // bottom when the strip rect is missing or shorter than its tabs), which buys two
235
+ // things at once: the bow needs no per-row offsets stacked on top, and a same-row
236
+ // arc between ROW-1 tabs of a wrapped strip clears row 2's labels instead of being
237
+ // drawn through them (the retune's own first draft had exactly that regression).
234
238
  const LINEAGE_DIP_BASE_PX = 14;
235
- const LINEAGE_DIP_PER_PX = 0.085;
239
+ const LINEAGE_DIP_PER_PX = 0.06;
236
240
  const LINEAGE_DIP_MIN_PX = 22;
237
- const LINEAGE_DIP_MAX_PX = 104;
241
+ const LINEAGE_DIP_MAX_PX = 64;
238
242
  // Siblings nest by this much. Widened with the stroke: at 2.5px plus its glow, arcs 6px
239
243
  // apart bled into one thick band instead of reading as three separate lines.
240
244
  const LINEAGE_SIBLING_STEP_PX = 8;
241
245
  const LINEAGE_STRIP_TOLERANCE_PX = 4;
246
+ // Lineage palette, assigned per CHILD in first-seen order and cycled (session-lineage.js).
247
+ // The empty FIRST entry means "no override": the CSS then falls back to --session-blue,
248
+ // which every skin block tunes for its own background, so a lone arc keeps the
249
+ // skin-aware blue that shipped in 1.18.2. The fixed entries are deliberately vivid
250
+ // (owner call 2026-08-15: matrix green, pinkish, violet, red, turquoise "and so on");
251
+ // they ride the same double glow as the blue, which is what keeps them legible over
252
+ // terminal text on every skin.
253
+ const LINEAGE_COLORS = ['', '#00ff66', '#ff5ea8', '#a78bfa', '#ff5252', '#2dd4bf', '#ffa940'];
242
254
 
243
255
  function computeLineagePath(input) {
244
256
  const parent = input?.parent;
@@ -269,18 +281,19 @@ function computeLineagePath(input) {
269
281
  const cBottom = cTop + ch;
270
282
  const sameRow = Math.abs(pTop + ph / 2 - (cTop + ch / 2)) <= Math.min(ph, ch) / 2;
271
283
 
272
- // Both ends anchor on the tab BOTTOM, and the control points hang below whichever
273
- // row is lower, so one formula covers a flat strip and a wrapped one.
284
+ // Both ends anchor on the tab BOTTOM, and the control points hang below the WHOLE
285
+ // strip, so one formula covers a flat strip, a wrapped pair, and a same-row pair
286
+ // sitting above further rows (see the corridor note above the constants).
274
287
  const span = Math.abs(cx - px);
275
- const rowDrop = Math.abs(cBottom - pBottom);
276
- // ⚠ A wrapped pair needs the dip measured from the LOWER row, or the bracket would
277
- // only reach the row gap again. Adding the row offset also keeps the curve clear of
278
- // the row it crosses instead of grazing its bottom edge.
288
+ const stripBottom =
289
+ strip && Number(strip.height) > 0 && Number.isFinite(Number(strip.top))
290
+ ? Number(strip.top) + Number(strip.height)
291
+ : Number.NEGATIVE_INFINITY;
292
+ const baseline = Math.max(pBottom, cBottom, stripBottom);
279
293
  const dip =
280
294
  Math.min(LINEAGE_DIP_MAX_PX, Math.max(LINEAGE_DIP_MIN_PX, LINEAGE_DIP_BASE_PX + span * LINEAGE_DIP_PER_PX)) +
281
- depth * LINEAGE_SIBLING_STEP_PX +
282
- rowDrop;
283
- const yc = Math.max(pBottom, cBottom) + dip;
295
+ depth * LINEAGE_SIBLING_STEP_PX;
296
+ const yc = baseline + dip;
284
297
  const d = `M ${r1(px)} ${r1(pBottom)} C ${r1(px)} ${r1(yc)}, ${r1(cx)} ${r1(yc)}, ${r1(cx)} ${r1(cBottom)}`;
285
298
  return { d, endX: cx, endY: cBottom, sameRow };
286
299
  }
@@ -424,6 +437,94 @@ function computeSseStale(input) {
424
437
  return now - lastMessageAt >= timeoutMs;
425
438
  }
426
439
 
440
+ // Home-screen session order: one comparator for both overviews.
441
+ //
442
+ // The phone overview (mobile-overview.js) and the desktop tab rail
443
+ // (home-sessions.js) list the same sessions, so they answer the same question
444
+ // and must answer it the same way: "which of these wants me next?".
445
+ //
446
+ // 1. Anything blocked on a human first (red question, then error, then a
447
+ // yellow idle prompt), longest-blocked at the top: a session that has been
448
+ // sitting on a permission dialog for 20 minutes is starving, one that
449
+ // raised it 5 seconds ago is not.
450
+ // 2. Then whatever is running, LONGEST-RUNNING first, since that is the turn most
451
+ // likely to be finished, or stuck, by the time you look.
452
+ // 3. Then everything quiet, MOST RECENTLY quiet first: when nothing is
453
+ // running, the session that just finished is the one you came back for,
454
+ // and the one you abandoned yesterday sinks.
455
+ //
456
+ // So the tiebreak flips direction halfway down the list, and that is the point:
457
+ // for a state something is still doing, longer = more urgent; for a state
458
+ // something has stopped in, more recent = more relevant.
459
+ //
460
+ // Pure: no DOM, no clock (every input is an epoch-ms stamp already on the
461
+ // session payload), no `this`. Unit-tested in test/session-overview-order.test.ts.
462
+ const SESSION_ACTIVITY_RANK = {
463
+ needs: 0,
464
+ error: 1,
465
+ waiting: 2,
466
+ working: 3,
467
+ idle: 4,
468
+ done: 5,
469
+ };
470
+
471
+ /** States still in progress, where the OLDEST stamp sorts first. */
472
+ const SESSION_ACTIVITY_OLDEST_FIRST = ['needs', 'error', 'waiting', 'working'];
473
+
474
+ /**
475
+ * When the row entered the state it is in.
476
+ *
477
+ * For everything quiet that is `lastActivityAt`, the last byte the pane printed:
478
+ * a Claude pane sitting at its composer prints nothing, so the end of the last
479
+ * turn is exactly when it went quiet.
480
+ *
481
+ * A WORKING pane is the opposite: it repaints about once a second, so its
482
+ * last-activity stamp is always "now" and would rank every running turn as
483
+ * freshly started. Its real start is the pane's last Enter (`lastSubmitAt`),
484
+ * persisted server-side and therefore stable across a Codeman restart. A
485
+ * working pane that has never submitted (spawned with its prompt on the command
486
+ * line, or an external CLI) falls back to last activity, which puts it at the
487
+ * short end of the running group rather than falsely at the head of it.
488
+ */
489
+ function sessionActivityAnchor(row) {
490
+ const activeAt = Number(row && row.lastActivityAt) || 0;
491
+ if (row && row.state === 'working') return Number(row.lastSubmitAt) || activeAt;
492
+ return activeAt;
493
+ }
494
+
495
+ /**
496
+ * Sort comparator for one overview row against another.
497
+ * @param {{state: string, lastActivityAt?: number, lastSubmitAt?: number, orderIndex?: number}} a
498
+ * @param {{state: string, lastActivityAt?: number, lastSubmitAt?: number, orderIndex?: number}} b
499
+ */
500
+ function compareSessionActivity(a, b) {
501
+ const rankA = SESSION_ACTIVITY_RANK[a.state];
502
+ const rankB = SESSION_ACTIVITY_RANK[b.state];
503
+ const rank = (rankA === undefined ? 99 : rankA) - (rankB === undefined ? 99 : rankB);
504
+ if (rank !== 0) return rank;
505
+
506
+ const atA = sessionActivityAnchor(a);
507
+ const atB = sessionActivityAnchor(b);
508
+ if (atA !== atB) {
509
+ // A row with no stamp at all gets no opinion: it sorts last either way
510
+ // rather than claiming to be the oldest (0) thing on the screen.
511
+ if (!atA) return 1;
512
+ if (!atB) return -1;
513
+ return SESSION_ACTIVITY_OLDEST_FIRST.includes(a.state) ? atA - atB : atB - atA;
514
+ }
515
+
516
+ // Equal stamps (or two unstamped rows): fall back to the user's tab order so
517
+ // the list is deterministic and cannot shuffle between renders.
518
+ const orderA = Number.isFinite(a.orderIndex) ? a.orderIndex : Number.MAX_SAFE_INTEGER;
519
+ const orderB = Number.isFinite(b.orderIndex) ? b.orderIndex : Number.MAX_SAFE_INTEGER;
520
+ return orderA - orderB;
521
+ }
522
+
523
+ /** Copy of `rows`, in overview order. Never sorts in place, so callers keep their array. */
524
+ function sortSessionsByActivity(rows) {
525
+ return (Array.isArray(rows) ? rows.slice() : []).sort(compareSessionActivity);
526
+ }
527
+
427
528
  if (typeof window !== 'undefined') {
428
529
  window.WEBGL_FALLBACK = WEBGL_FALLBACK;
429
530
  window.evaluateWebGLLongTaskTrip = evaluateWebGLLongTaskTrip;
@@ -441,6 +542,7 @@ if (typeof window !== 'undefined') {
441
542
  DIP_MIN_PX: LINEAGE_DIP_MIN_PX,
442
543
  DIP_MAX_PX: LINEAGE_DIP_MAX_PX,
443
544
  SIBLING_STEP_PX: LINEAGE_SIBLING_STEP_PX,
545
+ COLORS: LINEAGE_COLORS,
444
546
  };
445
547
  window.CodemanConnectionLoss = {
446
548
  compute: computeConnectionLossUi,
@@ -450,6 +552,12 @@ if (typeof window !== 'undefined') {
450
552
  compute: computeSseStale,
451
553
  TIMEOUT_MS: SSE_STALE_TIMEOUT_MS,
452
554
  };
555
+ window.CodemanSessionOrder = {
556
+ RANK: SESSION_ACTIVITY_RANK,
557
+ anchor: sessionActivityAnchor,
558
+ compare: compareSessionActivity,
559
+ sort: sortSessionsByActivity,
560
+ };
453
561
  }
454
562
 
455
563
  // Scheduler API — prioritize terminal writes over background UI updates.
@@ -879,6 +987,45 @@ function computeRewriteScrollLine(input) {
879
987
  return Math.max(0, (input?.baseY || 0) - linesFromBottom);
880
988
  }
881
989
 
990
+ /**
991
+ * Absolute file paths in agent output, as ONE pattern with two consumers: the
992
+ * xterm link provider (terminal-ui.js) and the response viewer's markdown
993
+ * linkifier (app.js). They used to be able to drift, and a path that is
994
+ * clickable in the terminal but inert in the chat reads as a bug, not a policy.
995
+ *
996
+ * Anchored on a known absolute root (so an ordinary fraction or a date can
997
+ * never match) and terminated by a known extension (so the end of the path is
998
+ * unambiguous — a trailing `)` or `.` after the extension stays out). Longer
999
+ * extensions come first in each family (`tsx|ts`), so the trailing `\b` cannot
1000
+ * be satisfied by the shorter branch mid-word.
1001
+ *
1002
+ * ⚠ Consumers must never share one instance: `lastIndex` is per-object state on
1003
+ * a `/g` regex, so {@link absoluteFilePathPattern} mints a fresh one per call.
1004
+ */
1005
+ const FILE_PATH_LINK_PATTERN =
1006
+ /(\/(?:home|Users|tmp|var|private|etc|opt|mnt|srv|media|data|workspace)\/[^\s"'<>|;&\n\x00-\x1f]*\.(?:log|txt|json|md|ya?ml|csv|xml|sh|py|tsx|ts|jsx|js|mjs|cjs|css|html|toml|ini|sql|png|jpe?g|gif|webp|bmp|svg|pdf|docx|pptx|mp4|webm|mov|mp3|wav))\b/g;
1007
+
1008
+ /** A fresh, zero-state instance of {@link FILE_PATH_LINK_PATTERN}. */
1009
+ function absoluteFilePathPattern() {
1010
+ return new RegExp(FILE_PATH_LINK_PATTERN.source, 'g');
1011
+ }
1012
+
1013
+ /**
1014
+ * Extensions the file-preview overlay renders itself. Everything else a link
1015
+ * points at goes to the tail/log viewer, which is the right home for a growing
1016
+ * text file and the wrong one for bytes (tailing a PNG shows binary noise).
1017
+ */
1018
+ const FILE_PREVIEW_EXTENSIONS = new Set(
1019
+ ('png jpg jpeg gif webp bmp svg pdf docx pptx mp4 webm mov mp3 wav').split(' ')
1020
+ );
1021
+
1022
+ /** Whether a path's extension is one {@link FILE_PREVIEW_EXTENSIONS} covers. */
1023
+ function previewsInFileViewer(filePath) {
1024
+ const ext = String(filePath || '').split('.').pop().toLowerCase();
1025
+ return FILE_PREVIEW_EXTENSIONS.has(ext);
1026
+ }
1027
+
882
1028
  if (typeof window !== 'undefined') {
883
1029
  window.CodemanHistoryFormat = { formatHistoryBytes, computeHistoryTruncationNotice, computeRewriteScrollLine };
1030
+ window.CodemanFilePaths = { absoluteFilePathPattern, previewsInFileViewer, FILE_PREVIEW_EXTENSIONS };
884
1031
  }