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.
- package/README.md +2 -1
- package/dist/search-service.d.ts +9 -1
- package/dist/search-service.d.ts.map +1 -1
- package/dist/search-service.js +13 -3
- package/dist/search-service.js.map +1 -1
- package/dist/services/unified-session-service.d.ts +9 -0
- package/dist/services/unified-session-service.d.ts.map +1 -1
- package/dist/services/unified-session-service.js +4 -1
- package/dist/services/unified-session-service.js.map +1 -1
- package/dist/types/search.d.ts +17 -3
- package/dist/types/search.d.ts.map +1 -1
- package/dist/web/public/admin-ui.js +35 -20
- package/dist/web/public/admin-ui.js.br +0 -0
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/{app.4d006245.js → app.f9c6aadd.js} +18 -18
- package/dist/web/public/app.f9c6aadd.js.br +0 -0
- package/dist/web/public/app.f9c6aadd.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/{constants.7d0f4047.js → constants.53dc0fa1.js} +43 -0
- package/dist/web/public/constants.53dc0fa1.js.br +0 -0
- package/dist/web/public/constants.53dc0fa1.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/home-sessions.js +119 -8
- package/dist/web/public/home-sessions.js.br +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/i18n.1a8b3412.js +1 -0
- package/dist/web/public/i18n.1a8b3412.js.br +0 -0
- package/dist/web/public/i18n.1a8b3412.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +807 -727
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
- package/dist/web/public/{keyboard-accessory.1f35b482.js → keyboard-accessory.0f524637.js} +207 -5
- package/dist/web/public/keyboard-accessory.0f524637.js.br +0 -0
- package/dist/web/public/keyboard-accessory.0f524637.js.gz +0 -0
- package/dist/web/public/mobile-handlers.32fdd57f.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.52b30b35.css +1 -0
- package/dist/web/public/mobile.52b30b35.css.br +0 -0
- package/dist/web/public/mobile.52b30b35.css.gz +0 -0
- package/dist/web/public/notification-manager.5d229063.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.3df9cd95.js → panels-ui.54b0d004.js} +1 -1
- package/dist/web/public/panels-ui.54b0d004.js.br +0 -0
- package/dist/web/public/panels-ui.54b0d004.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js +80 -16
- package/dist/web/public/readmymind-ui.js.br +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-ui.f4fed187.js.gz +0 -0
- package/dist/web/public/{settings-ui.1c15649c.js → settings-ui.c674f367.js} +9 -9
- package/dist/web/public/settings-ui.c674f367.js.br +0 -0
- package/dist/web/public/settings-ui.c674f367.js.gz +0 -0
- package/dist/web/public/styles.2eb23e0f.css +1 -0
- package/dist/web/public/styles.2eb23e0f.css.br +0 -0
- package/dist/web/public/styles.2eb23e0f.css.gz +0 -0
- package/dist/web/public/subagent-windows.4cc8b005.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/terminal-ui.61c7829d.js +4 -0
- package/dist/web/public/terminal-ui.61c7829d.js.br +0 -0
- package/dist/web/public/terminal-ui.61c7829d.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.6cec0fba.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.e5461859.js.gz +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/routes/search-routes.d.ts +3 -1
- package/dist/web/routes/search-routes.d.ts.map +1 -1
- package/dist/web/routes/search-routes.js +34 -1
- package/dist/web/routes/search-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +151 -9
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/session-history-index.d.ts +108 -0
- package/dist/web/session-history-index.d.ts.map +1 -0
- package/dist/web/session-history-index.js +118 -0
- package/dist/web/session-history-index.js.map +1 -0
- package/package.json +1 -1
- package/dist/web/public/app.4d006245.js.br +0 -0
- package/dist/web/public/app.4d006245.js.gz +0 -0
- package/dist/web/public/constants.7d0f4047.js.br +0 -0
- package/dist/web/public/constants.7d0f4047.js.gz +0 -0
- package/dist/web/public/i18n.ef50083f.js +0 -1
- package/dist/web/public/i18n.ef50083f.js.br +0 -0
- package/dist/web/public/i18n.ef50083f.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.1f35b482.js.br +0 -0
- package/dist/web/public/keyboard-accessory.1f35b482.js.gz +0 -0
- package/dist/web/public/mobile.5d8c896c.css +0 -1
- package/dist/web/public/mobile.5d8c896c.css.br +0 -0
- package/dist/web/public/mobile.5d8c896c.css.gz +0 -0
- package/dist/web/public/panels-ui.3df9cd95.js.br +0 -0
- package/dist/web/public/panels-ui.3df9cd95.js.gz +0 -0
- package/dist/web/public/settings-ui.1c15649c.js.br +0 -0
- package/dist/web/public/settings-ui.1c15649c.js.gz +0 -0
- package/dist/web/public/styles.0024c22e.css +0 -1
- package/dist/web/public/styles.0024c22e.css.br +0 -0
- package/dist/web/public/styles.0024c22e.css.gz +0 -0
- package/dist/web/public/terminal-ui.e7c5d233.js +0 -4
- package/dist/web/public/terminal-ui.e7c5d233.js.br +0 -0
- 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="×",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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
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
|
|
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
|
|
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
|
|
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":"AAAA
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
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, {
|