aicodeman 1.10.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +2 -0
  2. package/README.zh-CN.md +2 -0
  3. package/dist/config/file-editing.d.ts +39 -0
  4. package/dist/config/file-editing.d.ts.map +1 -0
  5. package/dist/config/file-editing.js +148 -0
  6. package/dist/config/file-editing.js.map +1 -0
  7. package/dist/types/common.d.ts +15 -0
  8. package/dist/types/common.d.ts.map +1 -1
  9. package/dist/web/public/admin-ui.js.gz +0 -0
  10. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  11. package/dist/web/public/{app.8ae6a2ad.js → app.166af5f1.js} +5 -5
  12. package/dist/web/public/app.166af5f1.js.br +0 -0
  13. package/dist/web/public/app.166af5f1.js.gz +0 -0
  14. package/dist/web/public/constants.e7175b95.js.gz +0 -0
  15. package/dist/web/public/cron-ui.js.gz +0 -0
  16. package/dist/web/public/entrance-animations.js.gz +0 -0
  17. package/dist/web/public/i18n.0fbea500.js +1 -0
  18. package/dist/web/public/i18n.0fbea500.js.br +0 -0
  19. package/dist/web/public/i18n.0fbea500.js.gz +0 -0
  20. package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
  21. package/dist/web/public/index.html +16 -7
  22. package/dist/web/public/index.html.br +0 -0
  23. package/dist/web/public/index.html.gz +0 -0
  24. package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
  25. package/dist/web/public/keyboard-accessory.9936227d.js.gz +0 -0
  26. package/dist/web/public/mobile-handlers.bc2b8c1d.js.gz +0 -0
  27. package/dist/web/public/mobile-overview.js.gz +0 -0
  28. package/dist/web/public/{mobile.e56d787a.css → mobile.32cacde5.css} +1 -1
  29. package/dist/web/public/mobile.32cacde5.css.br +0 -0
  30. package/dist/web/public/{mobile.e56d787a.css.gz → mobile.32cacde5.css.gz} +0 -0
  31. package/dist/web/public/notification-manager.5d229063.js.gz +0 -0
  32. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  33. package/dist/web/public/{panels-ui.600881ae.js → panels-ui.1d4203e0.js} +63 -62
  34. package/dist/web/public/panels-ui.1d4203e0.js.br +0 -0
  35. package/dist/web/public/panels-ui.1d4203e0.js.gz +0 -0
  36. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  37. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  38. package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
  39. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  40. package/dist/web/public/session-ui.1356a4f4.js.gz +0 -0
  41. package/dist/web/public/settings-ui.ed9cdb3f.js.gz +0 -0
  42. package/dist/web/public/{styles.7e638f69.css → styles.2e8dc8b7.css} +1 -1
  43. package/dist/web/public/styles.2e8dc8b7.css.br +0 -0
  44. package/dist/web/public/styles.2e8dc8b7.css.gz +0 -0
  45. package/dist/web/public/subagent-windows.4cc8b005.js.gz +0 -0
  46. package/dist/web/public/sw.js.gz +0 -0
  47. package/dist/web/public/terminal-ui.3ac6f8c2.js +3 -0
  48. package/dist/web/public/terminal-ui.3ac6f8c2.js.br +0 -0
  49. package/dist/web/public/terminal-ui.3ac6f8c2.js.gz +0 -0
  50. package/dist/web/public/ultracode-panel.js.gz +0 -0
  51. package/dist/web/public/ultracode-windows.js.gz +0 -0
  52. package/dist/web/public/upload.html.gz +0 -0
  53. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  54. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  55. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  56. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  57. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  58. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  59. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  60. package/dist/web/public/vendor/xterm.css.gz +0 -0
  61. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  62. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  63. package/dist/web/public/webview-tabs.js.gz +0 -0
  64. package/dist/web/routes/file-routes.d.ts +4 -1
  65. package/dist/web/routes/file-routes.d.ts.map +1 -1
  66. package/dist/web/routes/file-routes.js +215 -5
  67. package/dist/web/routes/file-routes.js.map +1 -1
  68. package/dist/web/schemas.d.ts +18 -0
  69. package/dist/web/schemas.d.ts.map +1 -1
  70. package/dist/web/schemas.js +24 -0
  71. package/dist/web/schemas.js.map +1 -1
  72. package/package.json +1 -1
  73. package/dist/web/public/app.8ae6a2ad.js.br +0 -0
  74. package/dist/web/public/app.8ae6a2ad.js.gz +0 -0
  75. package/dist/web/public/i18n.8a202f9d.js +0 -1
  76. package/dist/web/public/i18n.8a202f9d.js.br +0 -0
  77. package/dist/web/public/i18n.8a202f9d.js.gz +0 -0
  78. package/dist/web/public/mobile.e56d787a.css.br +0 -0
  79. package/dist/web/public/panels-ui.600881ae.js.br +0 -0
  80. package/dist/web/public/panels-ui.600881ae.js.gz +0 -0
  81. package/dist/web/public/styles.7e638f69.css.br +0 -0
  82. package/dist/web/public/styles.7e638f69.css.gz +0 -0
  83. package/dist/web/public/terminal-ui.07ec3b0b.js +0 -3
  84. package/dist/web/public/terminal-ui.07ec3b0b.js.br +0 -0
  85. package/dist/web/public/terminal-ui.07ec3b0b.js.gz +0 -0
