aicodeman 1.16.2 → 1.16.3

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 (116) hide show
  1. package/README.md +2 -1
  2. package/dist/search-service.d.ts +9 -1
  3. package/dist/search-service.d.ts.map +1 -1
  4. package/dist/search-service.js +13 -3
  5. package/dist/search-service.js.map +1 -1
  6. package/dist/services/unified-session-service.d.ts +9 -0
  7. package/dist/services/unified-session-service.d.ts.map +1 -1
  8. package/dist/services/unified-session-service.js +4 -1
  9. package/dist/services/unified-session-service.js.map +1 -1
  10. package/dist/types/search.d.ts +17 -3
  11. package/dist/types/search.d.ts.map +1 -1
  12. package/dist/web/public/admin-ui.js +35 -20
  13. package/dist/web/public/admin-ui.js.br +0 -0
  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.4d006245.js → app.f9c6aadd.js} +18 -18
  17. package/dist/web/public/app.f9c6aadd.js.br +0 -0
  18. package/dist/web/public/app.f9c6aadd.js.gz +0 -0
  19. package/dist/web/public/approvals-ui.js.gz +0 -0
  20. package/dist/web/public/{constants.7d0f4047.js → constants.53dc0fa1.js} +43 -0
  21. package/dist/web/public/constants.53dc0fa1.js.br +0 -0
  22. package/dist/web/public/constants.53dc0fa1.js.gz +0 -0
  23. package/dist/web/public/cron-ui.js.gz +0 -0
  24. package/dist/web/public/entrance-animations.js.gz +0 -0
  25. package/dist/web/public/home-sessions.js +119 -8
  26. package/dist/web/public/home-sessions.js.br +0 -0
  27. package/dist/web/public/home-sessions.js.gz +0 -0
  28. package/dist/web/public/i18n.1a8b3412.js +1 -0
  29. package/dist/web/public/i18n.1a8b3412.js.br +0 -0
  30. package/dist/web/public/i18n.1a8b3412.js.gz +0 -0
  31. package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
  32. package/dist/web/public/index.html +807 -727
  33. package/dist/web/public/index.html.br +0 -0
  34. package/dist/web/public/index.html.gz +0 -0
  35. package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
  36. package/dist/web/public/{keyboard-accessory.1f35b482.js → keyboard-accessory.0f524637.js} +207 -5
  37. package/dist/web/public/keyboard-accessory.0f524637.js.br +0 -0
  38. package/dist/web/public/keyboard-accessory.0f524637.js.gz +0 -0
  39. package/dist/web/public/mobile-handlers.32fdd57f.js.gz +0 -0
  40. package/dist/web/public/mobile-overview.js.gz +0 -0
  41. package/dist/web/public/mobile.52b30b35.css +1 -0
  42. package/dist/web/public/mobile.52b30b35.css.br +0 -0
  43. package/dist/web/public/mobile.52b30b35.css.gz +0 -0
  44. package/dist/web/public/notification-manager.5d229063.js.gz +0 -0
  45. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  46. package/dist/web/public/{panels-ui.3df9cd95.js → panels-ui.54b0d004.js} +1 -1
  47. package/dist/web/public/panels-ui.54b0d004.js.br +0 -0
  48. package/dist/web/public/panels-ui.54b0d004.js.gz +0 -0
  49. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  50. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  51. package/dist/web/public/readmymind-ui.js +80 -16
  52. package/dist/web/public/readmymind-ui.js.br +0 -0
  53. package/dist/web/public/readmymind-ui.js.gz +0 -0
  54. package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
  55. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  56. package/dist/web/public/session-ui.f4fed187.js.gz +0 -0
  57. package/dist/web/public/{settings-ui.1c15649c.js → settings-ui.c674f367.js} +9 -9
  58. package/dist/web/public/settings-ui.c674f367.js.br +0 -0
  59. package/dist/web/public/settings-ui.c674f367.js.gz +0 -0
  60. package/dist/web/public/styles.2eb23e0f.css +1 -0
  61. package/dist/web/public/styles.2eb23e0f.css.br +0 -0
  62. package/dist/web/public/styles.2eb23e0f.css.gz +0 -0
  63. package/dist/web/public/subagent-windows.4cc8b005.js.gz +0 -0
  64. package/dist/web/public/sw.js.gz +0 -0
  65. package/dist/web/public/terminal-ui.61c7829d.js +4 -0
  66. package/dist/web/public/terminal-ui.61c7829d.js.br +0 -0
  67. package/dist/web/public/terminal-ui.61c7829d.js.gz +0 -0
  68. package/dist/web/public/ultracode-panel.js.gz +0 -0
  69. package/dist/web/public/ultracode-windows.js.gz +0 -0
  70. package/dist/web/public/upload.html.gz +0 -0
  71. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  72. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  73. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  74. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  75. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  76. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  77. package/dist/web/public/vendor/xterm-predictive-echo.6cec0fba.js.gz +0 -0
  78. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  79. package/dist/web/public/vendor/xterm.css.gz +0 -0
  80. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  81. package/dist/web/public/voice-input.e5461859.js.gz +0 -0
  82. package/dist/web/public/webview-tabs.js.gz +0 -0
  83. package/dist/web/routes/search-routes.d.ts +3 -1
  84. package/dist/web/routes/search-routes.d.ts.map +1 -1
  85. package/dist/web/routes/search-routes.js +34 -1
  86. package/dist/web/routes/search-routes.js.map +1 -1
  87. package/dist/web/routes/session-routes.d.ts.map +1 -1
  88. package/dist/web/routes/session-routes.js +151 -9
  89. package/dist/web/routes/session-routes.js.map +1 -1
  90. package/dist/web/session-history-index.d.ts +108 -0
  91. package/dist/web/session-history-index.d.ts.map +1 -0
  92. package/dist/web/session-history-index.js +118 -0
  93. package/dist/web/session-history-index.js.map +1 -0
  94. package/package.json +1 -1
  95. package/dist/web/public/app.4d006245.js.br +0 -0
  96. package/dist/web/public/app.4d006245.js.gz +0 -0
  97. package/dist/web/public/constants.7d0f4047.js.br +0 -0
  98. package/dist/web/public/constants.7d0f4047.js.gz +0 -0
  99. package/dist/web/public/i18n.ef50083f.js +0 -1
  100. package/dist/web/public/i18n.ef50083f.js.br +0 -0
  101. package/dist/web/public/i18n.ef50083f.js.gz +0 -0
  102. package/dist/web/public/keyboard-accessory.1f35b482.js.br +0 -0
  103. package/dist/web/public/keyboard-accessory.1f35b482.js.gz +0 -0
  104. package/dist/web/public/mobile.5d8c896c.css +0 -1
  105. package/dist/web/public/mobile.5d8c896c.css.br +0 -0
  106. package/dist/web/public/mobile.5d8c896c.css.gz +0 -0
  107. package/dist/web/public/panels-ui.3df9cd95.js.br +0 -0
  108. package/dist/web/public/panels-ui.3df9cd95.js.gz +0 -0
  109. package/dist/web/public/settings-ui.1c15649c.js.br +0 -0
  110. package/dist/web/public/settings-ui.1c15649c.js.gz +0 -0
  111. package/dist/web/public/styles.0024c22e.css +0 -1
  112. package/dist/web/public/styles.0024c22e.css.br +0 -0
  113. package/dist/web/public/styles.0024c22e.css.gz +0 -0
  114. package/dist/web/public/terminal-ui.e7c5d233.js +0 -4
  115. package/dist/web/public/terminal-ui.e7c5d233.js.br +0 -0
  116. package/dist/web/public/terminal-ui.e7c5d233.js.gz +0 -0
