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.
- package/README.md +2 -0
- package/README.zh-CN.md +2 -0
- package/dist/config/file-editing.d.ts +39 -0
- package/dist/config/file-editing.d.ts.map +1 -0
- package/dist/config/file-editing.js +148 -0
- package/dist/config/file-editing.js.map +1 -0
- package/dist/types/common.d.ts +15 -0
- package/dist/types/common.d.ts.map +1 -1
- 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.8ae6a2ad.js → app.166af5f1.js} +5 -5
- package/dist/web/public/app.166af5f1.js.br +0 -0
- package/dist/web/public/app.166af5f1.js.gz +0 -0
- package/dist/web/public/constants.e7175b95.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/i18n.0fbea500.js +1 -0
- package/dist/web/public/i18n.0fbea500.js.br +0 -0
- package/dist/web/public/i18n.0fbea500.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +16 -7
- 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.9936227d.js.gz +0 -0
- package/dist/web/public/mobile-handlers.bc2b8c1d.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/{mobile.e56d787a.css → mobile.32cacde5.css} +1 -1
- package/dist/web/public/mobile.32cacde5.css.br +0 -0
- package/dist/web/public/{mobile.e56d787a.css.gz → mobile.32cacde5.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.600881ae.js → panels-ui.1d4203e0.js} +63 -62
- package/dist/web/public/panels-ui.1d4203e0.js.br +0 -0
- package/dist/web/public/panels-ui.1d4203e0.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/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-ui.1356a4f4.js.gz +0 -0
- package/dist/web/public/settings-ui.ed9cdb3f.js.gz +0 -0
- package/dist/web/public/{styles.7e638f69.css → styles.2e8dc8b7.css} +1 -1
- package/dist/web/public/styles.2e8dc8b7.css.br +0 -0
- package/dist/web/public/styles.2e8dc8b7.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.3ac6f8c2.js +3 -0
- package/dist/web/public/terminal-ui.3ac6f8c2.js.br +0 -0
- package/dist/web/public/terminal-ui.3ac6f8c2.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-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.085e9e73.js.gz +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/routes/file-routes.d.ts +4 -1
- package/dist/web/routes/file-routes.d.ts.map +1 -1
- package/dist/web/routes/file-routes.js +215 -5
- package/dist/web/routes/file-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +18 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +24 -0
- package/dist/web/schemas.js.map +1 -1
- package/package.json +1 -1
- package/dist/web/public/app.8ae6a2ad.js.br +0 -0
- package/dist/web/public/app.8ae6a2ad.js.gz +0 -0
- package/dist/web/public/i18n.8a202f9d.js +0 -1
- package/dist/web/public/i18n.8a202f9d.js.br +0 -0
- package/dist/web/public/i18n.8a202f9d.js.gz +0 -0
- package/dist/web/public/mobile.e56d787a.css.br +0 -0
- package/dist/web/public/panels-ui.600881ae.js.br +0 -0
- package/dist/web/public/panels-ui.600881ae.js.gz +0 -0
- package/dist/web/public/styles.7e638f69.css.br +0 -0
- package/dist/web/public/styles.7e638f69.css.gz +0 -0
- package/dist/web/public/terminal-ui.07ec3b0b.js +0 -3
- package/dist/web/public/terminal-ui.07ec3b0b.js.br +0 -0
- package/dist/web/public/terminal-ui.07ec3b0b.js.gz +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/web/public/sw.js.gz
CHANGED
|
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="×",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
|
|
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
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview File browser and streaming routes.
|
|
3
|
-
* Provides directory listing, file content preview, raw file serving,
|
|
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
|
|
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,
|
|
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;
|