Binary file
@@ -0,0 +1,3 @@
1
+ "use strict";(function(e){const t=/^\x1b\[[\?>=]?[\d;]*[cnR]$/,s=/^\x1b\][\d;]*[^\x07\x1b]*(?:\x07|\x1b\\)$/;function a(_){return t.test(_)||s.test(_)}function n(_){return a(_)}const l={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"}},h=new Set(["paper-gray","solarized-light","catppuccin-latte","rose-pine-dawn"]);function u(){return typeof document<"u"&&document.documentElement.dataset.skin||"daylight-blue"}function c(){const _=u();return l[_]||l["daylight-blue"]}function m(_=u()){return h.has(_)}e.CodemanTerminalInput={isTerminalQueryResponse:a,shouldSuppressTerminalQueryResponse:n,USER_SCROLL_STICKY_SUPPRESS_MS:1500,TOUCH_COMPAT_MOUSE_SUPPRESS_MS:450},e.CODEMAN_XTERM_THEMES=l,e.codemanCurrentXtermTheme=c,e.codemanCurrentSkinIsLight=m})(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 r=new Unicode11Addon.Unicode11Addon;this.terminal.loadAddon(r),this.terminal.unicode.activeVersion="11"}catch{}const s=document.getElementById("terminalContainer");this.terminal.open(s),this._installMobileTapMouseGuard(),this.terminal.attachCustomKeyEventHandler(r=>{if(r.isComposing||r.keyCode===229||r.altKey&&!r.ctrlKey&&!r.shiftKey&&/^(Digit[1-9]|BracketLeft|BracketRight|KeyK)$/.test(r.code||"")||r.type==="keydown"&&this.shouldOpenCommandPaletteFromShortcut?.(r))return!1;if(this.shouldCopyTerminalSelectionFromShortcut?.(r)){const f=this.terminal.hasSelection?.()?this.terminal.getSelection():"";return f?(r.preventDefault(),this.copyTerminalSelection(f),!1):r.shiftKey?(r.preventDefault(),!1):!0}if((r.ctrlKey||r.metaKey)&&r.key==="v"&&r.type==="keydown")return this.activeSessionId&&this._handleImagePaste&&this._handleImagePaste(),!1;if(r.key==="Enter"&&(r.shiftKey||r.ctrlKey)&&r.type==="keydown"){if(this.activeSessionId)if(this._localEchoEnabled){const f=this._localEchoOverlay?.pendingText||"";this._localEchoOverlay?.clear(),this._localEchoOverlay?.suppressBufferDetection(),this._flushedOffsets?.delete(this.activeSessionId),this._flushedTexts?.delete(this.activeSessionId),f&&(this._pendingInput+=f,w()),setTimeout(()=>{fetch(`/api/sessions/${this.activeSessionId}/send-key`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({key:r.ctrlKey?"C-Enter":"S-Enter"})})},f?80:0)}else fetch(`/api/sessions/${this.activeSessionId}/send-key`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({key:r.ctrlKey?"C-Enter":"S-Enter"})});return!1}return!0});{const r=s.querySelector(".xterm-helper-textarea");if(r&&MobileDetection.isTouchDevice()){let f=!1,d=0;r.addEventListener("compositionstart",()=>{f=!0}),r.addEventListener("compositionend",()=>{f=!1}),r.addEventListener("keydown",g=>{!g.isComposing&&g.keyCode!==229&&(d=Date.now())}),r.addEventListener("input",g=>{if(f||g.isComposing||g.inputType!=="insertText"||!g.data||Date.now()-d<50)return;const b=g.data,T=this._localEchoOverlay?.pendingText||"";Promise.resolve().then(()=>{if(this._lastTerminalData?.data===b&&performance.now()-this._lastTerminalData.time<100){r.value="";return}const v=this._localEchoOverlay?.pendingText||"";if(this._localEchoEnabled&&v.length>T.length&&v.endsWith(b)){r.value="";return}const p=r.value;!p||p.trim()===""&&b!==" "||(this.terminal._core.coreService.triggerDataEvent(b,!0),r.value="")})})}}this._webglAddon=null;const i=new URLSearchParams(location.search),o=(()=>{try{const r=localStorage.getItem("codeman-webgl-disabled");if(!r)return!1;const{at:f}=JSON.parse(r);return Date.now()-f>WEBGL_FALLBACK.STICKY_EXPIRY_MS?(localStorage.removeItem("codeman-webgl-disabled"),!1):!0}catch{return!1}})(),a=this.loadAppSettingsFromStorage(),n=this.getDefaultSettings(),l=a.webglRendererEnabled??n.webglRendererEnabled,{skip:h,clearSticky:u}=shouldSkipWebGL({deviceType:MobileDetection.getDeviceType(),noWebglParam:i.has("nowebgl"),forceParam:i.get("webgl")==="force",stickyDisabled:o,userPrefEnabled:l});if(u)try{localStorage.removeItem("codeman-webgl-disabled")}catch{}if(h&&o&&console.log("[CRASH-DIAG] WebGL sticky-disabled from prior stalls \u2014 DOM renderer in use. Re-enable: ?webgl=force"),!h)if(typeof WebglAddon<"u")this._initWebGL();else{const r=document.createElement("script");r.src="vendor/xterm-addon-webgl.min.js",r.onload=()=>this._initWebGL(),r.onerror=()=>console.warn("[CRASH-DIAG] Failed to load WebGL addon \u2014 using canvas renderer"),document.head.appendChild(r)}this._localEchoOverlay=new LocalEchoOverlay(this.terminal),MobileDetection.isTouchDevice()&&(this.terminal.onCursorMove(()=>this._syncMobileHelperTextareaToCursor()),this.terminal.onRender(()=>this._syncMobileHelperTextareaToCursor())),this._cjkInput=null,typeof CjkInput<"u"&&(this._cjkInput=CjkInput.init({send:r=>{this._handleCjkInput(r)}}));const c=this.terminal.focus.bind(this.terminal);this.terminal.focus=()=>{const r=document.getElementById("cjkInput");r?.classList.contains("cjk-input-visible")?r.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",r=>{r.preventDefault();const f=this._wheelScrollLines(r);if(this._shouldForwardWheelToApp(r)){this._sendSyntheticSgrWheel(r.clientX,r.clientY,f);return}this._noteTerminalUserScroll(f),this.terminal.scrollLines(f)},{passive:!1});{const r=()=>this.terminal._core?._renderService?.dimensions?.css?.cell?.height||13;let f=0,d=0,g=0,b=null,T=!1;const v=I=>{const k=g?(I-g)/16.67:1;if(g=I,!T&&Math.abs(d)>.3){const A=Math.round(d/r());A!==0&&this.terminal.scrollLines(A),d*=.92,b=requestAnimationFrame(v)}else T?b=requestAnimationFrame(v):(b=null,d=0)};let p=0,S=!1,C=0;const M=8;s.addEventListener("touchstart",I=>{I.touches.length===1&&(f=I.touches[0].clientY,C=f,d=0,p=0,T=!0,S=!1,g=0,b&&(cancelAnimationFrame(b),b=null))},{passive:!0}),s.addEventListener("touchmove",I=>{if(I.touches.length===1&&T){const k=I.touches[0].clientY;if(!S&&Math.abs(k-C)>=M&&(S=!0),!S)return;I.preventDefault();const A=f-k;p+=A,d=A*1.2,f=k;const y=r(),L=Math.trunc(p/y);L!==0&&(this._noteTerminalUserScroll(L),this.terminal.scrollLines(L),p-=L*y)}},{passive:!1}),s.addEventListener("touchend",I=>{if(T=!1,!b&&Math.abs(d)>.3&&(b=requestAnimationFrame(v)),!S&&this.terminal){const k=I.changedTouches&&I.changedTouches[0],A=this.terminal.modes?.mouseTrackingMode,y=!!A&&A!=="none";k&&this._suppressTrustedTapMouseEvents(),k&&y?this._dispatchSyntheticTerminalClick(k.clientX,k.clientY):k&&this._sessionUsesServerMouseStrip()&&this._sendSyntheticSgrTap(k.clientX,k.clientY),this._syncMobileHelperTextareaToCursor();const L=document.getElementById("cjkInput");L?.classList.contains("cjk-input-visible")?L.focus():this.terminal.focus()}},{passive:!0}),s.addEventListener("touchcancel",()=>{T=!1,d=0,p=0},{passive:!0})}s.addEventListener("click",r=>this._handleDesktopTerminalClick(r)),this.showWelcome(),this.initImageInput(),this._chunkedWriteGen=0,this._bufferLoadSeq=0,this._bufferLoadOwner=null,this._lastUserScrollUpAt=null,this._resizeTimeout=null,this._lastResizeDims=null;const _=40,E=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 r=typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible;if(this.activeSessionId&&!r){const f=this.fitAddon.proposeDimensions(),d=f?Math.max(f.cols,_):_,g=f?Math.max(f.rows,E):E;if(!this._lastResizeDims||d!==this._lastResizeDims.cols||g!==this._lastResizeDims.rows){const b=this.activeSessionId?this.sessions.get(this.activeSessionId):null;b&&b.mode!=="shell"&&this.terminal&&this.isTerminalAtBottom()&&this.terminal.write("\x1B[3J\x1B[H\x1B[2J"),this._lastResizeDims={cols:d,rows:g};const T=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:d,r:g,v:T})),v=!0}catch{}v||fetch(`/api/sessions/${this.activeSessionId}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({cols:d,rows:g,viewportType:T})}).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 w=()=>{if(this._inputFlushTimeout=null,this._pendingInput&&this.activeSessionId){const r=this._pendingInput,f=this.activeSessionId;this._pendingInput="",this._sendInputAsync(f,r)}};this.terminal.onData(r=>{if(!/^\x1b\[<\d+;\d+;\d+[Mm]$/.test(r)&&(window.cjkActive||document.activeElement?.id==="cjkInput")){const d=document.getElementById("cjkInput");d?.classList.contains("cjk-input-visible")&&document.activeElement===this.terminal.textarea&&!window.CodemanTerminalInput?.shouldSuppressTerminalQueryResponse(r)&&(_crashDiag.log("CJK regain-focus (onData swallowed input)"),d.focus());return}if(this.activeSessionId){if(window.CodemanTerminalInput?.shouldSuppressTerminalQueryResponse(r))return;if(this._lastTerminalData={data:r,time:performance.now()},this._localEchoEnabled){if(r==="\x7F"){if(this._localEchoOverlay?.removeChar()==="flushed"){const{count:b,text:T}=this._localEchoOverlay.getFlushed();this._flushedOffsets?.has(this.activeSessionId)&&(b===0?(this._flushedOffsets.delete(this.activeSessionId),this._flushedTexts?.delete(this.activeSessionId)):(this._flushedOffsets.set(this.activeSessionId,b),this._flushedTexts?.set(this.activeSessionId,T))),this._pendingInput+=r,w()}return}if(/^[\r\n]+$/.test(r)){const g=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),g&&(this._pendingInput+=g,w()),setTimeout(()=>{this._pendingInput+="\r",w()},80);return}if(r.length>1&&r.charCodeAt(0)>=32){this._localEchoOverlay?.appendText(r);return}if(r.charCodeAt(0)<32){if(r.length>1&&r.charCodeAt(0)===27){this._pendingInput+=r,w();return}if(this._restoringFlushedState){this._pendingInput+=r,w();return}if(r===" "){const b=this._localEchoOverlay?.pendingText||"";this._localEchoOverlay?.clear(),this._flushedOffsets?.delete(this.activeSessionId),this._flushedTexts?.delete(this.activeSessionId),b&&(this._pendingInput+=b),this._pendingInput+=r,this._inputFlushTimeout&&(clearTimeout(this._inputFlushTimeout),this._inputFlushTimeout=null);let T="";try{const p=this._localEchoOverlay?.findPrompt?.();if(p){const S=this.terminal.buffer.active,C=S.getLine(S.viewportY+p.row);C&&(T=C.translateToString(!0).slice(p.col+2).trimEnd())}}catch{}this._tabCompletionBaseText=T,w(),this._tabCompletionSessionId=this.activeSessionId,this._tabCompletionRetries=0,this._tabCompletionFallback&&clearTimeout(this._tabCompletionFallback);const v=this;this._tabCompletionFallback=setTimeout(()=>{if(v._tabCompletionFallback=null,!v._tabCompletionSessionId||v._tabCompletionSessionId!==v.activeSessionId)return;const p=v._localEchoOverlay;!p||p.pendingText||v.terminal.write("",()=>{if(!v._tabCompletionSessionId)return;p.resetBufferDetection();const S=p.detectBufferText();S&&S!==v._tabCompletionBaseText&&(v._tabCompletionSessionId=null,v._tabCompletionRetries=0,v._tabCompletionBaseText=null,p.rerender())})},300);return}const g=this._localEchoOverlay?.pendingText||"";this._localEchoOverlay?.clear(),this._localEchoOverlay?.suppressBufferDetection(),this._flushedOffsets?.delete(this.activeSessionId),this._flushedTexts?.delete(this.activeSessionId),g&&(this._pendingInput+=g),this._pendingInput+=r,this._inputFlushTimeout&&(clearTimeout(this._inputFlushTimeout),this._inputFlushTimeout=null),w();return}if(r.length===1&&r.charCodeAt(0)>=32){this._localEchoOverlay?.addChar(r);return}}if(this._pendingInput+=r,r.charCodeAt(0)<32||r.length>1){this._inputFlushTimeout&&(clearTimeout(this._inputFlushTimeout),this._inputFlushTimeout=null),w();return}const d=performance.now();d-this._lastKeystrokeTime>50?(this._inputFlushTimeout&&(clearTimeout(this._inputFlushTimeout),this._inputFlushTimeout=null),this._lastKeystrokeTime=d,w()):(this._lastKeystrokeTime=d,this._inputFlushTimeout||(this._inputFlushTimeout=setTimeout(w,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 o=e.terminal.buffer.active;if(!o.getLine(s-1)){i(void 0);return}const n=e.terminal.cols,l=S=>o.getLine(S-1),h=S=>{if(S<=1)return!1;if(l(S)?.isWrapped)return!0;const C=l(S-1);return!!C&&C.translateToString(!0).length>=n},u=12;let c=s;for(;c>1&&s-c<u&&h(c);)c--;let m=s;for(;m<o.length&&m-c<u&&h(m+1);)m++;const _=[];for(let S=c;S<=m;S++){const C=l(S);if(!C)break;_.push(C.translateToString(S===m))}const E=_.join(""),x=S=>{let C=S;for(let M=0;M<_.length-1;M++){if(C<_[M].length)return{x:C+1,y:c+M};C-=_[M].length}return{x:C+1,y:c+_.length-1}};if(!E||!E.includes("/")){i(void 0);return}const w=[],r=/https?:\/\/(?:[^\s"'<>|;&)\]\x00-\x1f]|&(?!&))+/g,f=(S,C)=>{const M=S.replace(/[.,;:!?)&]+$/,""),I=E.indexOf(M,C);if(I===-1)return;const k=x(I),A=x(I+M.length);w.some(y=>y.range.start.x===k.x&&y.range.start.y===k.y)||w.push({text:M,range:{start:k,end:A},decorations:{pointerCursor:!0,underline:!0},activate(y,L){window.open(L,"_blank","noopener,noreferrer")},hover(){e._linkHovered=!0},leave(){e._linkHovered=!1}})},d=/\b(tail|cat|head|less|grep|watch|vim|nano)\s+(?:[^\s\/]+\s+){0,4}(\/[^\s"'<>|;&\n\x00-\x1f]+)/g,g=/(\/(?: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,b=/Bash\([^)]*?(\/(?:home|tmp|var|etc|opt)[^\s"'<>|;&\)\n\x00-\x1f]+)/g,T=new Set(["png","jpg","jpeg","gif","webp","bmp","svg","pdf"]),v=(S,C)=>{const M=E.indexOf(S,C);if(M===-1)return;const I=x(M),k=x(M+S.length);w.some(A=>A.range.start.x===I.x&&A.range.start.y===I.y)||w.push({text:S,range:{start:I,end:k},decorations:{pointerCursor:!0,underline:!0},activate(A,y){const L=(y.split(".").pop()||"").toLowerCase();if(T.has(L)){e.openFilePreview(y,e.activeSessionId);return}e.openLogViewerWindow(y,e.activeSessionId)},hover(){e._linkHovered=!0},leave(){e._linkHovered=!1}})};let p;for(r.lastIndex=0;(p=r.exec(E))!==null;)f(p[0],p.index);for(d.lastIndex=0;(p=d.exec(E))!==null;)v(p[2],p.index);for(g.lastIndex=0;(p=g.exec(E))!==null;)v(p[1],p.index);for(b.lastIndex=0;(p=b.exec(E))!==null;)v(p[1],p.index);w.length>0&&console.debug("[LinkProvider] Found links:",w.map(S=>S.text)),i(w.length>0?w: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.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._updateCjkInputState?.()},hideWelcome(){this.hideMobileOverview?.();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 a of s){const n=a.projectKey||a.workingDir;i.has(n)||i.set(n,[]),i.get(n).push(a)}const o=[];for(const[,a]of i)o.push(...a.slice(0,3));return o.sort((a,n)=>new Date(n.lastModified)-new Date(a.lastModified)),o},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||[]},_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 o=i.path.length;(!s||o>s.len)&&(s={name:i.name,suffix:e.slice(o),len:o})}}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,o=typeof e.sizeBytes=="number",a=o?e.sizeBytes<1024?`${e.sizeBytes}B`:e.sizeBytes<1048576?`${(e.sizeBytes/1024).toFixed(0)}K`:`${(e.sizeBytes/1048576).toFixed(1)}M`:"",n=typeof e.lastActivityAt=="number"?e.lastActivityAt:e.lastModified?Date.parse(e.lastModified):NaN;let l="";if(!Number.isNaN(n)){const y=new Date(n);l=y.toLocaleDateString("en",{month:"short",day:"numeric"})+" "+y.toLocaleTimeString("en",{hour:"2-digit",minute:"2-digit",hour12:!1})}const h=this._shortenHomePath(e.workingDir),u=this._resolveCaseLabel(e.workingDir,t),c=Array.isArray(e.sources)&&e.sources.includes("live"),m=e.pinned===!0,_=document.createElement("div");_.className="history-item"+(m?" is-pinned":""),_.title=e.workingDir||"";const E=document.createElement("div");E.className="history-item-main",E.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 w=document.createElement("span");if(w.className="history-item-title",m){const y=document.createElement("span");y.className="history-item-pin",y.textContent="\u{1F4CC}",y.setAttribute("aria-label","Pinned"),y.title="Pinned",w.appendChild(y)}w.appendChild(document.createTextNode(e.name||e.firstPrompt||h));const r=document.createElement("div");if(r.className="history-item-badges",e.mode){const y=document.createElement("span");y.className="history-item-badge history-item-badge-mode",y.textContent=e.mode,r.appendChild(y)}if(c){const y=document.createElement("span");y.className="history-item-badge history-item-badge-live",y.textContent="LIVE",r.appendChild(y)}const f=document.createElement("span");f.className="history-item-subtitle",u.startsWith("#")&&f.classList.add("is-case"),f.textContent=u,x.append(w),r.childElementCount>0&&x.append(r),x.append(f);const d=document.createElement("span");d.className="history-item-meta",d.textContent=l;const g=document.createElement("button");g.className="history-item-expand",g.type="button",g.setAttribute("aria-haspopup","menu"),g.setAttribute("aria-label","Session actions"),g.setAttribute("aria-expanded","false"),g.textContent="\u22EF",E.append(x,d,g);const b=document.createElement("div");b.className="history-item-detail",b.hidden=!0;const T=document.createElement("div");T.className="history-detail-row";const v=document.createElement("span");v.className="history-detail-label",v.textContent="Prompt";const p=document.createElement("span");p.className="history-detail-value history-detail-prompt",p.textContent=e.firstPrompt||"(no prompt captured)",T.append(v,p);let S=null;if(e.lastPrompt&&e.lastPrompt!==e.firstPrompt){S=document.createElement("div"),S.className="history-detail-row";const y=document.createElement("span");y.className="history-detail-label",y.textContent="Last prompt";const L=document.createElement("span");L.className="history-detail-value history-detail-prompt",L.textContent=e.lastPrompt,S.append(y,L)}const C=document.createElement("div");C.className="history-detail-row";const M=document.createElement("span");M.className="history-detail-label",M.textContent="Path";const I=document.createElement("span");I.className="history-detail-value history-detail-path",I.textContent=h,C.append(M,I);const k=document.createElement("div");k.className="history-detail-row history-detail-meta";const A=[];if(l&&A.push(l),o&&A.push(a),A.push(e.sessionId.slice(0,8)),k.textContent=A.join(" \xB7 "),b.append(T),S&&b.append(S),b.append(C,k),i&&e.projectKey){const y=document.createElement("div");y.className="history-detail-row history-detail-actions";const L=document.createElement("button");L.type="button",L.className="history-view-all-btn",L.textContent="View all in this folder",L.addEventListener("click",R=>{R.stopPropagation(),this.openFolderHistoryModal(e.projectKey,e.workingDir,t)}),y.appendChild(L),b.appendChild(y)}return g.addEventListener("click",y=>{y.stopPropagation(),y.preventDefault(),this._openSessionRowMenu(y.currentTarget,e,t,_,b)}),_.append(E,b),_},_openSessionRowMenu(e,t,s,i,o){if(this._openRowMenuClose)try{this._openRowMenuClose()}catch{}const a=Array.isArray(t.sources)&&t.sources.includes("live")&&this.sessions.has(t.sessionId),n=document.createElement("div");n.className="session-row-menu",n.setAttribute("role","menu");let l=null,h=null,u=null;const c=()=>{document.removeEventListener("mousedown",l,!0),document.removeEventListener("keydown",h,!0),window.removeEventListener("scroll",u,!0),window.removeEventListener("resize",u,!0);try{n.remove()}catch{}this._openRowMenuEl===n&&(this._openRowMenuEl=null,this._openRowMenuClose=null)},m=(d,g,b)=>{const T=document.createElement("button");T.type="button",T.className="session-row-menu-item",T.setAttribute("role","menuitem");const v=document.createElement("span");if(v.className="session-row-menu-label",v.textContent=d,T.appendChild(v),b&&b.sublabel){const p=document.createElement("span");p.className="session-row-menu-sublabel",p.textContent=b.sublabel,T.appendChild(p)}T.addEventListener("click",async p=>{p.stopPropagation(),p.preventDefault(),await g()}),n.appendChild(T)};m(a?"Switch to session":"Resume session",()=>{a?this.selectSession(t.sessionId):this.resumeHistorySession(t.claudeSessionId||t.sessionId,t.workingDir||"",t.name),this.closeSessionManager?.(),c()});const _=t.pinned===!0;m(_?"Unpin session":"Pin to top",async()=>{await this._setSessionPinned(t.sessionId,!_)?(t.pinned=!_,this.showToast(_?"Unpinned":"Pinned to top","success")):this.showToast("Pin failed","error"),c()}),a&&m("Open folder",()=>{this.selectSession(t.sessionId),this.loadFileBrowser?.(t.sessionId),this.closeSessionManager?.(),c()}),t.workingDir&&m("Copy path",async()=>{const d=await this._copyText(t.workingDir);this.showToast(d?"Path copied":"Copy failed",d?"success":"error"),c()}),m("Show details",()=>{const d=i.classList.toggle("expanded");o.hidden=!d,e.setAttribute("aria-expanded",d?"true":"false"),c()}),document.body.appendChild(n);const E=e.getBoundingClientRect(),x=n.getBoundingClientRect(),w=4;let r=E.bottom+w;r+x.height>window.innerHeight&&E.top-w-x.height>=0&&(r=E.top-w-x.height);let f=E.right-x.width;f<w&&(f=w),f+x.width>window.innerWidth-w&&(f=Math.max(w,window.innerWidth-w-x.width)),n.style.top=`${Math.max(w,r)}px`,n.style.left=`${f}px`,l=d=>{n.contains(d.target)||e.contains(d.target)||c()},h=d=>{d.key==="Escape"&&(d.stopPropagation(),c())},u=()=>c(),document.addEventListener("mousedown",l,!0),document.addEventListener("keydown",h,!0),window.addEventListener("scroll",u,!0),window.addEventListener("resize",u,!0),this._openRowMenuEl=n,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:4,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(n=>n.ok?n.json():null).then(n=>n?.data||[]).catch(()=>[]),[i,o]=await Promise.all([this._fetchUnifiedSessions(60),s]);if(i.length===0){e.style.display="none";return}t.replaceChildren();const a=this._HISTORY_INITIAL_COUNT;for(let n=0;n<Math.min(a,i.length);n++)t.appendChild(this._buildHistoryItem(i[n],o));if(i.length>a){const n=document.createElement("button");n.className="history-show-more",n.textContent=`Show ${i.length-a} more`,n.addEventListener("click",()=>{for(let l=a;l<i.length;l++)t.insertBefore(this._buildHistoryItem(i[l],o),n);n.remove()}),t.appendChild(n)}e.style.display=""}catch(s){console.error("[loadHistorySessions]",s),e.style.display="none"}},_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 o=document.createElement("div");o.className="modal-backdrop",o.addEventListener("click",()=>this._closeFolderHistoryModal());const a=document.createElement("div");a.className="modal-content modal-lg";const n=document.createElement("div");n.className="modal-header";const l=document.createElement("h3");l.textContent="Folder History";const h=document.createElement("div");h.className="folder-history-subtitle",h.textContent=this._shortenHomePath(t);const u=document.createElement("button");u.className="modal-close",u.setAttribute("aria-label","Close"),u.innerHTML="&times;",u.addEventListener("click",()=>this._closeFolderHistoryModal()),n.append(l,u);const c=document.createElement("div");c.className="modal-body";const m=document.createElement("div");m.className="folder-history-list",m.setAttribute("data-loading","true"),m.textContent="Loading...",c.append(h,m),a.append(n,c),i.append(o,a),document.body.appendChild(i),this._folderHistoryState={projectKey:e,workingDir:t,cases:s||[],offset:0,total:null,list:m},this._folderHistoryEscHandler=_=>{_.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,o=this._FOLDER_HISTORY_PAGE_SIZE,a=e.offset,n=i.querySelector(".folder-history-more");n&&n.remove(),a===0&&(i.replaceChildren(),i.removeAttribute("data-loading"));try{const l=`/api/history/sessions?projectKey=${encodeURIComponent(t)}&offset=${a}&limit=${o}`,u=await(await fetch(l)).json(),c=u.data?.sessions||[];if(e.total=typeof u.data?.total=="number"?u.data.total:c.length+a,a===0&&c.length===0){const m=document.createElement("div");m.className="folder-history-empty",m.textContent="No conversations found in this folder.",i.appendChild(m);return}for(const m of c)i.appendChild(this._buildHistoryItem(m,s,{showViewAll:!1}));if(e.offset=a+c.length,e.offset<e.total){const m=e.total-e.offset,_=document.createElement("button");_.className="history-show-more folder-history-more",_.textContent=`Show ${Math.min(o,m)} more (${m} remaining)`,_.addEventListener("click",()=>this._loadFolderHistoryPage()),i.appendChild(_)}}catch(l){console.error("[loadFolderHistoryPage]",l);const h=document.createElement("div");h.className="folder-history-empty",h.textContent="Failed to load folder history.",i.appendChild(h)}},_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[,o]of this.sessions){const a=o.name&&o.name.match(/^w(\d+)-/);if(a){const n=parseInt(a[1]);n>=i&&(i=n+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),a=(this.cases||[]).find(_=>_.path===t)?.name||t.split("/").pop()||"",n=this.loadAppSettingsFromStorage(),l=this.buildEnvOverrides(this.getCaseSettings(a),n),h=this.getEffortSetting(n),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}:{},...h?{effort:h}:{}})})).json();if(!c.success)throw new Error(c.error);const m=c.data.session.id;await fetch(`/api/sessions/${m}/interactive`,{method:"POST"}),this.terminal.writeln(`\x1B[90m Session ${i} ready\x1B[0m`),await this.selectSession(m),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)},_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.writeFrameScheduled||(this.writeFrameScheduled=!0,this._safeYield(()=>{this.flushPendingWrites(),this.writeFrameScheduled=!1}))},flushFlickerBuffer(){this.flickerFilterBuffer&&(this.pendingWrites.push(this.flickerFilterBuffer),this.flickerFilterBuffer="",this.flickerFilterActive=!1,this.writeFrameScheduled||(this.writeFrameScheduled=!0,this._safeYield(()=>{this.flushPendingWrites(),this.writeFrameScheduled=!1})))},_updateLocalEchoState(){const e=this.loadAppSettingsFromStorage(),t=this.activeSessionId?this.sessions.get(this.activeSessionId):null,i=!!((e.localEchoEnabled??MobileDetection.isTouchDevice())&&t);this._localEchoEnabled&&!i&&this._localEchoOverlay?.clear(),this._localEchoEnabled=i,this._localEchoOverlay&&t&&(t.mode==="opencode"?this._localEchoOverlay.setPrompt({type:"custom",offset:3,find:o=>{try{const a=o.buffer.active,n=a.cursorY,l=a.getLine(a.viewportY+n);if(!l)return null;const u=l.translateToString(!0).indexOf("\u2503");return u>=0?{row:n,col:u}:null}catch{return null}}}):t.mode==="shell"?(this._localEchoOverlay.clear(),this._localEchoEnabled=!1):this._localEchoOverlay.setPrompt({type:"custom",offset:0,find:o=>{try{const a=o.buffer.active;for(let n=o.rows-1;n>=0;n--){const l=a.getLine(a.viewportY+n);if(!l)continue;const u=l.translateToString(!0).lastIndexOf("\u276F");if(u>=0)return{row:n,col:u+2}}return{row:Math.max(0,Math.min(o.rows-1,a.cursorY)),col:Math.max(0,Math.min(o.cols-1,a.cursorX))}}catch{return null}}}))},_handleCjkInput(e){if(!this.activeSessionId){_crashDiag.log(`CJK send DROP no-session len=${e.length}`);return}_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 o=(this.activeSessionId&&this.sessions?this.sessions.get(this.activeSessionId):null)?.mode==="codex"?32768:65536;let a=!1;const n=this._hasRecentUserScrollUp()&&this.terminal.buffer?.active?this.terminal.buffer.active.viewportY:null;s<=o?this.terminal.write(t):(this.terminal.write(t.slice(0,o)),this.pendingWrites.push(t.slice(o)),a=!0,this.writeFrameScheduled||(this.writeFrameScheduled=!0,this._safeYield(()=>{this.flushPendingWrites(),this.writeFrameScheduled=!1}))),n!==null&&this.terminal.buffer?.active?.viewportY!==n&&typeof this.terminal.scrollToLine=="function"&&this.terminal.scrollToLine(n);const l=a?o:s,h=performance.now()-e;if((h>100||a)&&console.warn(`[CRASH-DIAG] flushPendingWrites: ${h.toFixed(0)}ms, ${(l/1024).toFixed(0)}KB written${a?", 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 u=this._localEchoOverlay,c=this;this.terminal.write("",()=>{if(!c._tabCompletionSessionId)return;u.resetBufferDetection();const m=u.detectBufferText();m?m===c._tabCompletionBaseText?(u.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),u.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 o=this._yieldQueue.shift();o&&o()}}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 o=Math.max(0,s);for(;i<o&&!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,o=this._beginBufferLoad(s);return new Promise(a=>{if(!e||e.length===0){this._finishBufferLoad(o),a();return}const n=e.replace(DEC_SYNC_STRIP_RE,""),l=()=>{this._chunkedWriteGen===i&&this._finishBufferLoad(o),a()};if(n.length<=t){this.terminal.write(n,l);return}let h=0;const u=performance.now();let c=0;const m=()=>{if(this._chunkedWriteGen!==i){a();return}if(h>=n.length){const w=performance.now()-u;console.log(`[CRASH-DIAG] chunkedTerminalWrite complete: ${n.length} bytes in ${c} chunks, ${w.toFixed(0)}ms total`),this._safeYield(l);return}const _=performance.now(),E=n.slice(h,h+t);this.terminal.write(E);const x=performance.now()-_;c++,x>50&&console.warn(`[CRASH-DIAG] chunk #${c} write took ${x.toFixed(0)}ms (${E.length} bytes at offset ${h})`),h+=t,this._safeYield(m)};this._safeYield(m)})},_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._localEchoEnabled&&this._localEchoOverlay?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._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)+`
2
+ `)}await navigator.clipboard.writeText(t.replace(/\n+$/,`
3
+ `)),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(),o=s.getBoundingClientRect();if(!i.width&&!i.height)return;const a=Math.max(0,Math.round(i.left-o.left)),n=Math.max(0,Math.round(i.top-o.top));e.style.setProperty("--xterm-helper-left",`${a}px`),e.style.setProperty("--xterm-helper-top",`${n}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 o=s.getBoundingClientRect(),a=Math.max(1,Math.min(this.terminal.cols,Math.floor((e-o.left)/i.width)+1)),n=Math.max(1,Math.min(this.terminal.rows,Math.floor((t-o.top)/i.height)+1));return{col:a,row:n}},_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(o=>!Number.isFinite(o)))return!1;const i=t.split(".").map(Number);for(let o=0;o<3;o++)if(s[o]!==i[o])return s[o]>i[o];return!0},_wheelScrollLines(e){const t=e.shiftKey&&Math.abs(e.deltaX)>Math.abs(e.deltaY)?e.deltaX:e.deltaY;return Math.round(t/25)||(t>0?1:-1)},_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),i=s?.mode||"claude";if(i==="claude"){if(!this._cliVersionAtLeast(s?.cliVersion,"2.1.187"))return!1}else if(i!=="codex")return!1;return this._terminalViewportAtBottom()},_sendSyntheticSgrWheel(e,t,s){if(!this.activeSessionId||!s)return;const i=this._clientPointToCell(e,t);if(!i)return;const o=s<0?64:65,a=Math.min(Math.abs(s),5),n=this._wheelSgrQueue||"";n.length>512||(this._wheelSgrQueue=n+`\x1B[<${o};${i.col};${i.row}M`.repeat(a),!this._wheelSgrFlushTimer&&(this._wheelSgrFlushTimer=setTimeout(()=>this._flushWheelSgrQueue(),40)))},_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()},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,o=!i||i.cols!==s.cols||i.rows!==s.rows;this._lastResizeDims={cols:s.cols,rows:s.rows};const a=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:a};return t.force&&(l.f=!0),this._ws.send(JSON.stringify(l)),o}catch{}const n={...s,viewportType:a};return t.force&&(n.force=!0),await fetch(`/api/sessions/${e}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)}),o},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();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 o(a){if(!Number.isFinite(a))return"";const n=new Date(a);return n.toLocaleDateString("en",{month:"short",day:"numeric"})+" "+n.toLocaleTimeString("en",{hour:"2-digit",minute:"2-digit",hour12:!1})}e.CodemanSearch={SEARCH_DEBOUNCE_MS:250,SEARCH_LIMIT:60,SOURCE_LABELS:i,formatSearchTime:o}})(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",n=>{n.key==="Escape"&&e.value&&(n.stopPropagation(),this._clearSearch())}),t&&t.addEventListener("click",()=>this._clearSearch()),document.querySelectorAll("#searchFilters .search-filter-chip").forEach(n=>{n.addEventListener("click",()=>{const l=n.dataset.typeFilter;this._searchTypes.has(l)&&this._searchTypes.size===1||(this._searchTypes.has(l)?(this._searchTypes.delete(l),n.classList.remove("active")):(this._searchTypes.add(l),n.classList.add("active")),this._runSearch())})});const i=document.getElementById("searchCaseFilter"),o=document.getElementById("searchStatusFilter"),a=document.getElementById("searchDateFilter");i&&i.addEventListener("change",()=>{this._searchSecondary.caseLabel=i.value,this._renderSearch(this._searchLastData)}),o&&o.addEventListener("change",()=>{this._searchSecondary.status=o.value,this._renderSearch(this._searchLastData)}),a&&a.addEventListener("change",()=>{this._searchSecondary.days=a.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(a=>a&&a.name).filter(Boolean))).sort(),i=e.value;e.innerHTML="";const o=document.createElement("option");o.value="",o.textContent="All cases",e.appendChild(o);for(const a of s){const n=document.createElement("option");n.value=a,n.textContent="#"+a,e.appendChild(n)}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 o=++this._searchSeq,a=await this._apiJson("/api/search?"+i.toString());o===this._searchSeq&&(a?this._searchLastData=a: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 o=e;if(t){const a="#"+t;o=o.filter(n=>(n.sessionName||"").includes(a)||n.sessionName===t)}if(s){const a=new Set((this.sessionOrder||[]).concat(Object.keys(this.sessions||{})));o=o.filter(n=>{const l=a.has(n.sessionId);return s==="active"?l:!l})}if(i){const a=Date.now()-Number(i)*24*60*60*1e3;o=o.filter(n=>Number.isFinite(n.timestamp)&&n.timestamp>=a)}return o},_renderSearch(e){const t=document.getElementById("searchResults"),s=document.getElementById("historyTitle"),i=document.getElementById("historyList");if(!t)return;const o=!!e;if(s&&(s.style.display=o?"none":""),i&&(i.style.display=o?"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 a=(e.groups||[]).map(l=>({type:l.type,results:this._applySecondaryFilters(l.results||[])})).filter(l=>l.results.length>0);if(a.reduce((l,h)=>l+h.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 a){const h=document.createElement("div");h.className="search-group-header";const u=document.createElement("span");u.className="search-group-label",u.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),h.append(u,c),t.appendChild(h);for(const m of l.results)t.appendChild(this._buildSearchResultCard(m))}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=document.createElement("span");i.className="search-result-badge search-badge-"+e.type,i.textContent=(window.CodemanSearch.SOURCE_LABELS[e.type]||e.type).replace(/s$/,"");const o=document.createElement("span");o.className="search-result-name",o.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(i,o,a);const n=document.createElement("div");n.className="search-result-snippet",n.textContent=e.snippet||"",t.append(s,n);const l=()=>this._jumpToSearchResult(e);return t.addEventListener("click",l),t.addEventListener("keydown",h=>{(h.key==="Enter"||h.key===" ")&&(h.preventDefault(),l())}),t},_jumpToSearchResult(e){const t=e&&e.jumpTo;if(t){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
@@ -1,6 +1,9 @@
1
1
  /**
2
2
  * @fileoverview File browser and streaming routes.
3
- * Provides directory listing, file content preview, raw file serving, and tail streaming.
3
+ * Provides directory listing, file content preview, raw file serving, tail
4
+ * streaming, and the File Viewer edit-mode write path (edit=1 read +
5
+ * PUT /api/sessions/:id/file-content; policy in src/config/file-editing.ts,
6
+ * design in docs/file-viewer-edit-plan.md).
4
7
  */
5
8
  import { FastifyInstance } from 'fastify';
6
9
  import type { ConfigPort, EventPort, SessionPort } from '../ports/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"file-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/file-routes.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAqB,MAAM,SAAS,CAAC;AAwC7D,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAwgB5E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,IAAI,CAs4BxG"}
1
+ {"version":3,"file":"file-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/file-routes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAqB,MAAM,SAAS,CAAC;AA0C7D,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAglB5E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,IAAI,CAyjCxG"}
@@ -1,10 +1,14 @@
1
1
  /**
2
2
  * @fileoverview File browser and streaming routes.
3
- * Provides directory listing, file content preview, raw file serving, and tail streaming.
3
+ * Provides directory listing, file content preview, raw file serving, tail
4
+ * streaming, and the File Viewer edit-mode write path (edit=1 read +
5
+ * PUT /api/sessions/:id/file-content; policy in src/config/file-editing.ts,
6
+ * design in docs/file-viewer-edit-plan.md).
4
7
  */
5
- import { basename as pathBasename, extname, isAbsolute, join, relative, resolve, sep } from 'node:path';
8
+ import { basename as pathBasename, dirname, extname, isAbsolute, join, relative, resolve, sep } from 'node:path';
6
9
  import { createReadStream, realpathSync } from 'node:fs';
7
10
  import fs from 'node:fs/promises';
11
+ import { createHash, randomBytes } from 'node:crypto';
8
12
  import { homedir } from 'node:os';
9
13
  import { ApiErrorCode, createErrorResponse, getErrorMessage } from '../../types.js';
10
14
  import { fileStreamManager } from '../../file-stream-manager.js';
@@ -17,7 +21,8 @@ import { isMultiUserMode, userSpacePath } from '../../config/multiuser.js';
17
21
  import { CASES_DIR, canAccessOwned, findSessionOrFail, getAuthUser, parseBody, validateSessionFilePath, } from '../route-helpers.js';
18
22
  import { isSensitivePath } from '../sensitive-path.js';
19
23
  import { SseEvent } from '../sse-events.js';
20
- import { FilesystemBrowseQuerySchema, FilesystemPreviewQuerySchema } from '../schemas.js';
24
+ import { FilesystemBrowseQuerySchema, FilesystemPreviewQuerySchema, FileWriteSchema } from '../schemas.js';
25
+ import { MAX_EDITABLE_BYTES, applyEol, detectEol, isDeniedEditRelativePath, isEditableFileName, } from '../../config/file-editing.js';
21
26
  const MIME_TYPES = {
22
27
  png: 'image/png',
23
28
  jpg: 'image/jpeg',
@@ -328,6 +333,66 @@ async function resolveFilesystemPickerPath(ctx, req, requestedPath, sessionId) {
328
333
  function appendDownloadFlag(url) {
329
334
  return `${url}${url.includes('?') ? '&' : '?'}download=true`;
330
335
  }
336
+ // ===== File Viewer edit mode (issue #212) =====
337
+ // Policy lives in src/config/file-editing.ts; design in docs/file-viewer-edit-plan.md.
338
+ function sha256Hex(buf) {
339
+ return createHash('sha256').update(buf).digest('hex');
340
+ }
341
+ /** NUL byte in the first 8KB — same binary signal the plain read path uses. */
342
+ function sniffsBinary(buf) {
343
+ const sniffLength = Math.min(buf.length, 8192);
344
+ for (let i = 0; i < sniffLength; i++) {
345
+ if (buf[i] === 0)
346
+ return true;
347
+ }
348
+ return false;
349
+ }
350
+ /**
351
+ * Structured-throw variant for the edit read/write paths. Identical mechanics to
352
+ * throwFilesystemPickerError (rendered by the central route error handler both
353
+ * in prod and in the app.inject() test harness); a separate name only so edit
354
+ * failures grep distinctly.
355
+ */
356
+ function throwFileEditError(statusCode, code, message) {
357
+ throw Object.assign(new Error(message), {
358
+ statusCode,
359
+ body: createErrorResponse(code, message),
360
+ });
361
+ }
362
+ /**
363
+ * Gate a resolved workspace file for edit-mode read/write. Throws a structured
364
+ * error when the file may not be edited; returns void when it may. Order
365
+ * matters for the message a user sees: confinement (the caller's 404) →
366
+ * sensitive/blocked (403) → .git (403) → extension allowlist (400).
367
+ */
368
+ function assertEditableTarget(resolvedPath, relativePath, blockedTrees) {
369
+ if (isSensitivePath(resolvedPath) || isBlockedAttachmentPath(resolvedPath, blockedTrees)) {
370
+ throwFileEditError(403, ApiErrorCode.FORBIDDEN, 'Editing this file is blocked');
371
+ }
372
+ if (isDeniedEditRelativePath(relativePath)) {
373
+ throwFileEditError(403, ApiErrorCode.FORBIDDEN, 'Files under .git cannot be edited');
374
+ }
375
+ if (!isEditableFileName(pathBasename(resolvedPath))) {
376
+ throwFileEditError(400, ApiErrorCode.INVALID_INPUT, 'This file type is not editable');
377
+ }
378
+ }
379
+ /**
380
+ * Decode a candidate edit buffer, refusing binary and non-UTF-8 content. The
381
+ * round-trip compare is what protects against silent corruption: decoding
382
+ * latin-1 (or any non-UTF-8) bytes yields U+FFFD replacements, and writing
383
+ * those back would destroy the original bytes. A UTF-8 BOM round-trips and is
384
+ * deliberately preserved.
385
+ */
386
+ function decodeEditableText(buf) {
387
+ if (sniffsBinary(buf)) {
388
+ throwFileEditError(400, ApiErrorCode.INVALID_INPUT, 'Binary files cannot be edited');
389
+ }
390
+ const text = buf.toString('utf8');
391
+ if (!Buffer.from(text, 'utf8').equals(buf)) {
392
+ throwFileEditError(400, ApiErrorCode.INVALID_INPUT, 'Only UTF-8 text files can be edited');
393
+ }
394
+ return text;
395
+ }
331
396
  function getSessionAttachmentHistory(ctx, sessionId, req) {
332
397
  const user = getAuthUser(req);
333
398
  const liveSession = ctx.sessions.get(sessionId);
@@ -690,7 +755,7 @@ export function registerFileRoutes(app, ctx) {
690
755
  // Get file content for preview (File Browser)
691
756
  app.get('/api/sessions/:id/file-content', async (req) => {
692
757
  const { id } = req.params;
693
- const { path: filePath, lines, raw } = req.query;
758
+ const { path: filePath, lines, raw, edit, } = req.query;
694
759
  const session = findSessionOrFail(ctx, id, req);
695
760
  if (!filePath) {
696
761
  return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Missing path parameter');
@@ -700,7 +765,46 @@ export function registerFileRoutes(app, ctx) {
700
765
  if (!validated) {
701
766
  return createErrorResponse(ApiErrorCode.NOT_FOUND, 'File not found');
702
767
  }
703
- const { resolvedPath } = validated;
768
+ const { resolvedPath, relativePath } = validated;
769
+ // Read-for-edit: never truncated (a truncated buffer must never become an
770
+ // edit buffer), tighter size cap, full editability gate, and the hash/eol
771
+ // the client must echo back on PUT. Outside the shared try/catch below so
772
+ // its structured errors keep their status codes instead of collapsing into
773
+ // OPERATION_FAILED.
774
+ if (edit === '1' || edit === 'true') {
775
+ const guard = await loadAttachmentGuardConfig();
776
+ assertEditableTarget(resolvedPath, relativePath, guard.blockedTrees);
777
+ let editStat;
778
+ try {
779
+ editStat = await fs.stat(resolvedPath);
780
+ }
781
+ catch {
782
+ throwFileEditError(404, ApiErrorCode.NOT_FOUND, 'File not found');
783
+ }
784
+ if (!editStat.isFile()) {
785
+ throwFileEditError(400, ApiErrorCode.INVALID_INPUT, 'Only regular files can be edited');
786
+ }
787
+ if (editStat.size > MAX_EDITABLE_BYTES) {
788
+ throwFileEditError(413, ApiErrorCode.INVALID_INPUT, `File too large to edit here (${Math.ceil(editStat.size / 1024)}KB > ${MAX_EDITABLE_BYTES / 1024}KB limit)`);
789
+ }
790
+ const editBuf = await fs.readFile(resolvedPath);
791
+ const editText = decodeEditableText(editBuf);
792
+ return {
793
+ success: true,
794
+ data: {
795
+ path: filePath,
796
+ content: editText,
797
+ size: editBuf.length,
798
+ mtimeMs: editStat.mtimeMs,
799
+ totalLines: editText.split('\n').length,
800
+ truncated: false,
801
+ extension: filePath.split('.').pop()?.toLowerCase() || '',
802
+ editable: true,
803
+ hash: sha256Hex(editBuf),
804
+ eol: detectEol(editText),
805
+ },
806
+ };
807
+ }
704
808
  try {
705
809
  const stat = await fs.stat(resolvedPath);
706
810
  // Classify by extension. Known media types render with a dedicated player;
@@ -809,6 +913,17 @@ export function registerFileRoutes(app, ctx) {
809
913
  const allLines = content.split('\n');
810
914
  const truncatedContent = allLines.length > maxLines;
811
915
  const displayContent = truncatedContent ? allLines.slice(0, maxLines).join('\n') : content;
916
+ // Additive edit-mode advertisement: whether an edit=1 re-fetch would
917
+ // succeed. The UTF-8 round-trip compare is a cheap memcmp and mirrors
918
+ // decodeEditableText; no hash here — the Edit action re-fetches with
919
+ // edit=1, which is where the baseHash comes from.
920
+ const guard = await loadAttachmentGuardConfig();
921
+ const editable = isEditableFileName(pathBasename(resolvedPath)) &&
922
+ !isDeniedEditRelativePath(relativePath) &&
923
+ !isSensitivePath(resolvedPath) &&
924
+ !isBlockedAttachmentPath(resolvedPath, guard.blockedTrees) &&
925
+ stat.size <= MAX_EDITABLE_BYTES &&
926
+ Buffer.from(content, 'utf8').equals(buf);
812
927
  return {
813
928
  success: true,
814
929
  data: {
@@ -818,6 +933,7 @@ export function registerFileRoutes(app, ctx) {
818
933
  totalLines: allLines.length,
819
934
  truncated: truncatedContent,
820
935
  extension: ext,
936
+ editable,
821
937
  },
822
938
  };
823
939
  }
@@ -825,6 +941,100 @@ export function registerFileRoutes(app, ctx) {
825
941
  return createErrorResponse(ApiErrorCode.OPERATION_FAILED, `Failed to read file: ${getErrorMessage(err)}`);
826
942
  }
827
943
  });
944
+ // File Viewer edit mode: save a text file back into the session workspace.
945
+ // Edit-in-place ONLY — there is deliberately no O_CREAT path in this handler,
946
+ // so it can never create, and it never deletes. Confinement is identical to
947
+ // the read path (realpath + workspace boundary + ownership via
948
+ // findSessionOrFail), plus the sensitive-path/attachment-guard blocklists and
949
+ // the extension allowlist. Concurrency is optimistic: the client echoes the
950
+ // sha256 it loaded (baseHash) and a mismatch is a 409 unless force is set.
951
+ // bodyLimit: JSON escaping can expand content up to ~6x (each control char
952
+ // becomes \uXXXX), so the 512KB content cap needs headroom over Fastify's
953
+ // 1MB default.
954
+ app.put('/api/sessions/:id/file-content', { bodyLimit: 4 * 1024 * 1024 }, async (req) => {
955
+ const { id } = req.params;
956
+ const session = findSessionOrFail(ctx, id, req);
957
+ const body = parseBody(FileWriteSchema, req.body);
958
+ // Exact byte cap — the schema's .max() counts UTF-16 code units and is
959
+ // only a coarse pre-filter.
960
+ if (Buffer.byteLength(body.content, 'utf8') > MAX_EDITABLE_BYTES) {
961
+ throwFileEditError(413, ApiErrorCode.INVALID_INPUT, `Content too large (${MAX_EDITABLE_BYTES / 1024}KB limit)`);
962
+ }
963
+ const validated = validateSessionFilePath(session.workingDir, body.path);
964
+ if (!validated) {
965
+ // Covers missing files, traversal, and symlink escapes alike — a write
966
+ // target that fails confinement is reported identically to a missing
967
+ // one, matching the read route.
968
+ throwFileEditError(404, ApiErrorCode.NOT_FOUND, 'File not found');
969
+ }
970
+ const { resolvedPath, relativePath } = validated;
971
+ const guard = await loadAttachmentGuardConfig();
972
+ assertEditableTarget(resolvedPath, relativePath, guard.blockedTrees);
973
+ let stat;
974
+ try {
975
+ stat = await fs.stat(resolvedPath);
976
+ }
977
+ catch {
978
+ throwFileEditError(404, ApiErrorCode.NOT_FOUND, 'File not found');
979
+ }
980
+ if (!stat.isFile()) {
981
+ throwFileEditError(400, ApiErrorCode.INVALID_INPUT, 'Only regular files can be edited');
982
+ }
983
+ if (stat.size > MAX_EDITABLE_BYTES) {
984
+ throwFileEditError(413, ApiErrorCode.INVALID_INPUT, `File too large to edit here (${MAX_EDITABLE_BYTES / 1024}KB limit)`);
985
+ }
986
+ const currentBuf = await fs.readFile(resolvedPath);
987
+ const currentText = decodeEditableText(currentBuf);
988
+ const currentHash = sha256Hex(currentBuf);
989
+ if (currentHash !== body.baseHash && !body.force) {
990
+ throwFileEditError(409, ApiErrorCode.CONFLICT, 'File changed on disk since it was loaded — reload it or overwrite');
991
+ }
992
+ // Re-apply the file's original line endings (a <textarea> normalizes to
993
+ // LF; without this a two-line edit of a CRLF file rewrites every line).
994
+ const eol = body.eol ?? detectEol(currentText);
995
+ const outText = applyEol(body.content, eol);
996
+ const outBuf = Buffer.from(outText, 'utf8');
997
+ if (outBuf.length > MAX_EDITABLE_BYTES) {
998
+ throwFileEditError(413, ApiErrorCode.INVALID_INPUT, `Content too large (${MAX_EDITABLE_BYTES / 1024}KB limit)`);
999
+ }
1000
+ // Atomic replace: O_EXCL temp in the same directory, then rename.
1001
+ // 'wx' cannot follow a pre-existing symlink and rename() replaces (not
1002
+ // follows) a symlink in the final component, which closes the
1003
+ // validate-then-write TOCTOU window. fchmod because open()'s mode is
1004
+ // masked by the process umask; fsync so the rename never publishes a
1005
+ // partially-durable file. Trade-off (same as vim's default): the inode
1006
+ // changes, so hardlinks keep the old content.
1007
+ const fileMode = stat.mode & 0o777;
1008
+ const tmpPath = join(dirname(resolvedPath), `.${pathBasename(resolvedPath)}.codeman-tmp-${randomBytes(6).toString('hex')}`);
1009
+ let handle;
1010
+ try {
1011
+ handle = await fs.open(tmpPath, 'wx', fileMode);
1012
+ await handle.chmod(fileMode);
1013
+ await handle.writeFile(outBuf);
1014
+ await handle.sync();
1015
+ await handle.close();
1016
+ handle = undefined;
1017
+ await fs.rename(tmpPath, resolvedPath);
1018
+ }
1019
+ catch (err) {
1020
+ if (handle)
1021
+ await handle.close().catch(() => { });
1022
+ await fs.unlink(tmpPath).catch(() => { });
1023
+ throwFileEditError(500, ApiErrorCode.OPERATION_FAILED, `Failed to save file: ${getErrorMessage(err)}`);
1024
+ }
1025
+ const newStat = await fs.stat(resolvedPath).catch(() => undefined);
1026
+ return {
1027
+ success: true,
1028
+ data: {
1029
+ path: body.path,
1030
+ size: outBuf.length,
1031
+ mtimeMs: newStat?.mtimeMs ?? Date.now(),
1032
+ hash: sha256Hex(outBuf),
1033
+ totalLines: outText.split('\n').length,
1034
+ eol,
1035
+ },
1036
+ };
1037
+ });
828
1038
  // Serve raw file content (for images/binary files)
829
1039
  app.get('/api/sessions/:id/file-raw', async (req, reply) => {
830
1040
  const { id } = req.params;