@@ -0,0 +1,4 @@
1
+ "use strict";(function(e){const t=/^\x1b\[[\?>=]?[\d;]*[cnR]$/,s=/^\x1b\][\d;]*[^\x07\x1b]*(?:\x07|\x1b\\)$/,i=/^\x1b\[<\d+;\d+;\d+[Mm]$/,n=/^\x1b\[M[\s\S]{3}$/,r=/^\x1b\[[IO]$/,d=/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b\[[0-9;?<>=!]*[ -/]*[@-~]|\x1b[()#][0-9A-Za-z]|\x1b[=>78M]/g,m="\x1B[5~",c="\x1B[6~",L=/^\x1b(?:\[(?:[ABCDHF]|1;[2-8][ABCDHF]|[1-8](?:;[2-8])?~)|O[ABCDHF])$/,x="\x1B[200~";function f(T){return L.test(T)}const o=/^› /;function y(T){const I=Array.from(T);if(I.length===1){const k=I[0].codePointAt(0);return k===127?"backspace":k>=32?"char":"clear"}return T.charCodeAt(0)===27?"clear":T.charCodeAt(0)>=32?"text":"clear"}function u(T){try{const I=T.buffer.active,k=I.getLine(I.baseY+I.cursorY);return!!k&&o.test(k.translateToString(!0))}catch{return!1}}function w(T){return t.test(T)||s.test(T)}function S(T){return w(T)}function g(T){return r.test(T)||i.test(T)||n.test(T)}const v={og:{background:"#0d0d0d",foreground:"#e0e0e0",cursor:"#e0e0e0",cursorAccent:"#0d0d0d",selection:"rgba(255,255,255,0.3)",black:"#0d0d0d",red:"#ff6b6b",green:"#51cf66",yellow:"#ffd43b",blue:"#339af0",magenta:"#cc5de8",cyan:"#22b8cf",white:"#e0e0e0",brightBlack:"#495057",brightRed:"#ff8787",brightGreen:"#69db7c",brightYellow:"#ffe066",brightBlue:"#5c7cfa",brightMagenta:"#da77f2",brightCyan:"#66d9e8",brightWhite:"#ffffff"},"daylight-green":{background:"#161b23",foreground:"#dfe6ef",cursor:"#2fd3aa",cursorAccent:"#161b23",selection:"rgba(47,211,170,0.22)",black:"#161b23",red:"#ff8585",green:"#34d8a0",yellow:"#f0c25a",blue:"#5cc6e8",magenta:"#c79af2",cyan:"#2bcbbb",white:"#dfe6ef",brightBlack:"#5b6675",brightRed:"#ffa0a0",brightGreen:"#5fe6b8",brightYellow:"#ffd884",brightBlue:"#82d4ee",brightMagenta:"#d6b3f7",brightCyan:"#5ee0d4",brightWhite:"#f3f6fa"},"daylight-blue":{background:"#161b23",foreground:"#dfe6ef",cursor:"#38b6f0",cursorAccent:"#161b23",selection:"rgba(56,182,240,0.22)",black:"#161b23",red:"#ff8585",green:"#34d8a0",yellow:"#f0c25a",blue:"#5cc6e8",magenta:"#c79af2",cyan:"#2bcbbb",white:"#dfe6ef",brightBlack:"#5b6675",brightRed:"#ffa0a0",brightGreen:"#5fe6b8",brightYellow:"#ffd884",brightBlue:"#82d4ee",brightMagenta:"#d6b3f7",brightCyan:"#5ee0d4",brightWhite:"#f3f6fa"},"paper-gray":{background:"#f6f8fa",foreground:"#1f2328",cursor:"#0969da",cursorAccent:"#ffffff",selection:"rgba(9,105,218,0.2)",black:"#24292f",red:"#cf222e",green:"#1a7f37",yellow:"#9a6700",blue:"#0969da",magenta:"#8250df",cyan:"#1b7c83",white:"#59636e",brightBlack:"#6e7781",brightRed:"#a40e26",brightGreen:"#116329",brightYellow:"#7d4e00",brightBlue:"#0550ae",brightMagenta:"#6639ba",brightCyan:"#116b75",brightWhite:"#1f2328"},"solarized-light":{background:"#fdf6e3",foreground:"#586e75",cursor:"#147ba3",cursorAccent:"#fdf6e3",selection:"rgba(38,139,210,0.2)",black:"#eee8d5",red:"#dc322f",green:"#758600",yellow:"#9b7800",blue:"#147ba3",magenta:"#d33682",cyan:"#2a9189",white:"#073642",brightBlack:"#93a1a1",brightRed:"#cb4b16",brightGreen:"#657b83",brightYellow:"#586e75",brightBlue:"#268bd2",brightMagenta:"#6c71c4",brightCyan:"#2aa198",brightWhite:"#002b36"},"catppuccin-latte":{background:"#eff1f5",foreground:"#4c4f69",cursor:"#1e66f5",cursorAccent:"#ffffff",selection:"rgba(30,102,245,0.18)",black:"#5c5f77",red:"#d20f39",green:"#3b8f2b",yellow:"#a86605",blue:"#1e66f5",magenta:"#8839ef",cyan:"#177f86",white:"#6c6f85",brightBlack:"#7c7f93",brightRed:"#b50930",brightGreen:"#2f7622",brightYellow:"#8b5604",brightBlue:"#174fbf",brightMagenta:"#6f2bc5",brightCyan:"#116b71",brightWhite:"#4c4f69"},"rose-pine-dawn":{background:"#faf4ed",foreground:"#575279",cursor:"#286983",cursorAccent:"#fffaf3",selection:"rgba(40,105,131,0.2)",black:"#575279",red:"#b4637a",green:"#286983",yellow:"#96681f",blue:"#477f91",magenta:"#907aa9",cyan:"#3f7f8b",white:"#6e6a86",brightBlack:"#797593",brightRed:"#984d66",brightGreen:"#1f5266",brightYellow:"#7d5417",brightBlue:"#386b7c",brightMagenta:"#765f90",brightCyan:"#326b76",brightWhite:"#575279"}},p=new Set(["paper-gray","solarized-light","catppuccin-latte","rose-pine-dawn"]);function _(){return typeof document<"u"&&document.documentElement.dataset.skin||"daylight-blue"}function E(){const T=_();return v[T]||v["daylight-blue"]}function R(T=_()){return p.has(T)}e.CodemanTerminalInput={isTerminalQueryResponse:w,shouldSuppressTerminalQueryResponse:S,isTerminalFocusOrMouseReport:g,isComposerNavKey:f,classifyPredictInput:y,isCodexComposerRow:u,CODEX_COMPOSER_ROW_RE:o,BRACKETED_PASTE_START:x,USER_SCROLL_STICKY_SUPPRESS_MS:1500,TOUCH_COMPAT_MOUSE_SUPPRESS_MS:450,REPLAY_ESCAPE_RE:d,KEY_PAGE_UP:m,KEY_PAGE_DOWN:c,PAGE_KEY_SCREEN_FRACTION:.5,PAGE_KEY_MAX_PER_BATCH:3},e.CODEMAN_XTERM_THEMES=v,e.codemanCurrentXtermTheme=E,e.codemanCurrentSkinIsLight=R})(window),Object.assign(CodemanApp.prototype,{initTerminal(){const e=parseInt(localStorage.getItem("codeman-scrollback")),t=Number.isFinite(e)&&e>0?Math.max(e,DEFAULT_SCROLLBACK):DEFAULT_SCROLLBACK;if(this.terminal=new Terminal({theme:{...window.codemanCurrentXtermTheme()},fontFamily:'"Fira Code", "Cascadia Code", "JetBrains Mono", "SF Mono", Monaco, monospace',fontSize:MobileDetection.getDeviceType()==="mobile"?10:14,lineHeight:1.2,cursorBlink:!1,cursorStyle:"block",minimumContrastRatio:window.codemanCurrentSkinIsLight()?4.5:1,scrollback:t,allowTransparency:!0,allowProposedApi:!0}),this.fitAddon=new FitAddon.FitAddon,this.terminal.loadAddon(this.fitAddon),this._xtermSnapshots=new Map,typeof SerializeAddon<"u")try{this._serializeAddon=new SerializeAddon.SerializeAddon,this.terminal.loadAddon(this._serializeAddon)}catch{this._serializeAddon=null}if(typeof Unicode11Addon<"u")try{const o=new Unicode11Addon.Unicode11Addon;this.terminal.loadAddon(o),this.terminal.unicode.activeVersion="11"}catch{}const s=document.getElementById("terminalContainer");this.terminal.open(s),this._installMobileTapMouseGuard(),this.terminal.attachCustomKeyEventHandler(o=>{if(o.isComposing||o.keyCode===229||o.altKey&&!o.ctrlKey&&!o.shiftKey&&/^(Digit[1-9]|BracketLeft|BracketRight|KeyK)$/.test(o.code||"")||o.type==="keydown"&&this.shouldOpenCommandPaletteFromShortcut?.(o))return!1;if(this.shouldCopyTerminalSelectionFromShortcut?.(o)){const y=this.terminal.hasSelection?.()?this.terminal.getSelection():"";return y?(o.preventDefault(),this.copyTerminalSelection(y),!1):o.shiftKey?(o.preventDefault(),!1):!0}if((o.ctrlKey||o.metaKey)&&o.key==="v"&&o.type==="keydown")return this.activeSessionId&&this._handleImagePaste&&this._handleImagePaste(),!1;if(o.key==="Enter"&&(o.shiftKey||o.ctrlKey)&&o.type==="keydown"){if(this.activeSessionId)if(this._localEchoEnabled){const y=this._localEchoOverlay?.pendingText||"";this._localEchoOverlay?.clear(),this._localEchoOverlay?.suppressBufferDetection(),this._flushedOffsets?.delete(this.activeSessionId),this._flushedTexts?.delete(this.activeSessionId),y&&(this._pendingInput+=y,f()),setTimeout(()=>{fetch(`/api/sessions/${this.activeSessionId}/send-key`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({key:o.ctrlKey?"C-Enter":"S-Enter"})})},y?80:0)}else fetch(`/api/sessions/${this.activeSessionId}/send-key`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({key:o.ctrlKey?"C-Enter":"S-Enter"})});return!1}return!0});{const o=s.querySelector(".xterm-helper-textarea");if(o&&MobileDetection.isTouchDevice()){let y=!1,u=0;o.addEventListener("compositionstart",()=>{y=!0}),o.addEventListener("compositionend",()=>{y=!1}),o.addEventListener("keydown",w=>{!w.isComposing&&w.keyCode!==229&&(u=Date.now())}),o.addEventListener("input",w=>{if(y||w.isComposing||w.inputType!=="insertText"||!w.data||Date.now()-u<50)return;const S=w.data,g=this._localEchoOverlay?.pendingText||"";Promise.resolve().then(()=>{if(this._lastTerminalData?.data===S&&performance.now()-this._lastTerminalData.time<100){o.value="";return}const v=this._localEchoOverlay?.pendingText||"";if(this._localEchoEnabled&&v.length>g.length&&v.endsWith(S)){o.value="";return}const p=o.value;!p||p.trim()===""&&S!==" "||(this.terminal._core.coreService.triggerDataEvent(S,!0),o.value="")})})}}this._webglAddon=null;const i=new URLSearchParams(location.search),n=(()=>{try{const o=localStorage.getItem("codeman-webgl-disabled");if(!o)return!1;const{at:y}=JSON.parse(o);return Date.now()-y>WEBGL_FALLBACK.STICKY_EXPIRY_MS?(localStorage.removeItem("codeman-webgl-disabled"),!1):!0}catch{return!1}})(),r=this.loadAppSettingsFromStorage(),a=this.getDefaultSettings(),l=r.webglRendererEnabled??a.webglRendererEnabled,{skip:d,clearSticky:m}=shouldSkipWebGL({deviceType:MobileDetection.getDeviceType(),noWebglParam:i.has("nowebgl"),forceParam:i.get("webgl")==="force",stickyDisabled:n,userPrefEnabled:l});if(m)try{localStorage.removeItem("codeman-webgl-disabled")}catch{}if(d&&n&&console.log("[CRASH-DIAG] WebGL sticky-disabled from prior stalls \u2014 DOM renderer in use. Re-enable: ?webgl=force"),!d)if(typeof WebglAddon<"u")this._initWebGL();else{const o=document.createElement("script");o.src="vendor/xterm-addon-webgl.min.js",o.onload=()=>this._initWebGL(),o.onerror=()=>console.warn("[CRASH-DIAG] Failed to load WebGL addon \u2014 using canvas renderer"),document.head.appendChild(o)}this._localEchoOverlay=new LocalEchoOverlay(this.terminal),this._predictiveEcho=typeof PredictiveEchoOverlay<"u"?new PredictiveEchoOverlay(this.terminal):null,this._predictiveEcho?.setPredictWhen(o=>window.CodemanTerminalInput.isCodexComposerRow(o)),MobileDetection.isTouchDevice()&&(this.terminal.onCursorMove(()=>this._syncMobileHelperTextareaToCursor()),this.terminal.onRender(()=>this._syncMobileHelperTextareaToCursor())),this._cjkInput=null,typeof CjkInput<"u"&&(this._cjkInput=CjkInput.init({send:o=>{this._handleCjkInput(o)}}));const c=this.terminal.focus.bind(this.terminal);this.terminal.focus=()=>{const o=document.getElementById("cjkInput");o?.classList.contains("cjk-input-visible")?o.focus():c()},MobileDetection.getDeviceType()==="mobile"&&document.body.classList.contains("safari-browser")?requestAnimationFrame(()=>{this.fitAddon.fit(),requestAnimationFrame(()=>this.fitAddon.fit())}):this.fitAddon.fit(),this.registerFilePathLinkProvider(),s.addEventListener("wheel",o=>{const y=this.terminal?.modes?.mouseTrackingMode;if(y&&y!=="none"||this.terminal?.buffer?.active?.type==="alternate")return;if(o.preventDefault(),o.stopPropagation(),this._shouldForwardWheelToApp(o)){this._logScrollRouting("forward-sgr"),this._forwardScrollToApp(o.clientX,o.clientY,this._wheelScrollLines(o));return}const u=this._wheelScrollLinesFloat(o);this._maybePageCliTranscript(o,u)||(this._logScrollRouting("local-scrollback"),this._noteTerminalUserScroll(u),this._smoothScrollBy(u))},{passive:!1,capture:!0});{const o=()=>this.terminal._core?._renderService?.dimensions?.css?.cell?.height||13;let y=0,u=0,w=0,S=0,g=null,v=!1;const p=I=>{const k=S?(I-S)/16.67:1;if(S=I,!v&&Math.abs(w)>.3){const A=Math.round(w/o());A!==0&&(this._shouldForwardWheelToApp({shiftKey:!1})?this._forwardScrollToApp(y,u,A):this._maybePageCliTranscript({shiftKey:!1},A)||(this.terminal.scrollLines(A),this._maybeLoadMoreHistoryOnScroll(A))),w*=.92,g=requestAnimationFrame(p)}else v?g=requestAnimationFrame(p):(g=null,w=0)};let _=0,E=!1,R=0;const T=8;s.addEventListener("touchstart",I=>{I.touches.length===1&&(y=I.touches[0].clientX,u=I.touches[0].clientY,R=u,w=0,_=0,v=!0,E=!1,S=0,g&&(cancelAnimationFrame(g),g=null))},{passive:!0}),s.addEventListener("touchmove",I=>{if(I.touches.length===1&&v){const k=I.touches[0].clientY;if(!E&&Math.abs(k-R)>=T&&(E=!0),!E)return;I.preventDefault();const A=u-k;_+=A,w=A*1.2,y=I.touches[0].clientX,u=k;const b=o(),M=Math.trunc(_/b);M!==0&&(this._shouldForwardWheelToApp({shiftKey:!1})?(this._logScrollRouting("forward-sgr"),this._forwardScrollToApp(y,u,M)):this._maybePageCliTranscript({shiftKey:!1},M)||(this._logScrollRouting("local-scrollback"),this._noteTerminalUserScroll(M),this.terminal.scrollLines(M),this._maybeLoadMoreHistoryOnScroll(M)),_-=M*b)}},{passive:!1}),s.addEventListener("touchend",I=>{if(v=!1,!g&&Math.abs(w)>.3&&(g=requestAnimationFrame(p)),!E&&this.terminal){const k=I.changedTouches&&I.changedTouches[0],A=this.terminal.modes?.mouseTrackingMode,b=!!A&&A!=="none";k&&this._suppressTrustedTapMouseEvents(),k&&b?this._dispatchSyntheticTerminalClick(k.clientX,k.clientY):k&&this._sessionUsesServerMouseStrip()&&this._sendSyntheticSgrTap(k.clientX,k.clientY),this._syncMobileHelperTextareaToCursor();const M=document.getElementById("cjkInput");M?.classList.contains("cjk-input-visible")?M.focus():this.terminal.focus()}},{passive:!0}),s.addEventListener("touchcancel",()=>{v=!1,w=0,_=0},{passive:!0})}s.addEventListener("click",o=>this._handleDesktopTerminalClick(o)),this.showWelcome(),this.initImageInput(),this._chunkedWriteGen=0,this._bufferLoadSeq=0,this._bufferLoadOwner=null,this._lastUserScrollUpAt=null,this._resizeTimeout=null,this._lastResizeDims=null;const C=40,L=10,x=()=>{this._resizeTimeout&&clearTimeout(this._resizeTimeout),this._resizeTimeout=setTimeout(()=>{this._resizeTimeout=null,this.fitAddon&&this.fitAddon.fit(),this.flickerFilterBuffer&&(this.flickerFilterTimeout&&(clearTimeout(this.flickerFilterTimeout),this.flickerFilterTimeout=null),this.flushFlickerBuffer());const o=typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible;if(this.activeSessionId&&!o){const y=this.fitAddon.proposeDimensions(),u=y?Math.max(y.cols,C):C,w=y?Math.max(y.rows,L):L;if(!this._lastResizeDims||u!==this._lastResizeDims.cols||w!==this._lastResizeDims.rows){const S=this.activeSessionId?this.sessions.get(this.activeSessionId):null;S&&S.mode!=="shell"&&this.terminal&&this.isTerminalAtBottom()&&this.terminal.write("\x1B[3J\x1B[H\x1B[2J"),this._lastResizeDims={cols:u,rows:w};const g=typeof MobileDetection<"u"&&MobileDetection.getDeviceType?MobileDetection.getDeviceType():"desktop";let v=!1;if(this._wsReady&&this._wsSessionId===this.activeSessionId)try{this._ws.send(JSON.stringify({t:"z",c:u,r:w,v:g})),v=!0}catch{}v||fetch(`/api/sessions/${this.activeSessionId}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({cols:u,rows:w,viewportType:g})}).catch(()=>{})}}this.updateConnectionLines(),this._localEchoOverlay?.hasPending&&this._localEchoOverlay.rerender()},300)};window.addEventListener("resize",x),this.terminalResizeObserver&&this.terminalResizeObserver.disconnect(),this.terminalResizeObserver=new ResizeObserver(x),this.terminalResizeObserver.observe(s),this._pendingInput="",this._inputFlushTimeout=null,this._lastKeystrokeTime=0;const f=()=>{if(this._inputFlushTimeout=null,this._pendingInput&&this.activeSessionId){const o=this._pendingInput,y=this.activeSessionId;this._pendingInput="",this._sendInputAsync(y,o)}};this.terminal.onData(o=>{if(!/^\x1b\[<\d+;\d+;\d+[Mm]$/.test(o)&&(window.cjkActive||document.activeElement?.id==="cjkInput")){const u=document.getElementById("cjkInput");u?.classList.contains("cjk-input-visible")&&document.activeElement===this.terminal.textarea&&!window.CodemanTerminalInput?.shouldSuppressTerminalQueryResponse(o)&&(_crashDiag.log("CJK regain-focus (onData swallowed input)"),u.focus());return}if(this.activeSessionId){if(window.CodemanTerminalInput?.shouldSuppressTerminalQueryResponse(o))return;typeof KeyboardAccessoryBar<"u"&&KeyboardAccessoryBar.isCtrlArmed?.()&&!window.CodemanTerminalInput?.isTerminalFocusOrMouseReport(o)&&(o=KeyboardAccessoryBar.consumeCtrl(o)),this._lastTerminalData={data:o,time:performance.now()};const u=this._localEchoEnabled&&this._echoPassthroughSessions?.has(this.activeSessionId);if(u&&(o==="\r"||o==="")&&this._echoPassthroughSessions.delete(this.activeSessionId),this._localEchoEnabled&&!u){if(o==="\x7F"){const S=this._localEchoOverlay?.removeChar();if(S==="flushed"){const{count:g,text:v}=this._localEchoOverlay.getFlushed();this._flushedOffsets?.has(this.activeSessionId)&&(g===0?(this._flushedOffsets.delete(this.activeSessionId),this._flushedTexts?.delete(this.activeSessionId)):(this._flushedOffsets.set(this.activeSessionId,g),this._flushedTexts?.set(this.activeSessionId,v))),this._pendingInput+=o,f()}else S===!1&&(this._pendingInput+=o,f());return}if(/^[\r\n]+$/.test(o)){const S=this._localEchoOverlay?.pendingText||"";this._localEchoOverlay?.clear(),this._localEchoOverlay?.suppressBufferDetection(),this._flushedOffsets?.delete(this.activeSessionId),this._flushedTexts?.delete(this.activeSessionId),this._inputFlushTimeout&&(clearTimeout(this._inputFlushTimeout),this._inputFlushTimeout=null),S&&(this._pendingInput+=S,f()),setTimeout(()=>{this._pendingInput+="\r",f()},80);return}if(o.length>1&&o.charCodeAt(0)>=32){this._localEchoOverlay?.appendText(o);return}if(o.charCodeAt(0)<32){if(o.length>1&&o.charCodeAt(0)===27){if(o.startsWith(window.CodemanTerminalInput.BRACKETED_PASTE_START)){const g=!!this._localEchoOverlay?.pendingText;this._flushLocalEchoPending(),g?(f(),setTimeout(()=>{this._pendingInput+=o,f()},80)):(this._pendingInput+=o,f());return}if(window.CodemanTerminalInput.isComposerNavKey(o)){this._flushLocalEchoPending(),this._echoPassthroughSessions||(this._echoPassthroughSessions=new Set),this._echoPassthroughSessions.add(this.activeSessionId),this._pendingInput+=o,f();return}this._pendingInput+=o,f();return}if(this._restoringFlushedState){this._pendingInput+=o,f();return}if(o===" "){const g=this._localEchoOverlay?.pendingText||"";this._localEchoOverlay?.clear(),this._flushedOffsets?.delete(this.activeSessionId),this._flushedTexts?.delete(this.activeSessionId),g&&(this._pendingInput+=g),this._pendingInput+=o,this._inputFlushTimeout&&(clearTimeout(this._inputFlushTimeout),this._inputFlushTimeout=null);let v="";try{const _=this._localEchoOverlay?.findPrompt?.();if(_){const E=this.terminal.buffer.active,R=E.getLine(E.viewportY+_.row);R&&(v=R.translateToString(!0).slice(_.col+2).trimEnd())}}catch{}this._tabCompletionBaseText=v,f(),this._tabCompletionSessionId=this.activeSessionId,this._tabCompletionRetries=0,this._tabCompletionFallback&&clearTimeout(this._tabCompletionFallback);const p=this;this._tabCompletionFallback=setTimeout(()=>{if(p._tabCompletionFallback=null,!p._tabCompletionSessionId||p._tabCompletionSessionId!==p.activeSessionId)return;const _=p._localEchoOverlay;!_||_.pendingText||p.terminal.write("",()=>{if(!p._tabCompletionSessionId)return;_.resetBufferDetection();const E=_.detectBufferText();E&&E!==p._tabCompletionBaseText&&(p._tabCompletionSessionId=null,p._tabCompletionRetries=0,p._tabCompletionBaseText=null,_.rerender())})},300);return}const S=this._localEchoOverlay?.pendingText||"";this._localEchoOverlay?.clear(),this._localEchoOverlay?.suppressBufferDetection(),this._flushedOffsets?.delete(this.activeSessionId),this._flushedTexts?.delete(this.activeSessionId),S&&(this._pendingInput+=S),this._pendingInput+=o,this._inputFlushTimeout&&(clearTimeout(this._inputFlushTimeout),this._inputFlushTimeout=null),f();return}if(o.length===1&&o.charCodeAt(0)>=32){this._localEchoOverlay?.addChar(o);return}}if(this._predictHookOnData(o),this._pendingInput+=o,o.charCodeAt(0)<32||o.length>1){this._inputFlushTimeout&&(clearTimeout(this._inputFlushTimeout),this._inputFlushTimeout=null),f();return}const w=performance.now();w-this._lastKeystrokeTime>50?(this._inputFlushTimeout&&(clearTimeout(this._inputFlushTimeout),this._inputFlushTimeout=null),this._lastKeystrokeTime=w,f()):(this._lastKeystrokeTime=w,this._inputFlushTimeout||(this._inputFlushTimeout=setTimeout(f,0)))}})},registerFilePathLinkProvider(){const e=this;let t=-1;this.terminal.registerLinkProvider({provideLinks(s,i){s!==t&&(t=s,console.debug("[LinkProvider] Checking line:",s));const n=e.terminal.buffer.active;if(!n.getLine(s-1)){i(void 0);return}const a=e.terminal.cols,l=_=>n.getLine(_-1),d=_=>{if(_<=1)return!1;if(l(_)?.isWrapped)return!0;const E=l(_-1);return!!E&&E.translateToString(!0).length>=a},m=12;let c=s;for(;c>1&&s-c<m&&d(c);)c--;let h=s;for(;h<n.length&&h-c<m&&d(h+1);)h++;const C=[];for(let _=c;_<=h;_++){const E=l(_);if(!E)break;C.push(E.translateToString(_===h))}const L=C.join(""),x=_=>{let E=_;for(let R=0;R<C.length-1;R++){if(E<C[R].length)return{x:E+1,y:c+R};E-=C[R].length}return{x:E+1,y:c+C.length-1}};if(!L||!L.includes("/")){i(void 0);return}const f=[],o=/https?:\/\/(?:[^\s"'<>|;&)\]\x00-\x1f]|&(?!&))+/g,y=(_,E)=>{const R=_.replace(/[.,;:!?)&]+$/,""),T=L.indexOf(R,E);if(T===-1)return;const I=x(T),k=x(T+R.length);f.some(A=>A.range.start.x===I.x&&A.range.start.y===I.y)||f.push({text:R,range:{start:I,end:k},decorations:{pointerCursor:!0,underline:!0},activate(A,b){window.open(b,"_blank","noopener,noreferrer")},hover(){e._linkHovered=!0},leave(){e._linkHovered=!1}})},u=/\b(tail|cat|head|less|grep|watch|vim|nano)\s+(?:[^\s\/]+\s+){0,4}(\/[^\s"'<>|;&\n\x00-\x1f]+)/g,w=/(\/(?:home|tmp|var|etc|opt)[^\s"'<>|;&\n\x00-\x1f]*\.(?:log|txt|json|md|yaml|yml|csv|xml|sh|py|ts|js|png|jpe?g|gif|webp|bmp|svg|pdf))\b/g,S=/Bash\([^)]*?(\/(?:home|tmp|var|etc|opt)[^\s"'<>|;&\)\n\x00-\x1f]+)/g,g=new Set(["png","jpg","jpeg","gif","webp","bmp","svg","pdf"]),v=(_,E)=>{const R=L.indexOf(_,E);if(R===-1)return;const T=x(R),I=x(R+_.length);f.some(k=>k.range.start.x===T.x&&k.range.start.y===T.y)||f.push({text:_,range:{start:T,end:I},decorations:{pointerCursor:!0,underline:!0},activate(k,A){const b=(A.split(".").pop()||"").toLowerCase();if(g.has(b)){e.openFilePreview(A,e.activeSessionId);return}e.openLogViewerWindow(A,e.activeSessionId)},hover(){e._linkHovered=!0},leave(){e._linkHovered=!1}})};let p;for(o.lastIndex=0;(p=o.exec(L))!==null;)y(p[0],p.index);for(u.lastIndex=0;(p=u.exec(L))!==null;)v(p[2],p.index);for(w.lastIndex=0;(p=w.exec(L))!==null;)v(p[1],p.index);for(S.lastIndex=0;(p=S.exec(L))!==null;)v(p[1],p.index);f.length>0&&console.debug("[LinkProvider] Found links:",f.map(_=>_.text)),i(f.length>0?f:void 0)}}),console.log("[LinkProvider] File path link provider registered")},showWelcome(){if(this.shouldUseMobileOverview?.()){const t=document.getElementById("welcomeOverlay");t&&t.classList.remove("visible"),this.hideHomeSessions?.(),this.showMobileOverview(),this._updateCjkInputState?.();return}this.hideMobileOverview?.();const e=document.getElementById("welcomeOverlay");e&&(e.classList.add("visible"),this.loadTunnelStatus(),this.applyWelcomeCliVisibility(),this.loadHistorySessions(),this.initSearchPanel(),this.showHomeSessions?.()),this._updateCjkInputState?.()},hideWelcome(){this.hideMobileOverview?.(),this.hideHomeSessions?.();const e=document.getElementById("welcomeOverlay");e&&e.classList.remove("visible");const t=document.getElementById("welcomeQr");t&&(clearTimeout(this._welcomeQrShrinkTimer),t.classList.remove("expanded")),this._updateCjkInputState?.()},async _fetchHistorySessions(){const s=(await(await fetch("/api/history/sessions")).json()).data?.sessions||[];if(s.length===0)return[];const i=new Map;for(const r of s){const a=r.projectKey||r.workingDir;i.has(a)||i.set(a,[]),i.get(a).push(r)}const n=[];for(const[,r]of i)n.push(...r.slice(0,3));return n.sort((r,a)=>new Date(a.lastModified)-new Date(r.lastModified)),n},async _fetchUnifiedSessions(e=60){const t=await fetch("/api/sessions/unified?limit="+e),s=await t.json().catch(()=>null);if(!t.ok||!s||s.success===!1||!s.data)throw new Error(s?.error||`unified sessions request failed (HTTP ${t.status})`);return s.data.sessions||[]},_worktreeLabel(e){const t=e&&e.worktreeName;if(!t)return"";let s=e.gitBranch||"";return(s===t||s===`worktree-${t}`)&&(s=""),s.length>24&&(s=s.slice(0,23)+"\u2026"),"\u2442 "+[t,s].filter(Boolean).join(" \xB7 ")},_resolveCaseLabel(e,t){if(!e)return"";let s=null;for(const i of t||[])if(!(!i||!i.path)){if(e===i.path)return`#${i.name}`;if(e.startsWith(i.path+"/")){const n=i.path.length;(!s||n>s.len)&&(s={name:i.name,suffix:e.slice(n),len:n})}}return s?`#${s.name}${s.suffix}`:e.split("/").pop()||e},_shortenHomePath(e){return(e||"").replace(/^\/home\/[^/]+\//,"~/").replace(/^\/Users\/[^/]+\//,"~/")},_buildHistoryItem(e,t,s){const i=s?.showViewAll!==!1,n=typeof e.sizeBytes=="number",r=n?e.sizeBytes<1024?`${e.sizeBytes}B`:e.sizeBytes<1048576?`${(e.sizeBytes/1024).toFixed(0)}K`:`${(e.sizeBytes/1048576).toFixed(1)}M`:"",a=typeof e.lastActivityAt=="number"?e.lastActivityAt:e.lastModified?Date.parse(e.lastModified):NaN;let l="";if(!Number.isNaN(a)){const b=new Date(a);l=b.toLocaleDateString("en",{month:"short",day:"numeric"})+" "+b.toLocaleTimeString("en",{hour:"2-digit",minute:"2-digit",hour12:!1})}const d=this._shortenHomePath(e.workingDir),m=this._resolveCaseLabel(e.workingDir,t),c=Array.isArray(e.sources)&&e.sources.includes("live"),h=e.pinned===!0,C=document.createElement("div");C.className="history-item"+(h?" is-pinned":""),C.title=e.workingDir||"";const L=document.createElement("div");L.className="history-item-main",L.addEventListener("click",s?.onActivate||(()=>{c&&this.sessions.has(e.sessionId)?this.selectSession(e.sessionId):this.resumeHistorySession(e.claudeSessionId||e.sessionId,e.workingDir||"",e.name)}));const x=document.createElement("div");x.className="history-item-text";const f=document.createElement("span");if(f.className="history-item-title",h){const b=document.createElement("span");b.className="history-item-pin",b.textContent="\u{1F4CC}",b.setAttribute("aria-label","Pinned"),b.title="Pinned",f.appendChild(b)}f.appendChild(document.createTextNode(this._historyRowLabel(e,d)));const o=document.createElement("div");if(o.className="history-item-badges",e.mode){const b=document.createElement("span");b.className="history-item-badge history-item-badge-mode",b.textContent=e.mode,o.appendChild(b)}const y=this._worktreeLabel(e);if(y){const b=document.createElement("span");b.className="history-item-badge history-item-badge-worktree",b.textContent=y,b.title=e.worktreeRepo?`worktree of ${e.worktreeRepo}`:y,o.appendChild(b)}if(c){const b=document.createElement("span");b.className="history-item-badge history-item-badge-live",b.textContent="LIVE",o.appendChild(b)}const u=document.createElement("span");u.className="history-item-subtitle",m.startsWith("#")&&u.classList.add("is-case"),u.textContent=m,x.append(f),o.childElementCount>0&&x.append(o),x.append(u);const w=document.createElement("span");w.className="history-item-meta",w.textContent=l;const S=document.createElement("button");S.className="history-item-expand",S.type="button",S.setAttribute("aria-haspopup","menu"),S.setAttribute("aria-label","Session actions"),S.setAttribute("aria-expanded","false"),S.textContent="\u22EF",L.append(x,w,S);const g=document.createElement("div");g.className="history-item-detail",g.hidden=!0;const v=document.createElement("div");v.className="history-detail-row";const p=document.createElement("span");p.className="history-detail-label",p.textContent="Prompt";const _=document.createElement("span");_.className="history-detail-value history-detail-prompt",_.textContent=e.firstPrompt||"(no prompt captured)",v.append(p,_);let E=null;if(e.lastPrompt&&e.lastPrompt!==e.firstPrompt){E=document.createElement("div"),E.className="history-detail-row";const b=document.createElement("span");b.className="history-detail-label",b.textContent="Last prompt";const M=document.createElement("span");M.className="history-detail-value history-detail-prompt",M.textContent=e.lastPrompt,E.append(b,M)}const R=document.createElement("div");R.className="history-detail-row";const T=document.createElement("span");T.className="history-detail-label",T.textContent="Path";const I=document.createElement("span");I.className="history-detail-value history-detail-path",I.textContent=d,R.append(T,I);const k=document.createElement("div");k.className="history-detail-row history-detail-meta";const A=[];if(l&&A.push(l),n&&A.push(r),A.push(e.sessionId.slice(0,8)),k.textContent=A.join(" \xB7 "),g.append(v),E&&g.append(E),g.append(R,k),i&&e.projectKey){const b=document.createElement("div");b.className="history-detail-row history-detail-actions";const M=document.createElement("button");M.type="button",M.className="history-view-all-btn",M.textContent="View all in this folder",M.addEventListener("click",P=>{P.stopPropagation(),this.openFolderHistoryModal(e.projectKey,e.workingDir,t)}),b.appendChild(M),g.appendChild(b)}return S.addEventListener("click",b=>{b.stopPropagation(),b.preventDefault(),this._openSessionRowMenu(b.currentTarget,e,t,C,g)}),C.append(L,g),C},_openSessionRowMenu(e,t,s,i,n){if(this._openRowMenuClose)try{this._openRowMenuClose()}catch{}const r=Array.isArray(t.sources)&&t.sources.includes("live")&&this.sessions.has(t.sessionId),a=document.createElement("div");a.className="session-row-menu",a.setAttribute("role","menu");let l=null,d=null,m=null;const c=()=>{document.removeEventListener("mousedown",l,!0),document.removeEventListener("keydown",d,!0),window.removeEventListener("scroll",m,!0),window.removeEventListener("resize",m,!0);try{a.remove()}catch{}this._openRowMenuEl===a&&(this._openRowMenuEl=null,this._openRowMenuClose=null)},h=(u,w,S)=>{const g=document.createElement("button");g.type="button",g.className="session-row-menu-item",g.setAttribute("role","menuitem");const v=document.createElement("span");if(v.className="session-row-menu-label",v.textContent=u,g.appendChild(v),S&&S.sublabel){const p=document.createElement("span");p.className="session-row-menu-sublabel",p.textContent=S.sublabel,g.appendChild(p)}g.addEventListener("click",async p=>{p.stopPropagation(),p.preventDefault(),await w()}),a.appendChild(g)};h(r?"Switch to session":"Resume session",()=>{r?this.selectSession(t.sessionId):this.resumeHistorySession(t.claudeSessionId||t.sessionId,t.workingDir||"",t.name),this.closeSessionManager?.(),c()});const C=t.pinned===!0;h(C?"Unpin session":"Pin to top",async()=>{await this._setSessionPinned(t.sessionId,!C)?(t.pinned=!C,this.showToast(C?"Unpinned":"Pinned to top","success")):this.showToast("Pin failed","error"),c()}),r&&h("Open folder",()=>{this.selectSession(t.sessionId),this.loadFileBrowser?.(t.sessionId),this.closeSessionManager?.(),c()}),t.workingDir&&h("Copy path",async()=>{const u=await this._copyText(t.workingDir);this.showToast(u?"Path copied":"Copy failed",u?"success":"error"),c()}),h("Show details",()=>{const u=i.classList.toggle("expanded");n.hidden=!u,e.setAttribute("aria-expanded",u?"true":"false"),c()}),document.body.appendChild(a);const L=e.getBoundingClientRect(),x=a.getBoundingClientRect(),f=4;let o=L.bottom+f;o+x.height>window.innerHeight&&L.top-f-x.height>=0&&(o=L.top-f-x.height);let y=L.right-x.width;y<f&&(y=f),y+x.width>window.innerWidth-f&&(y=Math.max(f,window.innerWidth-f-x.width)),a.style.top=`${Math.max(f,o)}px`,a.style.left=`${y}px`,l=u=>{a.contains(u.target)||e.contains(u.target)||c()},d=u=>{u.key==="Escape"&&(u.stopPropagation(),c())},m=()=>c(),document.addEventListener("mousedown",l,!0),document.addEventListener("keydown",d,!0),window.addEventListener("scroll",m,!0),window.addEventListener("resize",m,!0),this._openRowMenuEl=a,this._openRowMenuClose=c},async _setSessionPinned(e,t){try{const s=await fetch(`/api/sessions/${encodeURIComponent(e)}/pin`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"same-origin",body:JSON.stringify({pinned:t})});return s.ok?(await s.json())?.success===!0:!1}catch(s){return console.error("[_setSessionPinned]",s),!1}},_HISTORY_INITIAL_COUNT:10,_HISTORY_FETCH_LIMIT:200,_HISTORY_SORT_KEY:"codeman:historySort",async loadHistorySessions(){const e=document.getElementById("historySessions"),t=document.getElementById("historyList");if(!(!e||!t))try{const s=Array.isArray(this.cases)&&this.cases.length>0?Promise.resolve(this.cases):fetch("/api/cases").then(r=>r.ok?r.json():null).then(r=>r?.data||[]).catch(()=>[]),[i,n]=await Promise.all([this._fetchUnifiedSessions(this._HISTORY_FETCH_LIMIT),s]);if(i.length===0){e.style.display="none";return}this._historyAll=i,this._historyCases=n,this._wireHistoryControls(),this._renderHistoryList(),e.style.display=""}catch(s){console.error("[loadHistorySessions]",s),e.style.display="none"}},_wireHistoryControls(){if(this._historyControlsWired)return;const e=document.getElementById("historyFilter"),t=document.getElementById("historySort");if(!(!e&&!t)){if(this._historyControlsWired=!0,t)try{const s=localStorage.getItem(this._HISTORY_SORT_KEY);s&&Array.from(t.options).some(i=>i.value===s)&&(t.value=s)}catch{}e&&(e.addEventListener("input",()=>this._renderHistoryList()),e.addEventListener("keydown",s=>{s.key==="Escape"&&e.value&&(s.stopPropagation(),e.value="",this._renderHistoryList())})),t&&t.addEventListener("change",()=>{try{localStorage.setItem(this._HISTORY_SORT_KEY,t.value)}catch{}this._renderHistoryList()})}},_historyRowMatches(e,t,s){return[e.name,e.workingDir,this._resolveCaseLabel(e.workingDir,s),e.firstPrompt,e.lastPrompt,e.sessionId].some(n=>typeof n=="string"&&n.toLowerCase().includes(t))},_historyRowLabel(e,t){return e.name||e.firstPrompt||t||""},_sortHistoryRows(e,t){const n=t==="name"?l=>this._historyRowLabel(l,this._shortenHomePath(l.workingDir)).toLowerCase():l=>((l.workingDir||"").split("/").pop()||"").toLowerCase(),r=t==="recent"?e.slice():e.slice().sort((l,d)=>n(l).localeCompare(n(d),void 0,{sensitivity:"base",numeric:!0})),a=r.filter(l=>l.pinned);return a.length===0?r:a.concat(r.filter(l=>!l.pinned))},_renderHistoryList(){const e=document.getElementById("historyList");if(!e)return;const t=this._historyAll||[],s=this._historyCases||[],i=document.getElementById("historyCount"),n=(document.getElementById("historyFilter")?.value||"").trim().toLowerCase(),r=document.getElementById("historySort")?.value||"recent",a=n?t.filter(h=>this._historyRowMatches(h,n,s)):t,l=this._sortHistoryRows(a,r),d=!!this._historyExpanded||n.length>0,m=d?l:l.slice(0,this._HISTORY_INITIAL_COUNT);if(e.replaceChildren(),e.classList.toggle("expanded",d),l.length===0){const h=document.createElement("div");h.className="history-empty",h.textContent=`No conversations match "${n}"`,e.appendChild(h)}for(const h of m)e.appendChild(this._buildHistoryItem(h,s));const c=l.length-m.length;if(c>0){const h=document.createElement("button");h.className="history-show-more",h.textContent=`Show ${c} more`,h.addEventListener("click",()=>{this._historyExpanded=!0,this._renderHistoryList()}),e.appendChild(h)}else if(d&&!n&&l.length>this._HISTORY_INITIAL_COUNT){const h=document.createElement("button");h.className="history-show-more",h.textContent="Show less",h.addEventListener("click",()=>{this._historyExpanded=!1,this._renderHistoryList(),e.scrollTop=0}),e.appendChild(h)}i&&(i.textContent=n?`${l.length} of ${t.length}`:l.length>m.length?`${m.length} of ${l.length}`:String(l.length))},_FOLDER_HISTORY_PAGE_SIZE:20,openFolderHistoryModal(e,t,s){this._closeFolderHistoryModal();const i=document.createElement("div");i.className="modal active folder-history-modal",i.id="folderHistoryModal";const n=document.createElement("div");n.className="modal-backdrop",n.addEventListener("click",()=>this._closeFolderHistoryModal());const r=document.createElement("div");r.className="modal-content modal-lg";const a=document.createElement("div");a.className="modal-header";const l=document.createElement("h3");l.textContent="Folder History";const d=document.createElement("div");d.className="folder-history-subtitle",d.textContent=this._shortenHomePath(t);const m=document.createElement("button");m.className="modal-close",m.setAttribute("aria-label","Close"),m.innerHTML="&times;",m.addEventListener("click",()=>this._closeFolderHistoryModal()),a.append(l,m);const c=document.createElement("div");c.className="modal-body";const h=document.createElement("div");h.className="folder-history-list",h.setAttribute("data-loading","true"),h.textContent="Loading...",c.append(d,h),r.append(a,c),i.append(n,r),document.body.appendChild(i),this._folderHistoryState={projectKey:e,workingDir:t,cases:s||[],offset:0,total:null,list:h},this._folderHistoryEscHandler=C=>{C.key==="Escape"&&this._closeFolderHistoryModal()},document.addEventListener("keydown",this._folderHistoryEscHandler),this._loadFolderHistoryPage()},async _loadFolderHistoryPage(){const e=this._folderHistoryState;if(!e)return;const{projectKey:t,cases:s,list:i}=e,n=this._FOLDER_HISTORY_PAGE_SIZE,r=e.offset,a=i.querySelector(".folder-history-more");a&&a.remove(),r===0&&(i.replaceChildren(),i.removeAttribute("data-loading"));try{const l=`/api/history/sessions?projectKey=${encodeURIComponent(t)}&offset=${r}&limit=${n}`,m=await(await fetch(l)).json(),c=m.data?.sessions||[];if(e.total=typeof m.data?.total=="number"?m.data.total:c.length+r,r===0&&c.length===0){const h=document.createElement("div");h.className="folder-history-empty",h.textContent="No conversations found in this folder.",i.appendChild(h);return}for(const h of c)i.appendChild(this._buildHistoryItem(h,s,{showViewAll:!1}));if(e.offset=r+c.length,e.offset<e.total){const h=e.total-e.offset,C=document.createElement("button");C.className="history-show-more folder-history-more",C.textContent=`Show ${Math.min(n,h)} more (${h} remaining)`,C.addEventListener("click",()=>this._loadFolderHistoryPage()),i.appendChild(C)}}catch(l){console.error("[loadFolderHistoryPage]",l);const d=document.createElement("div");d.className="folder-history-empty",d.textContent="Failed to load folder history.",i.appendChild(d)}},_closeFolderHistoryModal(){const e=document.getElementById("folderHistoryModal");e&&e.remove(),this._folderHistoryEscHandler&&(document.removeEventListener("keydown",this._folderHistoryEscHandler),this._folderHistoryEscHandler=null),this._folderHistoryState=null},_resolveResumeName(e,t){if(typeof e=="string"&&e.trim())return e;const s=(t||"").split("/").pop()||"session";let i=1;for(const[,n]of this.sessions){const r=n.name&&n.name.match(/^w(\d+)-/);if(r){const a=parseInt(r[1]);a>=i&&(i=a+1)}}return`w${i}-${s}`},async resumeHistorySession(e,t,s){document.getElementById("runModeMenu")?.classList.remove("active"),this._closeFolderHistoryModal();try{this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Resuming conversation ${e.slice(0,8)}...\x1B[0m`);const i=this._resolveResumeName(s,t),r=(this.cases||[]).find(C=>C.path===t)?.name||t.split("/").pop()||"",a=this.loadAppSettingsFromStorage(),l=this.buildEnvOverrides(this.getCaseSettings(r),a),d=this.getEffortSetting(a),c=await(await fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:t,name:i,resumeSessionId:e,...Object.keys(l).length>0?{envOverrides:l}:{},...d?{effort:d}:{}})})).json();if(!c.success)throw new Error(c.error);const h=c.data.session.id;await fetch(`/api/sessions/${h}/interactive`,{method:"POST"}),this.terminal.writeln(`\x1B[90m Session ${i} ready\x1B[0m`),await this.selectSession(h),this.terminal.focus()}catch(i){this.terminal.writeln(`\x1B[1;31m Error: ${i.message}\x1B[0m`)}},isTerminalAtBottom(){if(!this.terminal)return!0;const e=this.terminal.buffer.active;return e.viewportY>=e.baseY-2},_noteTerminalUserScroll(e){e<0?this._lastUserScrollUpAt=performance.now():this.isTerminalAtBottom()&&(this._lastUserScrollUpAt=null)},_maybeLoadMoreHistoryOnScroll(e){e>=0||this.terminal?.buffer?.active?.viewportY===0&&this._maybeRefetchFullHistory?.()},_estimateReplayRows(e,t){if(typeof e!="string"||!e)return 0;const s=t>0?t:80,i=e.replace(window.CodemanTerminalInput.REPLAY_ESCAPE_RE,"");let n=0;for(const r of i.split(`
2
+ `)){const a=r.endsWith("\r")?r.length-1:r.length;n+=a>s?Math.ceil(a/s):1}return n},_replayWouldShrinkBuffer(e){const t=this.terminal,s=t?.buffer?.active?.length||0;if(!s)return!1;const i=t?.rows||24;return this._estimateReplayRows(e,t?.cols)+i<s},_smoothScrollBy(e){if(!e||(this._smoothScrollPending=(this._smoothScrollPending||0)+e,this._smoothScrollSession=this.activeSessionId,this._smoothScrollFrame))return;const t=()=>{this._smoothScrollFrame=null;const s=this._smoothScrollPending||0;if(!s)return;if(this.activeSessionId!==this._smoothScrollSession){this._smoothScrollPending=0;return}if(Math.abs(s)<1)return;const i=s*.22,n=s>0?Math.max(1,Math.floor(i)):Math.min(-1,Math.ceil(i));this._smoothScrollPending=s-n,this.terminal.scrollLines(n),this._maybeLoadMoreHistoryOnScroll(n),Math.abs(this._smoothScrollPending)>=1&&(this._smoothScrollFrame=requestAnimationFrame(t))};this._smoothScrollFrame=requestAnimationFrame(t)},_forwardScrollToApp(e,t,s){this._terminalViewportAtBottom()||this.terminal.scrollToBottom(),this._sendSyntheticSgrWheel(e,t,s)},_hasRecentUserScrollUp(){return typeof this._lastUserScrollUpAt!="number"?!1:performance.now()-this._lastUserScrollUpAt<window.CodemanTerminalInput.USER_SCROLL_STICKY_SUPPRESS_MS},batchTerminalWrite(e){if(this._isLoadingBuffer){this._loadBufferQueue&&this._loadBufferQueue.push(e);return}if(this.writeFrameScheduled||(this._wasAtBottomBeforeWrite=this.isTerminalAtBottom()),(this.activeSessionId?this.sessions.get(this.activeSessionId):null)?.flickerFilterEnabled??!1){if(e.includes("\x1B[2J")||e.includes("\x1B[H\x1B[J")||e.includes("\x1B[H")&&e.includes("\x1B[?25l")){this.flickerFilterActive=!0,this.flickerFilterBuffer+=e,this.flickerFilterTimeout&&clearTimeout(this.flickerFilterTimeout),this.flickerFilterTimeout=setTimeout(()=>{this.flickerFilterTimeout=null,this.flushFlickerBuffer()},SYNC_WAIT_TIMEOUT_MS);return}if(this.flickerFilterActive){this.flickerFilterBuffer+=e;return}}this.pendingWrites.push(e),this._scheduleTerminalWriteFlush()},_scheduleTerminalWriteFlush(){this.writeFrameScheduled||this.pendingWrites.length===0||(this.writeFrameScheduled=!0,this._safeYield(()=>{this.writeFrameScheduled=!1,this.flushPendingWrites()}))},flushFlickerBuffer(){this.flickerFilterBuffer&&(this.pendingWrites.push(this.flickerFilterBuffer),this.flickerFilterBuffer="",this.flickerFilterActive=!1,this._scheduleTerminalWriteFlush())},_flushLocalEchoPending(){const e=this._localEchoOverlay?.pendingText||"";this._localEchoOverlay?.clear(),this._localEchoOverlay?.suppressBufferDetection(),this._flushedOffsets?.delete(this.activeSessionId),this._flushedTexts?.delete(this.activeSessionId),e&&(this._pendingInput+=e)},_updateLocalEchoState(){const e=this.loadAppSettingsFromStorage(),t=this.activeSessionId?this.sessions.get(this.activeSessionId):null,s=e.localEchoEnabled??MobileDetection.isTouchDevice(),i=!!(s&&t);this._localEchoEnabled&&!i&&this._localEchoOverlay?.clear(),this._localEchoEnabled=i,this._localEchoOverlay&&t&&(t.mode==="opencode"?this._localEchoOverlay.setPrompt({type:"custom",offset:3,find:r=>{try{const a=r.buffer.active,l=a.cursorY,d=a.getLine(a.viewportY+l);if(!d)return null;const c=d.translateToString(!0).indexOf("\u2503");return c>=0?{row:l,col:c}:null}catch{return null}}}):t.mode==="shell"||t.mode==="codex"?(this._localEchoOverlay.clear(),this._localEchoEnabled=!1):this._localEchoOverlay.setPrompt({type:"custom",offset:0,find:r=>{try{const a=r.buffer.active;for(let l=r.rows-1;l>=0;l--){const d=a.getLine(a.viewportY+l);if(!d)continue;const c=d.translateToString(!0).lastIndexOf("\u276F");if(c>=0)return{row:l,col:c+2}}return{row:Math.max(0,Math.min(r.rows-1,a.cursorY)),col:Math.max(0,Math.min(r.cols-1,a.cursorX))}}catch{return null}}}));let n="off";t&&s&&(t.mode==="codex"?n="predict":t.mode!=="shell"&&(n="buffer")),this._localEchoPolicy=n,n!=="predict"&&this._predictiveEcho?.clearPredictions()},_predictHookOnData(e){if(!(this._localEchoPolicy!=="predict"||!this._predictiveEcho))try{const t=window.CodemanTerminalInput.classifyPredictInput(e);t==="char"?this._predictiveEcho.predictChar(e):t==="backspace"?this._predictiveEcho.predictBackspace():this._predictiveEcho.clearPredictions()}catch{}},_handleCjkInput(e){if(!this.activeSessionId){_crashDiag.log(`CJK send DROP no-session len=${e.length}`);return}typeof KeyboardAccessoryBar<"u"&&KeyboardAccessoryBar.isCtrlArmed?.()&&(e=KeyboardAccessoryBar.consumeCtrl(e)),this._localEchoPolicy==="predict"&&this._predictiveEcho?.clearPredictions(),_crashDiag.log(`CJK send\u2192${this.activeSessionId.slice(0,8)} len=${e.length}`),this._sendInputAsync(this.activeSessionId,e)},flushPendingWrites(){if(this.pendingWrites.length===0||!this.terminal)return;const e=performance.now(),t=this.pendingWrites.join("");this.pendingWrites=[];const s=t.length;s>16384&&_crashDiag.log(`FLUSH: ${(s/1024).toFixed(0)}KB`);const n=(this.activeSessionId&&this.sessions?this.sessions.get(this.activeSessionId):null)?.mode==="codex"?32768:65536;let r=!1;const a=this._hasRecentUserScrollUp()&&this.terminal.buffer?.active?this.terminal.buffer.active.viewportY:null;s<=n?this.terminal.write(t):(this.terminal.write(t.slice(0,n)),this.pendingWrites.push(t.slice(n)),r=!0,this._scheduleTerminalWriteFlush()),a!==null&&this.terminal.buffer?.active?.viewportY!==a&&typeof this.terminal.scrollToLine=="function"&&this.terminal.scrollToLine(a);const l=r?n:s,d=performance.now()-e;if((d>100||r)&&console.warn(`[CRASH-DIAG] flushPendingWrites: ${d.toFixed(0)}ms, ${(l/1024).toFixed(0)}KB written${r?", rest deferred":""} (total ${(s/1024).toFixed(0)}KB)`),this._wasAtBottomBeforeWrite&&!this._hasRecentUserScrollUp()&&this.terminal.scrollToBottom(),this._localEchoOverlay?.hasPending&&this._localEchoOverlay.rerender(),this._tabCompletionSessionId&&this._tabCompletionSessionId===this.activeSessionId&&this._localEchoOverlay&&!this._localEchoOverlay.pendingText){const m=this._localEchoOverlay,c=this;this.terminal.write("",()=>{if(!c._tabCompletionSessionId)return;m.resetBufferDetection();const h=m.detectBufferText();h?h===c._tabCompletionBaseText?(m.undoDetection(),c._tabCompletionRetries=(c._tabCompletionRetries||0)+1,c._tabCompletionRetries>60&&(c._tabCompletionSessionId=null,c._tabCompletionRetries=0)):(c._tabCompletionSessionId=null,c._tabCompletionRetries=0,c._tabCompletionBaseText=null,c._tabCompletionFallback&&(clearTimeout(c._tabCompletionFallback),c._tabCompletionFallback=null),m.rerender()):(c._tabCompletionRetries=(c._tabCompletionRetries||0)+1,c._tabCompletionRetries>60&&(c._tabCompletionSessionId=null,c._tabCompletionRetries=0))})}},_safeYield(e){let t=!1;const s=()=>{t||(t=!0,e())};requestAnimationFrame(s),setTimeout(s,50),this._workerYield(s)},_workerYield(e){try{if(this._yieldWorker===void 0){const t="onmessage=()=>setTimeout(()=>postMessage(0),0);",s=new Blob([t],{type:"application/javascript"}),i=URL.createObjectURL(s);this._yieldWorker=new Worker(i),URL.revokeObjectURL(i),this._yieldQueue=[],this._yieldWorker.onmessage=()=>{const n=this._yieldQueue.shift();n&&n()}}if(!this._yieldWorker)return;this._yieldQueue.push(e),this._yieldWorker.postMessage(0)}catch{this._yieldWorker=null}},scrollToLastNonEmptyLine(){if(!this.terminal?.buffer?.active){this.terminal?.scrollToBottom?.();return}const e=this.terminal.buffer.active,t=e.baseY+e.length;let s=-1;for(let i=t-1;i>=0;i--)if(e.getLine(i)?.translateToString(!0).trim()){s=i;break}if(s>=0&&typeof this.terminal.scrollToLine=="function"){let i=Math.max(0,s-this.terminal.rows+2);const n=Math.max(0,s);for(;i<n&&!e.getLine(i)?.translateToString(!0).trim();)i++;this.terminal.scrollToLine(i)}else this.terminal.scrollToBottom()},chunkedTerminalWrite(e,t=TERMINAL_CHUNK_SIZE,s){const i=++this._chunkedWriteGen,n=this._beginBufferLoad(s);return new Promise(r=>{if(!e||e.length===0){this._finishBufferLoad(n),r();return}const a=e.replace(DEC_SYNC_STRIP_RE,""),l=()=>{this._chunkedWriteGen===i&&this._finishBufferLoad(n),r()};if(a.length<=t){this.terminal.write(a,l);return}let d=0;const m=performance.now();let c=0;const h=()=>{if(this._chunkedWriteGen!==i){r();return}if(d>=a.length){const f=performance.now()-m;console.log(`[CRASH-DIAG] chunkedTerminalWrite complete: ${a.length} bytes in ${c} chunks, ${f.toFixed(0)}ms total`),this._safeYield(l);return}const C=performance.now(),L=a.slice(d,d+t);this.terminal.write(L);const x=performance.now()-C;c++,x>50&&console.warn(`[CRASH-DIAG] chunk #${c} write took ${x.toFixed(0)}ms (${L.length} bytes at offset ${d})`),d+=t,this._safeYield(h)};this._safeYield(h)})},_beginBufferLoad(e){this._bufferLoadSeq===void 0&&(this._bufferLoadSeq=0);const t=e===void 0?`buffer-${++this._bufferLoadSeq}`:e;return this._bufferLoadOwner=t,this._isLoadingBuffer=!0,this._loadBufferQueue=[],t},_finishBufferLoad(e,t){if(e!==void 0&&this._bufferLoadOwner!==e)return!1;const s=this._loadBufferQueue;if(this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._bufferLoadOwner=null,t?.flushQueued&&s&&s.length)for(const i of s)this.batchTerminalWrite(i);return!0},clearTerminal(){this.terminal.clear()},insertTerminalText(e){!this.activeSessionId||!e||(this._localEchoPolicy==="predict"&&this._predictiveEcho?.clearPredictions(),this._localEchoEnabled&&this._localEchoOverlay&&!this._echoPassthroughSessions?.has(this.activeSessionId)?this._localEchoOverlay.appendText(e):this.sendInput(e).catch(()=>{}),this.terminal?.focus())},clearTerminalInput(){if(this.activeSessionId){if(typeof CjkInput<"u"&&CjkInput.clear(),this._inputFlushTimeout&&(clearTimeout(this._inputFlushTimeout),this._inputFlushTimeout=null),this._pendingInput="",this._localEchoPolicy==="predict"&&this._predictiveEcho?.clearPredictions(),this._localEchoEnabled&&this._localEchoOverlay){const e=this._localEchoOverlay.getFlushed?.()||{count:0,text:""};this._localEchoOverlay.clear(),this._localEchoOverlay.suppressBufferDetection(),this._flushedOffsets?.delete(this.activeSessionId),this._flushedTexts?.delete(this.activeSessionId),e.count>0&&this.sendInput("\x7F".repeat(e.count)).catch(()=>{})}else this.sendInput("").catch(()=>{});this.showToast?.("Input cleared","success"),this.terminal?.focus()}},async restoreTerminalSize(){if(!this.activeSessionId){this.showToast("No active session","warning");return}const e=this.getTerminalDimensions();if(!e){this.showToast("Could not determine terminal size","error");return}try{await this.sendResize(this.activeSessionId,{force:!0}),this.showToast(`Terminal restored to ${e.cols}x${e.rows}`,"success")}catch(t){console.error("Failed to restore terminal size:",t),this.showToast("Failed to restore terminal size","error")}},sendPendingCtrlL(e){},shouldCopyTerminalSelectionFromShortcut(e){if(!e||e.type!=="keydown"||!e.ctrlKey&&!e.metaKey&&!e.altKey)return!1;const s=typeof this.getShortcutRegistry=="function"&&typeof this.matchesShortcutEvent=="function"?this.getShortcutRegistry().find(i=>i.id==="copy-selection"):null;return s?!s.disabled&&this.matchesShortcutEvent(e,s):!e.altKey&&(e.key||"").toLowerCase()==="c"},async copyTerminalSelection(e){const t=e??(this.terminal.hasSelection?.()?this.terminal.getSelection():"");if(!t)return!1;const s=await this._copyText(t);return s?(this.terminal.clearSelection?.(),this.showToast("Copied to clipboard","success")):this.showToast("Failed to copy","error"),this.terminal.focus(),s},async copyTerminal(){try{const e=this.terminal.buffer.active;let t="";for(let s=0;s<e.length;s++){const i=e.getLine(s);i&&(t+=i.translateToString(!0)+`
3
+ `)}await navigator.clipboard.writeText(t.replace(/\n+$/,`
4
+ `)),this.showToast("Copied to clipboard","success")}catch{this.showToast("Failed to copy","error")}},_syncMobileHelperTextareaToCursor(){if(!(!MobileDetection.isTouchDevice()||!this.terminal?.element))try{const e=this.terminal.element,t=this.terminal.element.querySelector(".xterm-cursor"),s=this.terminal.element.querySelector(".xterm-screen");if(!(e instanceof HTMLElement)||!(t instanceof HTMLElement)||!(s instanceof HTMLElement))return;const i=t.getBoundingClientRect(),n=s.getBoundingClientRect();if(!i.width&&!i.height)return;const r=Math.max(0,Math.round(i.left-n.left)),a=Math.max(0,Math.round(i.top-n.top));e.style.setProperty("--xterm-helper-left",`${r}px`),e.style.setProperty("--xterm-helper-top",`${a}px`)}catch{}},_dispatchSyntheticTerminalClick(e,t){const s=this.terminal?.element;if(!s||!Number.isFinite(e)||!Number.isFinite(t))return;const i={bubbles:!0,cancelable:!0,view:window,clientX:e,clientY:t,screenX:e,screenY:t,button:0,detail:1};try{s.dispatchEvent(new MouseEvent("mousedown",{...i,buttons:1})),s.dispatchEvent(new MouseEvent("mouseup",{...i,buttons:0}))}catch{}},_sessionUsesServerMouseStrip(){const e=this.sessions?.get(this.activeSessionId)?.mode||"claude";return e==="claude"||e==="codex"||e==="gemini"},_terminalViewportAtBottom(){const e=this.terminal?.buffer?.active;return!e||e.viewportY>=e.baseY},_clientPointToCell(e,t){if(!this.terminal||!Number.isFinite(e)||!Number.isFinite(t))return null;const s=this.terminal.element?.querySelector(".xterm-screen"),i=this.terminal._core?._renderService?.dimensions?.css?.cell;if(!s||!i?.width||!i?.height)return null;const n=s.getBoundingClientRect(),r=Math.max(1,Math.min(this.terminal.cols,Math.floor((e-n.left)/i.width)+1)),a=Math.max(1,Math.min(this.terminal.rows,Math.floor((t-n.top)/i.height)+1));return{col:r,row:a}},_sendSyntheticSgrTap(e,t){if(!this.activeSessionId||!this._terminalViewportAtBottom())return;const s=this._clientPointToCell(e,t);s&&this._sendInputAsync(this.activeSessionId,`\x1B[<0;${s.col};${s.row}M\x1B[<0;${s.col};${s.row}m`)},_cliVersionAtLeast(e,t){if(typeof e!="string")return!1;const s=e.trim().replace(/^v/,"").split(".").map(Number);if(s.length!==3||s.some(n=>!Number.isFinite(n)))return!1;const i=t.split(".").map(Number);for(let n=0;n<3;n++)if(s[n]!==i[n])return s[n]>i[n];return!0},_wheelScrollLines(e){const t=this._wheelScrollLinesFloat(e);return t?Math.round(t)||(t>0?1:-1):0},_wheelScrollLinesFloat(e){const t=e.shiftKey&&Math.abs(e.deltaX)>Math.abs(e.deltaY)?e.deltaX:e.deltaY;return t?e.deltaMode===1?t:e.deltaMode===2?t*(this.terminal?.rows||24):t/25:0},_shouldForwardWheelToApp(e){if(e.shiftKey||this.loadAppSettingsFromStorage?.()?.terminalWheelLocalScrollback)return!1;const t=this.terminal?.modes?.mouseTrackingMode;if(t&&t!=="none")return!1;const s=this.sessions?.get(this.activeSessionId);return!((s?.mode||"claude")!=="claude"||!this._cliVersionAtLeast(s?.cliVersion,"2.1.187"))},_sendSyntheticSgrWheel(e,t,s){if(!this.activeSessionId||!s)return;const i=this._clientPointToCell(e,t);if(!i)return;const n=s<0?64:65,r=Math.min(Math.abs(s),5);this._queueScrollBytes(`\x1B[<${n};${i.col};${i.row}M`.repeat(r))},_queueScrollBytes(e){if(!e||!this.activeSessionId)return;const t=this._wheelSgrQueue||"";t.length>512||(this._wheelSgrQueue=t+e,!this._wheelSgrFlushTimer&&(this._wheelSgrFlushTimer=setTimeout(()=>this._flushWheelSgrQueue(),40)))},_localScrollbackIsHollow(){if((this.sessions?.get(this.activeSessionId)?.mode||"claude")!=="claude")return!1;const t=this.terminal?.buffer?.active;return!t||t.type==="alternate"?!1:(t.baseY||0)===0},_maybePageCliTranscript(e,t){if(!t||e?.shiftKey||!this.activeSessionId||!this._localScrollbackIsHollow())return!1;this._pageKeySession!==this.activeSessionId&&(this._pageKeySession=this.activeSessionId,this._pageKeyPending=0);const s=window.CodemanTerminalInput,i=Math.max(2,Math.round((this.terminal?.rows||24)*s.PAGE_KEY_SCREEN_FRACTION)),n=(this._pageKeyPending||0)+t,r=Math.trunc(n/i);if(this._pageKeyPending=n-r*i,r){const a=r<0?s.KEY_PAGE_UP:s.KEY_PAGE_DOWN;this._queueScrollBytes(a.repeat(Math.min(Math.abs(r),s.PAGE_KEY_MAX_PER_BATCH)))}return this._logScrollRouting("page-keys"),!0},_logScrollRouting(e){const t=this.activeSessionId||"(none)",s=this.sessions?.get(t),i=!!this.loadAppSettingsFromStorage?.()?.terminalWheelLocalScrollback,n=this.terminal?.modes?.mouseTrackingMode||"none",r=this.terminal?.buffer?.active?.baseY??-1,a=`${e}|${s?.mode}|${s?.cliVersion}|${i}|${n}|${r>0}`;this._scrollRoutingLogged||(this._scrollRoutingLogged=new Map),this._scrollRoutingLogged.get(t)!==a&&(this._scrollRoutingLogged.set(t,a),console.log(`[scroll] ${t} \u2192 ${e} (mode=${s?.mode||"?"}, cliVersion=${s?.cliVersion||"unknown"}, localScrollbackOptOut=${i}, mouseTracking=${n}, localScrollbackRows=${r})`))},_flushWheelSgrQueue(){this._wheelSgrFlushTimer=null;const e=this._wheelSgrQueue;this._wheelSgrQueue="",e&&this.activeSessionId&&this._sendInputEphemeral(this.activeSessionId,e)},_handleDesktopTerminalClick(e){if(!this.terminal||!e?.isTrusted||e.button!==0||e.detail!==1||e.shiftKey||e.altKey||e.ctrlKey||e.metaKey)return;const t=this.terminal.modes?.mouseTrackingMode;t&&t!=="none"||this._sessionUsesServerMouseStrip()&&(this.terminal.hasSelection?.()||this._linkHovered||performance.now()<=(this._trustedTapMouseSuppressUntil||0)||e.target?.closest?.(".xterm-screen")&&this._sendSyntheticSgrTap(e.clientX,e.clientY))},_installMobileTapMouseGuard(){const e=this.terminal?.element;if(!e||e._codemanTapMouseGuardInstalled||typeof MobileDetection<"u"&&MobileDetection.isTouchDevice&&!MobileDetection.isTouchDevice())return;e._codemanTapMouseGuardInstalled=!0;const t=s=>{const i=this._trustedTapMouseSuppressUntil||0;!s.isTrusted||performance.now()>i||(s.preventDefault(),s.stopImmediatePropagation())};e.addEventListener("mousedown",t,!0),e.addEventListener("mouseup",t,!0)},_suppressTrustedTapMouseEvents(){const e=window.CodemanTerminalInput?.TOUCH_COMPAT_MOUSE_SUPPRESS_MS||450;this._trustedTapMouseSuppressUntil=performance.now()+e},increaseFontSize(){const e=this.terminal.options.fontSize||14;this.setFontSize(Math.min(e+2,24))},decreaseFontSize(){const e=this.terminal.options.fontSize||14;this.setFontSize(Math.max(e-2,10))},setFontSize(e){this.terminal.options.fontSize=e,document.getElementById("fontSizeDisplay").textContent=e,this.fitAddon.fit(),localStorage.setItem("codeman-font-size",e),this._localEchoOverlay?.refreshFont(),this._predictiveEcho?.refreshFont()},loadFontSize(){const e=localStorage.getItem("codeman-font-size");if(e){const t=parseInt(e,10);t>=10&&t<=24&&(this.terminal.options.fontSize=t,document.getElementById("fontSizeDisplay").textContent=t)}},getTerminalDimensions(){const s=this.fitAddon?.proposeDimensions();return s?{cols:Math.max(s.cols,40),rows:Math.max(s.rows,10)}:null},async sendResize(e,t={}){this.fitAddon&&this.fitAddon.fit();const s=this.getTerminalDimensions();if(!s)return!1;const i=this._lastResizeDims,n=!i||i.cols!==s.cols||i.rows!==s.rows;this._lastResizeDims={cols:s.cols,rows:s.rows};const r=typeof MobileDetection<"u"&&MobileDetection.getDeviceType?MobileDetection.getDeviceType():window.innerWidth<430?"mobile":window.innerWidth<768?"tablet":"desktop";if(!t.forceHttp&&this._wsReady&&this._wsSessionId===e)try{const l={t:"z",c:s.cols,r:s.rows,v:r};return t.force&&(l.f=!0),this._ws.send(JSON.stringify(l)),n}catch{}const a={...s,viewportType:r};return t.force&&(a.force=!0),await fetch(`/api/sessions/${e}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)}),n},async sendInput(e){!this.activeSessionId||!e||this._sendInputAsync(this.activeSessionId,e,{useMux:!0})},toggleDirInput(){const e=document.querySelector("#dirDisplay").parentElement,t=document.getElementById("dirInput");t.classList.contains("hidden")&&(t.classList.remove("hidden"),e.style.display="none",t.focus())},hideDirInput(){const e=document.querySelector("#dirDisplay").parentElement,t=document.getElementById("dirInput");setTimeout(()=>{t.classList.add("hidden"),e.style.display="";const s=t.value.trim();document.getElementById("dirDisplay").textContent=s||"No directory"},100)},applyTerminalSkin(e){const t={...window.CODEMAN_XTERM_THEMES[e]||window.CODEMAN_XTERM_THEMES["daylight-blue"]},s=window.codemanCurrentSkinIsLight(e)?4.5:1;if(this.terminal){this.terminal.options.minimumContrastRatio=s,this.terminal.options.theme=t,this._localEchoOverlay?.refreshFont(),this._predictiveEcho?.refreshFont();try{this.terminal.refresh(0,this.terminal.rows-1)}catch{}}if(this.teammateTerminals){for(const[,i]of this.teammateTerminals)if(i&&i.terminal){i.terminal.options.minimumContrastRatio=s,i.terminal.options.theme={...t};try{i.terminal.refresh(0,i.terminal.rows-1)}catch{}}}}}),(function(e){const i={session:"Sessions",event:"Events",file:"Files"};function n(r){if(!Number.isFinite(r))return"";const a=new Date(r);return a.toLocaleDateString("en",{month:"short",day:"numeric"})+" "+a.toLocaleTimeString("en",{hour:"2-digit",minute:"2-digit",hour12:!1})}e.CodemanSearch={SEARCH_DEBOUNCE_MS:250,SEARCH_LIMIT:60,SOURCE_LABELS:i,formatSearchTime:n}})(window),Object.assign(CodemanApp.prototype,{initSearchPanel(){const e=document.getElementById("searchInput");if(!e||this._searchPanelWired){this._searchPanelWired&&this._populateSearchCaseFilter();return}this._searchPanelWired=!0,this._searchTypes=new Set(["session","event","file"]),this._searchSecondary={caseLabel:"",status:"",days:""},this._searchDebounceTimer=null,this._searchSeq=0,this._searchLastData=null;const t=document.getElementById("searchClearBtn"),s=document.getElementById("searchResults");e.addEventListener("input",()=>{t&&(t.hidden=e.value.length===0),this._scheduleSearch()}),e.addEventListener("keydown",a=>{a.key==="Escape"&&e.value&&(a.stopPropagation(),this._clearSearch())}),t&&t.addEventListener("click",()=>this._clearSearch()),document.querySelectorAll("#searchFilters .search-filter-chip").forEach(a=>{a.addEventListener("click",()=>{const l=a.dataset.typeFilter;this._searchTypes.has(l)&&this._searchTypes.size===1||(this._searchTypes.has(l)?(this._searchTypes.delete(l),a.classList.remove("active")):(this._searchTypes.add(l),a.classList.add("active")),this._runSearch())})});const i=document.getElementById("searchCaseFilter"),n=document.getElementById("searchStatusFilter"),r=document.getElementById("searchDateFilter");i&&i.addEventListener("change",()=>{this._searchSecondary.caseLabel=i.value,this._renderSearch(this._searchLastData)}),n&&n.addEventListener("change",()=>{this._searchSecondary.status=n.value,this._renderSearch(this._searchLastData)}),r&&r.addEventListener("change",()=>{this._searchSecondary.days=r.value,this._renderSearch(this._searchLastData)}),this._populateSearchCaseFilter(),s&&(s.hidden=!0)},_populateSearchCaseFilter(){const e=document.getElementById("searchCaseFilter");if(!e)return;const t=Array.isArray(this.cases)?this.cases:[],s=Array.from(new Set(t.map(r=>r&&r.name).filter(Boolean))).sort(),i=e.value;e.innerHTML="";const n=document.createElement("option");n.value="",n.textContent="All cases",e.appendChild(n);for(const r of s){const a=document.createElement("option");a.value=r,a.textContent="#"+r,e.appendChild(a)}i&&s.includes(i)&&(e.value=i)},_scheduleSearch(){clearTimeout(this._searchDebounceTimer),this._searchDebounceTimer=setTimeout(()=>this._runSearch(),window.CodemanSearch.SEARCH_DEBOUNCE_MS)},_clearSearch(){const e=document.getElementById("searchInput"),t=document.getElementById("searchClearBtn");e&&(e.value=""),t&&(t.hidden=!0),this._searchLastData=null,this._renderSearch(null)},async _runSearch(){const e=document.getElementById("searchInput");if(!e)return;const t=e.value.trim();if(t.length===0){this._searchLastData=null,this._renderSearch(null);return}const s=Array.from(this._searchTypes),i=new URLSearchParams;i.set("q",t.slice(0,200)),s.length>0&&s.length<3&&i.set("types",s.join(",")),i.set("limit",String(window.CodemanSearch.SEARCH_LIMIT));const n=++this._searchSeq,r=await this._apiJson("/api/search?"+i.toString());n===this._searchSeq&&(r?this._searchLastData=r:this._searchLastData={query:t,groups:[],totalResults:0,truncated:!1,_error:!0},this._renderSearch(this._searchLastData))},_applySecondaryFilters(e){const{caseLabel:t,status:s,days:i}=this._searchSecondary;let n=e;if(t){const r="#"+t;n=n.filter(a=>(a.sessionName||"").includes(r)||a.sessionName===t)}if(s){const r=new Set((this.sessionOrder||[]).concat(Object.keys(this.sessions||{})));n=n.filter(a=>{const l=r.has(a.sessionId);return s==="active"?l:!l})}if(i){const r=Date.now()-Number(i)*24*60*60*1e3;n=n.filter(a=>Number.isFinite(a.timestamp)&&a.timestamp>=r)}return n},_renderSearch(e){const t=document.getElementById("searchResults"),s=document.getElementById("historyHeader")||document.getElementById("historyTitle"),i=document.getElementById("historyList");if(!t)return;const n=!!e;if(s&&(s.style.display=n?"none":""),i&&(i.style.display=n?"none":""),t.innerHTML="",!e){t.hidden=!0;return}if(t.hidden=!1,e._error){const l=document.createElement("div");l.className="search-empty",l.textContent="Search unavailable \u2014 check the query and try again.",t.appendChild(l);return}const r=(e.groups||[]).map(l=>({type:l.type,results:this._applySecondaryFilters(l.results||[])})).filter(l=>l.results.length>0);if(r.reduce((l,d)=>l+d.results.length,0)===0){const l=document.createElement("div");l.className="search-empty",l.textContent='No results for "'+(e.query||"")+'"',t.appendChild(l);return}for(const l of r){const d=document.createElement("div");d.className="search-group-header";const m=document.createElement("span");m.className="search-group-label",m.textContent=window.CodemanSearch.SOURCE_LABELS[l.type]||l.type;const c=document.createElement("span");c.className="search-group-count",c.textContent=String(l.results.length),d.append(m,c),t.appendChild(d);for(const h of l.results)t.appendChild(this._buildSearchResultCard(h))}if(e.truncated){const l=document.createElement("div");l.className="search-truncated",l.textContent="Showing the top matches \u2014 refine your search to narrow results.",t.appendChild(l)}},_buildSearchResultCard(e){const t=document.createElement("div");t.className="search-result-card",t.dataset.type=e.type,t.tabIndex=0,t.setAttribute("role","button");const s=document.createElement("div");s.className="search-result-top";const i=e.jumpTo&&e.jumpTo.kind==="resume-session",n=document.createElement("span");n.className="search-result-badge search-badge-"+e.type+(i?" search-badge-past":""),n.textContent=i?"Resume":(window.CodemanSearch.SOURCE_LABELS[e.type]||e.type).replace(/s$/,"");const r=document.createElement("span");r.className="search-result-name",r.textContent=e.sessionName||e.sessionId||"(session)";const a=document.createElement("span");a.className="search-result-time",a.textContent=window.CodemanSearch.formatSearchTime(e.timestamp),s.append(n,r,a);const l=document.createElement("div");l.className="search-result-snippet",l.textContent=e.snippet||"",t.append(s,l);const d=()=>this._jumpToSearchResult(e);return t.addEventListener("click",d),t.addEventListener("keydown",m=>{(m.key==="Enter"||m.key===" ")&&(m.preventDefault(),d())}),t},_jumpToSearchResult(e){const t=e&&e.jumpTo;if(t){if(t.kind==="resume-session"){this.resumeHistorySession(t.claudeSessionId||t.sessionId,t.workingDir||"",e.sessionName);return}typeof this.hideWelcome=="function"&&this.hideWelcome();try{t.kind==="run-summary"?this.openRunSummary(t.sessionId):t.kind==="file-preview"?this.openFilePreview(t.relativePath||"",t.sessionId,t.targetId||null):this.selectSession(t.sessionId)}catch(s){console.error("[search] jump failed",s)}}}});
Binary file
Binary file
Binary file
@@ -3,7 +3,9 @@
3
3
  *
4
4
  * Registers `GET /api/search?q=&types=&limit=` — a bounded, in-memory search
5
5
  * across three v1 sources, returned in the standard ApiResponse envelope:
6
- * 1. sessions/cases — name, working directory, session id
6
+ * 1. sessions/cases, name, working directory, session id, for LIVE sessions
7
+ * plus the past-session snapshot in `session-history-index.ts` (issue #261:
8
+ * the live map alone made every closed session unfindable by folder name)
7
9
  * 2. run-summary events — event title/details (from the live run-summary trackers)
8
10
  * 3. file paths — per-session attachment history (workspace-relative paths only)
9
11
  *
@@ -1 +1 @@
1
- {"version":3,"file":"search-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/search-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAW1C,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAqFhE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI,CA6C7F"}
1
+ {"version":3,"file":"search-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/search-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAW1C,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AA+GhE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI,CAkD7F"}
@@ -3,7 +3,9 @@
3
3
  *
4
4
  * Registers `GET /api/search?q=&types=&limit=` — a bounded, in-memory search
5
5
  * across three v1 sources, returned in the standard ApiResponse envelope:
6
- * 1. sessions/cases — name, working directory, session id
6
+ * 1. sessions/cases, name, working directory, session id, for LIVE sessions
7
+ * plus the past-session snapshot in `session-history-index.ts` (issue #261:
8
+ * the live map alone made every closed session unfindable by folder name)
7
9
  * 2. run-summary events — event title/details (from the live run-summary trackers)
8
10
  * 3. file paths — per-session attachment history (workspace-relative paths only)
9
11
  *
@@ -24,6 +26,7 @@
24
26
  import { canAccessOwned, getAuthUser, parseBody } from '../route-helpers.js';
25
27
  import { SearchQuerySchema } from '../schemas.js';
26
28
  import { searchSources, } from '../../search-service.js';
29
+ import { ensureHistorySessionIndexFresh, getHistorySessionIndex } from '../session-history-index.js';
27
30
  /**
28
31
  * Per-source harvest caps. These bound how much in-memory data we hand to the
29
32
  * pure core BEFORE it applies its own result caps — they keep the harvest itself
@@ -34,17 +37,24 @@ const MAX_EVENTS_PER_SESSION = 500;
34
37
  /**
35
38
  * Harvest the three source arrays from the live in-memory stores. Reads only
36
39
  * bounded, already-loaded data — no disk I/O, no terminal buffers.
40
+ *
41
+ * Past sessions come from the `session-history-index` snapshot, which is built
42
+ * outside the request path for exactly that reason. Live rows are harvested
43
+ * first and win the dedupe, so a session that is both live and in the snapshot
44
+ * keeps its live jump-to (switch to the tab) instead of a resume.
37
45
  */
38
46
  function harvestSources(ctx, canSee) {
39
47
  const sessions = [];
40
48
  const events = [];
41
49
  const files = [];
50
+ const seenSessionIds = new Set();
42
51
  for (const raw of ctx.sessions.values()) {
43
52
  const s = raw;
44
53
  if (canSee && !canSee(s.owner))
45
54
  continue; // multi-user ownership scope
46
55
  const sessionName = s.name ?? '';
47
56
  const timestamp = s.lastActivityAt ?? s.createdAt ?? 0;
57
+ seenSessionIds.add(s.id);
48
58
  sessions.push({
49
59
  sessionId: s.id,
50
60
  sessionName,
@@ -65,6 +75,25 @@ function harvestSources(ctx, canSee) {
65
75
  });
66
76
  }
67
77
  }
78
+ // Past sessions: the out-of-band snapshot of the unified list. Unscoped on
79
+ // disk, so every row goes through the same ownership check as a live one,
80
+ // host-wide transcript rows carry no owner and are therefore admin-only in
81
+ // multi-user mode, matching GET /api/sessions/unified.
82
+ for (const item of getHistorySessionIndex().items) {
83
+ if (seenSessionIds.has(item.sessionId))
84
+ continue;
85
+ if (canSee && !canSee(item.owner))
86
+ continue;
87
+ seenSessionIds.add(item.sessionId);
88
+ sessions.push({
89
+ sessionId: item.sessionId,
90
+ sessionName: item.name,
91
+ workingDir: item.workingDir,
92
+ timestamp: item.timestamp,
93
+ history: true,
94
+ claudeSessionId: item.claudeSessionId,
95
+ });
96
+ }
68
97
  // Events: from the live run-summary trackers, keyed by session id.
69
98
  for (const [sessionId, tracker] of ctx.runSummaryTrackers) {
70
99
  const session = ctx.sessions.get(sessionId);
@@ -99,6 +128,10 @@ export function registerSearchRoutes(app, ctx) {
99
128
  .map((t) => t.trim())
100
129
  .filter(Boolean))
101
130
  : null;
131
+ // Fire-and-forget: a stale past-session snapshot is rebuilt in the
132
+ // background. This query still answers from whatever is already in memory,
133
+ // which is what keeps the request path free of disk I/O.
134
+ ensureHistorySessionIndexFresh();
102
135
  const sources = harvestSources(ctx, canSee);
103
136
  // Apply the optional source-type filter before searching so excluded
104
137
  // sources never contribute to (or consume budget in) the result set.
@@ -1 +1 @@
1
- {"version":3,"file":"search-routes.js","sourceRoot":"","sources":["../../../src/web/routes/search-routes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"search-routes.js","sourceRoot":"","sources":["../../../src/web/routes/search-routes.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"session-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/session-routes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAqB,MAAM,SAAS,CAAC;AA2F7D,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AA+DjG;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA0C1D;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAmCvE;AAWD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,OAAO,CAiBhF;AAGD,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AA+ND,2FAA2F;AAC3F,wBAAgB,uBAAuB,IAAI,IAAI,CAI9C;AAED,+EAA+E;AAC/E,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AA6DD,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAC/D,IAAI,CAguGN"}
1
+ {"version":3,"file":"session-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/session-routes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAqB,MAAM,SAAS,CAAC;AAiG7D,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AA+DjG;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA0C1D;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAmCvE;AAWD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,OAAO,CAiBhF;AAGD,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AA+ND,2FAA2F;AAC3F,wBAAgB,uBAAuB,IAAI,IAAI,CAI9C;AAED,+EAA+E;AAC/E,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AA6DD,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAC/D,IAAI,CAq4GN"}
@@ -26,6 +26,7 @@ import { imageWatcher } from '../../image-watcher.js';
26
26
  import { convertHeicToJpeg } from '../heic-jpeg-converter.js';
27
27
  import { getLifecycleLog } from '../../session-lifecycle-log.js';
28
28
  import { mergeUnifiedSessions, filterAndPaginate, } from '../../services/unified-session-service.js';
29
+ import { buildHistorySessionIndexItems, setHistoryIndexRefresher, setHistorySessionIndex, } from '../session-history-index.js';
29
30
  import { RunSummaryTracker } from '../../run-summary.js';
30
31
  import { MAX_INPUT_LENGTH, MAX_SESSION_NAME_LENGTH } from '../../config/terminal-limits.js';
31
32
  import { MAX_PASTE_IMAGE_BYTES } from '../../config/buffer-limits.js';
@@ -2731,6 +2732,82 @@ export function registerSessionRoutes(app, ctx) {
2731
2732
  }
2732
2733
  return sawNonCli;
2733
2734
  }
2735
+ /** `<repo>/.claude/worktrees/<name>` — the layout Claude Code's own worktree feature creates. */
2736
+ const CLAUDE_WORKTREE_PATH = /^(.*)\/\.claude\/worktrees\/([^/]+)\/?$/;
2737
+ /**
2738
+ * Recover cwd / branch / worktree from a transcript chunk.
2739
+ *
2740
+ * Claude Code stamps `"cwd"` and `"gitBranch"` on every user/assistant record,
2741
+ * and writes a dedicated `worktree-state` record when the session was started
2742
+ * through its own worktree feature. This reads buffers `scanProjectDir` has
2743
+ * ALREADY loaded, so it costs no extra file I/O.
2744
+ *
2745
+ * Why this matters beyond a label: `decodeProjectKey()` reconstructs a path by
2746
+ * stat-walking the filesystem and falls back to `$HOME` when nothing resolves.
2747
+ * A deleted worktree is the normal end of a worktree's life, so every past
2748
+ * worktree session used to collapse onto `$HOME` (#265). The transcript value
2749
+ * is the literal cwd — non-lossy, and it survives the directory being removed.
2750
+ *
2751
+ * cwd is taken from the FIRST record that carries it (a session's cwd does not
2752
+ * move); gitBranch from the LAST (a branch genuinely changes mid-session, and
2753
+ * the newest value in the scanned chunk is the closest to current).
2754
+ */
2755
+ function extractTranscriptGitInfo(text) {
2756
+ const info = {};
2757
+ let start = 0;
2758
+ while (start < text.length) {
2759
+ const end = text.indexOf('\n', start);
2760
+ const line = end === -1 ? text.slice(start) : text.slice(start, end);
2761
+ start = end === -1 ? text.length : end + 1;
2762
+ // Highest-confidence source: Claude's own worktree record. Names the
2763
+ // worktree explicitly, so it beats anything inferred from the path.
2764
+ if (line.includes('"worktree-state"')) {
2765
+ try {
2766
+ const rec = JSON.parse(line);
2767
+ const ws = rec.worktreeSession;
2768
+ if (ws) {
2769
+ if (typeof ws.worktreeName === 'string')
2770
+ info.worktreeName ||= ws.worktreeName;
2771
+ if (typeof ws.originalCwd === 'string')
2772
+ info.worktreeRepo ||= ws.originalCwd;
2773
+ if (typeof ws.worktreePath === 'string')
2774
+ info.cwd ||= ws.worktreePath;
2775
+ }
2776
+ }
2777
+ catch {
2778
+ // Malformed/truncated line — skip
2779
+ }
2780
+ continue;
2781
+ }
2782
+ if (!line.includes('"cwd"') && !line.includes('"gitBranch"'))
2783
+ continue;
2784
+ if (!line.includes('"type":"user"') && !line.includes('"type":"assistant"'))
2785
+ continue;
2786
+ try {
2787
+ const rec = JSON.parse(line);
2788
+ if (!info.cwd && typeof rec.cwd === 'string' && rec.cwd)
2789
+ info.cwd = rec.cwd;
2790
+ // Last one wins — closest to the session's current branch.
2791
+ if (typeof rec.gitBranch === 'string' && rec.gitBranch)
2792
+ info.gitBranch = rec.gitBranch;
2793
+ }
2794
+ catch {
2795
+ // Malformed/truncated line — skip
2796
+ }
2797
+ }
2798
+ // No explicit worktree record: infer from Claude's own worktree path layout.
2799
+ // A worktree created by hand (`git worktree add` anywhere) has no recoverable
2800
+ // NAME here — it still gets a branch, and the badge degrades to branch-only
2801
+ // rather than guessing.
2802
+ if (!info.worktreeName && info.cwd) {
2803
+ const m = CLAUDE_WORKTREE_PATH.exec(info.cwd);
2804
+ if (m) {
2805
+ info.worktreeName = m[2];
2806
+ info.worktreeRepo ||= m[1];
2807
+ }
2808
+ }
2809
+ return info;
2810
+ }
2734
2811
  /**
2735
2812
  * Extract the text of the LAST user message from a JSONL transcript chunk
2736
2813
  * (COD-145). Mirrors `extractFirstUserPrompt` exactly — same user-message
@@ -3081,14 +3158,33 @@ export function registerSessionRoutes(app, ctx) {
3081
3158
  const entrypoint = headEntrypoint === 'cli' || tailEntrypoint === 'cli' ? 'cli' : (headEntrypoint ?? tailEntrypoint);
3082
3159
  if (entrypoint && isAutomatedEntrypoint(entrypoint))
3083
3160
  continue;
3161
+ // Git/worktree facts from the buffers already read above — no extra I/O.
3162
+ // head first (cwd is stamped near the top; median offset ~1KB), tail as the
3163
+ // fallback for transcripts whose head read failed or came up empty.
3164
+ const headGit = head ? extractTranscriptGitInfo(head) : {};
3165
+ const tailGit = tail ? extractTranscriptGitInfo(tail) : {};
3166
+ const git = {
3167
+ cwd: headGit.cwd ?? tailGit.cwd,
3168
+ // Last-wins within a chunk; across chunks the tail is the newer one.
3169
+ gitBranch: tailGit.gitBranch ?? headGit.gitBranch,
3170
+ worktreeName: headGit.worktreeName ?? tailGit.worktreeName,
3171
+ worktreeRepo: headGit.worktreeRepo ?? tailGit.worktreeRepo,
3172
+ };
3084
3173
  out.push({
3085
3174
  sessionId,
3086
- workingDir,
3175
+ // The transcript's literal cwd beats decodeProjectKey's stat-walked guess,
3176
+ // which silently collapses to $HOME once the directory is gone (#265).
3177
+ // Absent cwd falls back to the old behaviour rather than inventing a path.
3178
+ workingDir: git.cwd ?? workingDir,
3179
+ workingDirExact: git.cwd !== undefined,
3087
3180
  projectKey: projDir,
3088
3181
  sizeBytes: fileStat.size,
3089
3182
  lastModified: fileStat.mtime.toISOString(),
3090
3183
  firstPrompt,
3091
3184
  lastPrompt,
3185
+ gitBranch: git.gitBranch,
3186
+ worktreeName: git.worktreeName,
3187
+ worktreeRepo: git.worktreeRepo,
3092
3188
  });
3093
3189
  }
3094
3190
  return out;
@@ -3145,14 +3241,14 @@ export function registerSessionRoutes(app, ctx) {
3145
3241
  results.sort((a, b) => new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime());
3146
3242
  return { sessions: results.slice(0, 50) };
3147
3243
  });
3148
- // Unified, read-only session list: merges live + persisted + lifecycle +
3149
- // transcript history + mux stats into one de-duplicated, searchable list
3150
- // (COD-121). Pure merge/filter logic lives in unified-session-service.ts.
3151
- app.get('/api/sessions/unified', async (req) => {
3152
- const query = req.query;
3153
- if (ctx.testMode) {
3154
- return { sessions: [], total: 0 };
3155
- }
3244
+ /**
3245
+ * Gather the four read-only views the unified list is merged from, plus mux
3246
+ * stats. This is the expensive half (the lifecycle log and a scan of every
3247
+ * Claude transcript), factored out of the route handler because the
3248
+ * past-session search index rebuilds itself from the very same inputs, off
3249
+ * the request path, see session-history-index.ts.
3250
+ */
3251
+ async function gatherUnifiedInputs() {
3156
3252
  // Live (in-memory) sessions.
3157
3253
  const live = [...ctx.sessions.values()].map((s) => {
3158
3254
  const st = s.toState();
@@ -3220,6 +3316,9 @@ export function registerSessionRoutes(app, ctx) {
3220
3316
  firstPrompt: h.firstPrompt,
3221
3317
  lastPrompt: h.lastPrompt,
3222
3318
  projectKey: h.projectKey,
3319
+ gitBranch: h.gitBranch,
3320
+ worktreeName: h.worktreeName,
3321
+ worktreeRepo: h.worktreeRepo,
3223
3322
  });
3224
3323
  }
3225
3324
  }
@@ -3245,14 +3344,51 @@ export function registerSessionRoutes(app, ctx) {
3245
3344
  catch {
3246
3345
  // Mux stats are optional.
3247
3346
  }
3347
+ return { live, persisted, lifecycle, history, mux };
3348
+ }
3349
+ /**
3350
+ * Publish a merged unified list as the past-session search index (issue #261).
3351
+ * The snapshot is stored UNSCOPED with a per-row owner, so it must only ever be
3352
+ * built from an unscoped merge, `harvestSources()` in search-routes re-applies
3353
+ * the ownership check on read.
3354
+ */
3355
+ function publishHistorySessionIndex(merged) {
3356
+ const ownerById = new Map();
3357
+ const stored = ctx.store.getState().sessions;
3358
+ for (const p of Object.values(stored))
3359
+ ownerById.set(p.id, p.owner);
3360
+ // Live wins: a session's owner on disk can lag the running one.
3361
+ for (const s of ctx.sessions.values())
3362
+ ownerById.set(s.id, s.owner);
3363
+ const liveIds = new Set(ctx.sessions.keys());
3364
+ setHistorySessionIndex(buildHistorySessionIndexItems(merged, ownerById, liveIds));
3365
+ }
3366
+ // Rebuild hook for the search route: it kicks this (fire-and-forget) when the
3367
+ // snapshot goes stale, so a search never pays for the scan itself.
3368
+ setHistoryIndexRefresher(async () => {
3369
+ if (ctx.testMode)
3370
+ return;
3371
+ publishHistorySessionIndex(mergeUnifiedSessions(await gatherUnifiedInputs()));
3372
+ });
3373
+ // Unified, read-only session list: merges live + persisted + lifecycle +
3374
+ // transcript history + mux stats into one de-duplicated, searchable list
3375
+ // (COD-121). Pure merge/filter logic lives in unified-session-service.ts.
3376
+ app.get('/api/sessions/unified', async (req) => {
3377
+ const query = req.query;
3378
+ if (ctx.testMode) {
3379
+ return { sessions: [], total: 0 };
3380
+ }
3381
+ const { live, persisted, lifecycle, history, mux } = await gatherUnifiedInputs();
3248
3382
  // Multi-user: a non-admin only sees their own sessions; host-wide transcript
3249
3383
  // history (not tied to an owned session) is admin-only.
3250
3384
  let sLive = live;
3251
3385
  let sPersisted = persisted;
3252
3386
  let sLifecycle = lifecycle;
3253
3387
  let sHistory = history;
3388
+ let scoped = false;
3254
3389
  const uUser = getAuthUser(req);
3255
3390
  if (isMultiUserMode() && uUser.role !== 'admin') {
3391
+ scoped = true;
3256
3392
  const ownedLive = new Set([...ctx.sessions.values()].filter((s) => canAccessOwned(uUser, s.owner)).map((s) => s.id));
3257
3393
  const stored = ctx.store.getState().sessions;
3258
3394
  const ownedPersisted = new Set(Object.values(stored)
@@ -3271,6 +3407,12 @@ export function registerSessionRoutes(app, ctx) {
3271
3407
  history: sHistory,
3272
3408
  mux,
3273
3409
  });
3410
+ // Refresh the search index off the back of this request, the home screen
3411
+ // fetches this endpoint whenever it opens, which is the same screen the
3412
+ // search box lives on, so the snapshot is warm before anyone types. A scoped
3413
+ // merge is a per-user subset and would corrupt the shared snapshot, so that
3414
+ // path re-merges unscoped instead (multi-user is opt-in and rarely hit).
3415
+ publishHistorySessionIndex(scoped ? mergeUnifiedSessions({ live, persisted, lifecycle, history, mux }) : merged);
3274
3416
  const offset = query.offset !== undefined ? parseInt(query.offset, 10) : undefined;
3275
3417
  const limit = query.limit !== undefined ? parseInt(query.limit, 10) : undefined;
3276
3418
  return filterAndPaginate(merged, {