tmux-weblink 2.2.21 → 2.2.22

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.
@@ -1,40 +1,40 @@
1
- var mn=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function Xe(e){return e.replace(/\x1b\[\?1049[hl]/g,"")}function pe(e){return e.replace(mn,"")}var Ae;function hn(){return Ae||(document.querySelector("link[data-tmux-web-xterm-css]")?Ae=Promise.resolve():(Ae=new Promise(e=>{let t=document.createElement("link");t.rel="stylesheet",t.href="/assets/xterm.css",t.setAttribute("data-tmux-web-xterm-css",""),t.addEventListener("load",()=>e(),{once:!0}),t.addEventListener("error",()=>e(),{once:!0}),document.head.appendChild(t)}),Ae))}var vn=class jt{terminal;fitAddon;container;constructor(t,n,s){this.container=t,this.terminal=n,this.fitAddon=s,this.terminal.loadAddon(this.fitAddon),this.terminal.open(t)}static async create(t,n,s){let[{Terminal:l},{FitAddon:r}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),hn()]),c=new l({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:n,convertEol:!1,smoothScrollDuration:120,theme:s});return new jt(t,c,new r)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(t){return new Promise(n=>this.terminal.write(t,n))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom()}scrollToLine(t,n=!1){this.terminal.scrollToLine(Math.max(0,t),n)}scrollLines(t){let n=this.terminal.buffer?.active;this.terminal.scrollLines(t)}isNearScrollbackTop(){return this.terminal.buffer.active.viewportY<=1}viewportY(){return this.terminal.buffer.active.viewportY}baseY(){return this.terminal.buffer.active.baseY}fit(){let t=gn(this.container);if(t.width<=0||t.height<=0)return!1;let n=this.terminal._core?._renderService?.dimensions?.css?.cell;if(!n?.width||!n?.height)return this.fitAddon.fit(),!0;let s=n.width,l=n.height,r=this.terminal.element?.querySelector(".xterm-viewport"),c=r?Math.max(0,r.offsetWidth-r.clientWidth):0,a=Math.max(2,Math.floor((t.width-c)/s)),u=Math.max(2,Math.floor(t.height/l));return(a!==this.terminal.cols||u!==this.terminal.rows)&&this.terminal.resize(a,u),!0}focus(){this.terminal.focus()}pasteText(t){this.terminal.paste(t)}input(t){this.terminal.input(t,!1)}onData(t){this.terminal.onData(t),this.terminal.onBinary(t)}onResize(t){this.terminal.onResize(t)}onScroll(t){this.terminal.onScroll(t)}attachCustomKeyEventHandler(t){this.terminal.attachCustomKeyEventHandler(t)}isFocused(){let t=document.activeElement;return t?t===this.container||t===this.terminal.textarea||this.container.contains(t)||!!t.closest?.(".xterm"):!1}getScreenText(){return this.getRowsText(0,this.terminal.rows-1)}getRowsText(t,n){let s=this.terminal.buffer.active,[l,r]=t<=n?[t,n]:[n,t],c=s.viewportY,a=[];for(let u=l;u<=r;u++){let f=s.getLine(c+u);a.push(f?f.translateToString(!0):"")}return a.join(`
2
- `)}selectRows(t,n){let s=this.terminal.buffer.active,[l,r]=t<=n?[t,n]:[n,t];this.terminal.selectLines(s.viewportY+l,s.viewportY+r)}selectCells(t,n,s,l){let r=this.terminal.buffer.active,c=Math.min(n,l),a=Math.max(n,l);if(c===a){let u=Math.min(t,s),f=Math.max(t,s);this.terminal.select(u,r.viewportY+c,f-u+1)}else this.terminal.selectLines(r.viewportY+c,r.viewportY+a)}getCellsText(t,n,s,l){let r=this.terminal.buffer.active,c=Math.min(n,l),a=Math.max(n,l),u=n<l?t:n>l?s:Math.min(t,s),f=n<l?s:n>l?t:Math.max(t,s),S=r.viewportY,L=[];for(let F=c;F<=a;F++){let M=r.getLine(S+F);if(!M){L.push("");continue}let b=F===c?u:0,O=F===a?f:this.terminal.cols-1,se="";for(let Ce=b;Ce<=O;Ce++){let W=M.getCell(Ce);W&&(se+=W.getChars())}L.push(se.replace(/\s+$/,""))}return L.join(`
3
- `)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}};function gn(e){let t=e.getBoundingClientRect(),n=window.visualViewport;return n?{width:Math.min(t.width,n.width),height:Math.max(0,Math.min(t.height,n.height-Math.max(0,t.top)))}:t}function yn(e,t,n){return e.innerHTML="",e.classList.add("terminal-pending"),(async()=>{let s=await vn.create(e,n.scrollback,n.theme),l,r=0,c,a=null,u=0,f=0,S=.2,L=10,F=.95,M=.04,b=null,O=null;function se(i){b||(b=document.createElement("div"),b.style.cssText=["position:absolute","left:0","width:40%","padding:10px 0","text-align:center","font:600 14px/1.4 system-ui, -apple-system, sans-serif","color:rgba(255,255,255,0.95)","background:rgba(20,30,50,0.55)","border:1px solid rgba(255,255,255,0.25)","border-radius:10px","pointer-events:none","opacity:0","transition:opacity 0.15s ease","z-index:6"].join(";"),e.appendChild(b)),b.textContent=i?"\u2191 \u4E0A\u7FFB\u4E00\u9875 (PgUp)":"\u2193 \u4E0B\u7FFB\u4E00\u9875 (PgDn)",b.style.top=i?"10%":"auto",b.style.bottom=i?"auto":"10%",b.style.opacity="1",O&&clearTimeout(O),O=setTimeout(()=>{b&&(b.style.opacity="0")},900)}function Ce(){O&&clearTimeout(O),b&&(b.style.opacity="0")}function W(){u&&(cancelAnimationFrame(u),u=0),f=0}function Ut(i){if(W(),Math.abs(i)<S)return;f=Math.max(-L,Math.min(L,i));let o=()=>{if(j){u=0;return}let d=e.querySelector(".xterm-rows > div"),p=d?d.getBoundingClientRect().height:16,v=p>0?Math.round(f*16/p):f>0?1:-1;v===0&&(v=f>0?1:-1);let A=s.viewportY()-v,V=s.baseY(),ue=Math.max(0,Math.min(A,V));if(s.scrollToLine(ue,!0),f*=F,ue!==A){u=0;return}if(Math.abs(f)<M){u=0;return}u=requestAnimationFrame(o)};u=requestAnimationFrame(o)}let ke=0,R="connecting",Se=0,ie=!1,Te=[],G="",j=!1,ae=1e3,_t=location.protocol==="https:"?"wss:":"ws:",Yt=n.wsBase||"/ws",Ot=_t+"//"+location.host+Yt+"/"+encodeURIComponent(t),Wt="/api/session/"+encodeURIComponent(t)+"/upload",qt=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function Kt(){return Te.join("")+G}async function Vt(i,o=0){let d=i?s.viewportY():0,p=pe(Kt());if(s.reset(),!p){i||s.scrollToBottom();return}await s.write(p),i?s.scrollToLine(o+d):s.scrollToBottom()}function q(i){l?.readyState===WebSocket.OPEN&&l.send(JSON.stringify(i))}function Xt(i){let o;try{o=JSON.parse(i)}catch{R==="live"&&(G+=i,s.write(pe(i)));return}if(o.type==="auth.required"){let d=localStorage.getItem("tmux-web-token");d?q({type:"auth.token",token:d}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(o.type==="attach_failed"&&typeof o.message=="string"){Be=!0,R="dead",s.reset(),s.write(`\r
4
- \x1B[31m`+pe(o.message)+`\x1B[0m\r
5
- `).then(()=>{Me();try{l?.close()}catch{}});return}if(o.type==="auth.ok"){o.token&&localStorage.setItem("tmux-web-token",o.token),ae=1e3,K(),q({type:"resize",cols:s.cols,rows:s.rows}),T();return}if(o.type==="auth.failed"){if(o.permanentLock||o.retryAfterMs&&o.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(o.message);return}return}if(o.type==="snapshot"&&typeof o.data=="string"){let d=Xe(pe(o.data));Te=[d],G="",Se=typeof o.lines=="number"?o.lines:n.terminal.initialLines,R="live",s.reset(),K(),q({type:"resize",cols:s.cols,rows:s.rows}),s.write(d).then(()=>s.scrollToBottom());return}if(o.type==="window_changed"&&typeof o.activeIndex=="number"){let d=new URL(location.href);d.searchParams.get("window")!==String(o.activeIndex)&&(d.searchParams.set("window",String(o.activeIndex)),history.replaceState(history.state,"",d)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:o.windows}));return}if(o.type==="history"&&typeof o.data=="string"){o.lines>0&&o.data?(Te.unshift(Xe(o.data)),Se+=o.lines,Vt(!0,o.lines).finally(()=>{ie=!1})):ie=!1;return}if(o.type==="data"&&typeof o.data=="string"){let d=Xe(pe(o.data));if(R==="connecting"){R="live",G=d,s.write(d);return}R==="live"&&(G+=d,s.write(d))}}let Be=!1;function ot(){if(Be)return;let i=Ot,o=localStorage.getItem("tmux-web-token");o&&(i+=(i.includes("?")?"&":"?")+"token="+encodeURIComponent(o)),i+=(i.includes("?")?"&":"?")+"cols="+s.cols+"&rows="+s.rows,l=new WebSocket(i),l.onopen=()=>{R="connecting",Se=0,ie=!1,Te=[],G="",s.reset(),ae=1e3},l.onmessage=d=>{typeof d.data=="string"&&Xt(d.data)},l.onclose=()=>{j||(R="connecting",!Be&&setTimeout(()=>{ae=Math.min(ae*2,1e4),!j&&!Be&&ot()},ae))},l.onerror=()=>l?.close()}function Q(i){q({type:"input",data:i})}function K(){return s.fit()}function Me(){e.classList.remove("terminal-pending")}function T(){r&&cancelAnimationFrame(r),c&&clearTimeout(c),K(),r=requestAnimationFrame(()=>{r=0,K()&&Me()}),c=setTimeout(()=>{K()&&Me()},120)}function Jt(){T(),setTimeout(T,50),setTimeout(T,150),setTimeout(T,300)}function lt(i){return typeof i=="string"&&i.startsWith("image/")}function rt(i){return i?.files?.length?i.files[0]??null:null}function Zt(i){if(!i?.items)return null;for(let o=0;o<i.items.length;o++){let d=i.items[o];if(d?.kind==="file"&&lt(d.type))return d.getAsFile()}return null}async function Gt(i){let o=new FormData,d=i instanceof File&&i.name?i.name:"upload";o.append("file",i,d);let p=localStorage.getItem("tmux-web-token"),v={};p&&(v.Authorization="Bearer "+p);let A=await fetch(Wt,{method:"POST",body:o,headers:v});if(!A.ok){let ue="upload failed";try{let ht=await A.json();ht.error&&(ue=ht.error)}catch{}throw new Error(ue)}let V=await A.json();if(!V.path)throw new Error("no path in response");return V.path}async function qe(i){try{let o=await Gt(i);Q(o)}catch{}}s.onData(i=>Q(i)),s.onResize(({cols:i,rows:o})=>q({type:"resize",cols:i,rows:o})),s.onScroll(()=>{R!=="live"||ie||!s.isNearScrollbackTop()||(ie=!0,q({type:"load_history",before:Se}))});let oe=!1,le=!1,E=null,x=null,re=document.createElement("div");re.className="terminal-copy-banner",re.innerHTML='<strong>Copy mode</strong><span class="hint">drag to copy \xB7 Esc exits</span>',re.hidden=!0,e.appendChild(re);let $=document.createElement("div");$.className="terminal-copy-banner terminal-copy-toast",$.textContent="Copied",$.hidden=!0,e.appendChild($);function ct(i,o){let d=e.querySelector(".xterm-screen, canvas"),p=(d&&d.getBoundingClientRect().width>0?d:e).getBoundingClientRect(),v=p.height>0&&s.rows>0?p.height/s.rows:18,A=p.width>0&&s.cols>0?p.width/s.cols:9;return{row:Math.max(0,Math.min(s.rows-1,Math.floor((o-p.top)/v))),col:Math.max(0,Math.min(s.cols-1,Math.floor((i-p.left)/A)))}}async function Ie(i){if(!i)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(i);return}}catch{}let o=document.createElement("textarea");o.value=i,o.style.position="fixed",o.style.opacity="0",o.style.pointerEvents="none",document.body.appendChild(o),o.focus(),o.select();try{document.execCommand("copy")}catch{}o.remove()}let ce;function De(){$.hidden=!1,ce&&clearTimeout(ce),ce=setTimeout(()=>{$.hidden=!0},1e3)}function ee(i){oe=i,re.hidden=!i,e.classList.toggle("terminal-copy-mode-active",i),i&&s.focus()}function dt(){ee(!oe)}function Qt(i){j||i.button!==0||le||(oe||i.shiftKey)&&(i.preventDefault(),i.stopPropagation(),le=!0,E=ct(i.clientX,i.clientY),x=E,s.selectCells(E.col,E.row,x.col,x.row),document.addEventListener("mousemove",Ke,!0),document.addEventListener("mouseup",Ve,!0))}function Ke(i){j||!le||!E||(i.preventDefault(),i.stopPropagation(),x=ct(i.clientX,i.clientY),s.selectCells(E.col,E.row,x.col,x.row))}function Ve(i){if(j||!le||!E||!x)return;i.preventDefault(),i.stopPropagation(),document.removeEventListener("mousemove",Ke,!0),document.removeEventListener("mouseup",Ve,!0),le=!1;let o=s.getCellsText(E.col,E.row,x.col,x.row);E=null,x=null,s.clearSelection(),ee(!1),o&&($.textContent="\u5DF2\u590D\u5236",Ie(o).then(De)),s.focus()}e.addEventListener("mousedown",Qt,!0),document.addEventListener("keydown",en,!0);function en(i){if(!(j||!s.isFocused()||i.defaultPrevented||i.isComposing)){if(oe){if(i.key==="Escape"){i.preventDefault(),i.stopPropagation(),E=null,x=null,s.clearSelection(),ee(!1);return}if(i.key==="Enter"||(i.ctrlKey||i.metaKey)&&i.code==="KeyC"){i.preventDefault(),i.stopPropagation(),Ie(s.getScreenText()).then(De),ee(!1);return}}if(i.altKey&&i.code==="KeyC"&&!i.ctrlKey&&!i.metaKey){if(i.preventDefault(),i.stopPropagation(),E&&x){let o=s.getCellsText(E.col,E.row,x.col,x.row);E=null,x=null,s.clearSelection(),ee(!1),o&&($.textContent="Copied",Ie(o).then(De))}else{let o=s.getSelection();o?(s.clearSelection(),$.textContent="Copied",Ie(o).then(De)):dt()}return}if((i.ctrlKey||i.metaKey)&&i.code==="KeyV"){i.preventDefault(),i.stopPropagation(),(async()=>{try{if(navigator.clipboard?.read){let o=await navigator.clipboard.read();for(let d of o)for(let p of d.types)if(lt(p)){let v=await d.getType(p);await qe(v);return}}}catch{}try{let o=await navigator.clipboard?.readText();o&&s.pasteText(ut(o))}catch{}})();return}}}let Fe=0;function ut(i){return i.split(`\r
1
+ var hn=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function Je(e){return e.replace(/\x1b\[\?1049[hl]/g,"")}function fe(e){return e.replace(hn,"")}var Ae;function vn(){return Ae||(document.querySelector("link[data-tmux-web-xterm-css]")?Ae=Promise.resolve():(Ae=new Promise(e=>{let t=document.createElement("link");t.rel="stylesheet",t.href="/assets/xterm.css",t.setAttribute("data-tmux-web-xterm-css",""),t.addEventListener("load",()=>e(),{once:!0}),t.addEventListener("error",()=>e(),{once:!0}),document.head.appendChild(t)}),Ae))}var gn=class Ut{terminal;fitAddon;container;constructor(t,s,n){this.container=t,this.terminal=s,this.fitAddon=n,this.terminal.loadAddon(this.fitAddon),this.terminal.open(t)}static async create(t,s,n){let[{Terminal:l},{FitAddon:r}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),vn()]),c=new l({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:s,convertEol:!1,smoothScrollDuration:120,theme:n});return new Ut(t,c,new r)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(t){return new Promise(s=>this.terminal.write(t,s))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom()}scrollToLine(t,s=!1){this.terminal.scrollToLine(Math.max(0,t),s)}scrollLines(t){let s=this.terminal.buffer?.active;this.terminal.scrollLines(t)}isNearScrollbackTop(){return this.terminal.buffer.active.viewportY<=1}viewportY(){return this.terminal.buffer.active.viewportY}baseY(){return this.terminal.buffer.active.baseY}fit(){let t=yn(this.container);if(t.width<=0||t.height<=0)return!1;let s=this.terminal._core?._renderService?.dimensions?.css?.cell;if(!s?.width||!s?.height)return this.fitAddon.fit(),!0;let n=s.width,l=s.height,r=this.terminal.element?.querySelector(".xterm-viewport"),c=r?Math.max(0,r.offsetWidth-r.clientWidth):0,a=Math.max(2,Math.floor((t.width-c)/n)),u=Math.max(2,Math.floor(t.height/l));return(a!==this.terminal.cols||u!==this.terminal.rows)&&this.terminal.resize(a,u),!0}focus(){this.terminal.focus()}pasteText(t){this.terminal.paste(t)}input(t){this.terminal.input(t,!1)}onData(t){this.terminal.onData(t),this.terminal.onBinary(t)}onResize(t){this.terminal.onResize(t)}onScroll(t){this.terminal.onScroll(t)}attachCustomKeyEventHandler(t){this.terminal.attachCustomKeyEventHandler(t)}isFocused(){let t=document.activeElement;return t?t===this.container||t===this.terminal.textarea||this.container.contains(t)||!!t.closest?.(".xterm"):!1}getScreenText(){return this.getRowsText(0,this.terminal.rows-1)}getRowsText(t,s){let n=this.terminal.buffer.active,[l,r]=t<=s?[t,s]:[s,t],c=n.viewportY,a=[];for(let u=l;u<=r;u++){let f=n.getLine(c+u);a.push(f?f.translateToString(!0):"")}return a.join(`
2
+ `)}selectRows(t,s){let n=this.terminal.buffer.active,[l,r]=t<=s?[t,s]:[s,t];this.terminal.selectLines(n.viewportY+l,n.viewportY+r)}selectCells(t,s,n,l){let r=this.terminal.buffer.active,c=Math.min(s,l),a=Math.max(s,l);if(c===a){let u=Math.min(t,n),f=Math.max(t,n);this.terminal.select(u,r.viewportY+c,f-u+1)}else this.terminal.selectLines(r.viewportY+c,r.viewportY+a)}getCellsText(t,s,n,l){let r=this.terminal.buffer.active,c=Math.min(s,l),a=Math.max(s,l),u=s<l?t:s>l?n:Math.min(t,n),f=s<l?n:s>l?t:Math.max(t,n),S=r.viewportY,L=[];for(let F=c;F<=a;F++){let M=r.getLine(S+F);if(!M){L.push("");continue}let b=F===c?u:0,q=F===a?f:this.terminal.cols-1,ie="";for(let Ce=b;Ce<=q;Ce++){let O=M.getCell(Ce);O&&(ie+=O.getChars())}L.push(ie.replace(/\s+$/,""))}return L.join(`
3
+ `)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}};function yn(e){let t=e.getBoundingClientRect(),s=window.visualViewport;return s?{width:Math.min(t.width,s.width),height:Math.max(0,Math.min(t.height,s.height-Math.max(0,t.top)))}:t}function wn(e,t,s){return e.innerHTML="",e.classList.add("terminal-pending"),(async()=>{let n=await gn.create(e,s.scrollback,s.theme),l,r=0,c,a=null,u=0,f=0,S=.2,L=10,F=.95,M=.04,b=null,q=null;function ie(i){b||(b=document.createElement("div"),b.style.cssText=["position:absolute","left:0","width:40%","padding:10px 0","text-align:center","font:600 14px/1.4 system-ui, -apple-system, sans-serif","color:rgba(255,255,255,0.95)","background:rgba(20,30,50,0.55)","border:1px solid rgba(255,255,255,0.25)","border-radius:10px","pointer-events:none","opacity:0","transition:opacity 0.15s ease","z-index:6"].join(";"),e.appendChild(b)),b.textContent=i?"\u2191 \u4E0A\u7FFB\u4E00\u9875 (PgUp)":"\u2193 \u4E0B\u7FFB\u4E00\u9875 (PgDn)",b.style.top=i?"10%":"auto",b.style.bottom=i?"auto":"10%",b.style.opacity="1",q&&clearTimeout(q),q=setTimeout(()=>{b&&(b.style.opacity="0")},900)}function Ce(){q&&clearTimeout(q),b&&(b.style.opacity="0")}function O(){u&&(cancelAnimationFrame(u),u=0),f=0}function _t(i){if(O(),Math.abs(i)<S)return;f=Math.max(-L,Math.min(L,i));let o=()=>{if(j){u=0;return}let d=e.querySelector(".xterm-rows > div"),p=d?d.getBoundingClientRect().height:16,v=p>0?Math.round(f*16/p):f>0?1:-1;v===0&&(v=f>0?1:-1);let A=n.viewportY()-v,V=n.baseY(),pe=Math.max(0,Math.min(A,V));if(n.scrollToLine(pe,!0),f*=F,pe!==A){u=0;return}if(Math.abs(f)<M){u=0;return}u=requestAnimationFrame(o)};u=requestAnimationFrame(o)}let ke=0,R="connecting",Se=0,ae=!1,Te=[],Z="",j=!1,oe=1e3,Yt=location.protocol==="https:"?"wss:":"ws:",qt=s.wsBase||"/ws",Ot=Yt+"//"+location.host+qt+"/"+encodeURIComponent(t),Wt="/api/session/"+encodeURIComponent(t)+"/upload",Kt=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function Vt(){return Te.join("")+Z}async function Xt(i,o=0){let d=i?n.viewportY():0,p=fe(Vt());if(n.reset(),!p){i||n.scrollToBottom();return}await n.write(p),i?n.scrollToLine(o+d):n.scrollToBottom()}function W(i){l?.readyState===WebSocket.OPEN&&l.send(JSON.stringify(i))}function Jt(i){let o;try{o=JSON.parse(i)}catch{R==="live"&&(Z+=i,n.write(fe(i)));return}if(o.type==="auth.required"){let d=localStorage.getItem("tmux-web-token");d?W({type:"auth.token",token:d}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(o.type==="attach_failed"&&typeof o.message=="string"){Be=!0,R="dead",n.reset(),n.write(`\r
4
+ \x1B[31m`+fe(o.message)+`\x1B[0m\r
5
+ `).then(()=>{Me();try{l?.close()}catch{}});return}if(o.type==="auth.ok"){o.token&&localStorage.setItem("tmux-web-token",o.token),oe=1e3,K(),W({type:"resize",cols:n.cols,rows:n.rows}),T();return}if(o.type==="auth.failed"){if(o.permanentLock||o.retryAfterMs&&o.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(o.message);return}return}if(o.type==="snapshot"&&typeof o.data=="string"){let d=Je(fe(o.data));Te=[d],Z="",Se=typeof o.lines=="number"?o.lines:s.terminal.initialLines,R="live",n.reset(),K(),W({type:"resize",cols:n.cols,rows:n.rows}),n.write(d).then(()=>n.scrollToBottom());return}if(o.type==="window_changed"&&typeof o.activeIndex=="number"){let d=new URL(location.href);d.searchParams.get("window")!==String(o.activeIndex)&&(d.searchParams.set("window",String(o.activeIndex)),history.replaceState(history.state,"",d)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:o.windows}));return}if(o.type==="history"&&typeof o.data=="string"){o.lines>0&&o.data?(Te.unshift(Je(o.data)),Se+=o.lines,Xt(!0,o.lines).finally(()=>{ae=!1})):ae=!1;return}if(o.type==="data"&&typeof o.data=="string"){let d=Je(fe(o.data));if(R==="connecting"){R="live",Z=d,n.write(d);return}R==="live"&&(Z+=d,n.write(d))}}let Be=!1;function lt(){if(Be)return;let i=Ot,o=localStorage.getItem("tmux-web-token");o&&(i+=(i.includes("?")?"&":"?")+"token="+encodeURIComponent(o)),i+=(i.includes("?")?"&":"?")+"cols="+n.cols+"&rows="+n.rows,l=new WebSocket(i),l.onopen=()=>{R="connecting",Se=0,ae=!1,Te=[],Z="",n.reset(),oe=1e3},l.onmessage=d=>{typeof d.data=="string"&&Jt(d.data)},l.onclose=()=>{j||(R="connecting",!Be&&setTimeout(()=>{oe=Math.min(oe*2,1e4),!j&&!Be&&lt()},oe))},l.onerror=()=>l?.close()}function Q(i){W({type:"input",data:i})}function K(){return n.fit()}function Me(){e.classList.remove("terminal-pending")}function T(){r&&cancelAnimationFrame(r),c&&clearTimeout(c),K(),r=requestAnimationFrame(()=>{r=0,K()&&Me()}),c=setTimeout(()=>{K()&&Me()},120)}function Gt(){T(),setTimeout(T,50),setTimeout(T,150),setTimeout(T,300)}function rt(i){return typeof i=="string"&&i.startsWith("image/")}function ct(i){return i?.files?.length?i.files[0]??null:null}function Zt(i){if(!i?.items)return null;for(let o=0;o<i.items.length;o++){let d=i.items[o];if(d?.kind==="file"&&rt(d.type))return d.getAsFile()}return null}async function Qt(i){let o=new FormData,d=i instanceof File&&i.name?i.name:"upload";o.append("file",i,d);let p=localStorage.getItem("tmux-web-token"),v={};p&&(v.Authorization="Bearer "+p);let A=await fetch(Wt,{method:"POST",body:o,headers:v});if(!A.ok){let pe="upload failed";try{let vt=await A.json();vt.error&&(pe=vt.error)}catch{}throw new Error(pe)}let V=await A.json();if(!V.path)throw new Error("no path in response");return V.path}async function Ke(i){try{let o=await Qt(i);Q(o)}catch{}}n.onData(i=>Q(i)),n.onResize(({cols:i,rows:o})=>W({type:"resize",cols:i,rows:o})),n.onScroll(()=>{R!=="live"||ae||!n.isNearScrollbackTop()||(ae=!0,W({type:"load_history",before:Se}))});let le=!1,re=!1,E=null,x=null,ce=document.createElement("div");ce.className="terminal-copy-banner",ce.innerHTML='<strong>Copy mode</strong><span class="hint">drag to copy \xB7 Esc exits</span>',ce.hidden=!0,e.appendChild(ce);let $=document.createElement("div");$.className="terminal-copy-banner terminal-copy-toast",$.textContent="Copied",$.hidden=!0,e.appendChild($);function dt(i,o){let d=e.querySelector(".xterm-screen, canvas"),p=(d&&d.getBoundingClientRect().width>0?d:e).getBoundingClientRect(),v=p.height>0&&n.rows>0?p.height/n.rows:18,A=p.width>0&&n.cols>0?p.width/n.cols:9;return{row:Math.max(0,Math.min(n.rows-1,Math.floor((o-p.top)/v))),col:Math.max(0,Math.min(n.cols-1,Math.floor((i-p.left)/A)))}}async function Ie(i){if(!i)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(i);return}}catch{}let o=document.createElement("textarea");o.value=i,o.style.position="fixed",o.style.opacity="0",o.style.pointerEvents="none",document.body.appendChild(o),o.focus(),o.select();try{document.execCommand("copy")}catch{}o.remove()}let de;function De(){$.hidden=!1,de&&clearTimeout(de),de=setTimeout(()=>{$.hidden=!0},1e3)}function ee(i){le=i,ce.hidden=!i,e.classList.toggle("terminal-copy-mode-active",i),i&&n.focus()}function ut(){ee(!le)}function en(i){j||i.button!==0||re||(le||i.shiftKey)&&(i.preventDefault(),i.stopPropagation(),re=!0,E=dt(i.clientX,i.clientY),x=E,n.selectCells(E.col,E.row,x.col,x.row),document.addEventListener("mousemove",Ve,!0),document.addEventListener("mouseup",Xe,!0))}function Ve(i){j||!re||!E||(i.preventDefault(),i.stopPropagation(),x=dt(i.clientX,i.clientY),n.selectCells(E.col,E.row,x.col,x.row))}function Xe(i){if(j||!re||!E||!x)return;i.preventDefault(),i.stopPropagation(),document.removeEventListener("mousemove",Ve,!0),document.removeEventListener("mouseup",Xe,!0),re=!1;let o=n.getCellsText(E.col,E.row,x.col,x.row);E=null,x=null,n.clearSelection(),ee(!1),o&&($.textContent="\u5DF2\u590D\u5236",Ie(o).then(De)),n.focus()}e.addEventListener("mousedown",en,!0),document.addEventListener("keydown",tn,!0);function tn(i){if(!(j||!n.isFocused()||i.defaultPrevented||i.isComposing)){if(le){if(i.key==="Escape"){i.preventDefault(),i.stopPropagation(),E=null,x=null,n.clearSelection(),ee(!1);return}if(i.key==="Enter"||(i.ctrlKey||i.metaKey)&&i.code==="KeyC"){i.preventDefault(),i.stopPropagation(),Ie(n.getScreenText()).then(De),ee(!1);return}}if(i.altKey&&i.code==="KeyC"&&!i.ctrlKey&&!i.metaKey){if(i.preventDefault(),i.stopPropagation(),E&&x){let o=n.getCellsText(E.col,E.row,x.col,x.row);E=null,x=null,n.clearSelection(),ee(!1),o&&($.textContent="Copied",Ie(o).then(De))}else{let o=n.getSelection();o?(n.clearSelection(),$.textContent="Copied",Ie(o).then(De)):ut()}return}if((i.ctrlKey||i.metaKey)&&i.code==="KeyV"){i.preventDefault(),i.stopPropagation(),(async()=>{try{if(navigator.clipboard?.read){let o=await navigator.clipboard.read();for(let d of o)for(let p of d.types)if(rt(p)){let v=await d.getType(p);await Ke(v);return}}}catch{}try{let o=await navigator.clipboard?.readText();o&&n.pasteText(pt(o))}catch{}})();return}}}let Fe=0;function pt(i){return i.split(`\r
6
6
  `).join(`
7
7
  `).split(`
8
- `).join("\r")}async function tn(i){let o=Zt(i.clipboardData);if(o){i.preventDefault(),i.stopPropagation();let v=Date.now();if(v-Fe<50)return;Fe=v,await qe(o);return}let d=i.clipboardData?.getData("text/plain");if(!d)return;i.preventDefault(),i.stopPropagation();let p=Date.now();p-Fe<50||(Fe=p,s.pasteText(ut(d)))}let de=0;function nn(i){i.preventDefault(),de++,rt(i.dataTransfer)&&e.classList.add("terminal-drag-over")}function sn(i){i.preventDefault(),i.dataTransfer&&(i.dataTransfer.dropEffect="copy")}function an(i){i.preventDefault(),de--,de<=0&&(de=0,e.classList.remove("terminal-drag-over"))}async function on(i){i.preventDefault(),de=0,e.classList.remove("terminal-drag-over");let o=rt(i.dataTransfer);o&&await qe(o)}function pt(i){!qt||i.key!=="Escape"||oe||i.defaultPrevented||i.repeat||i.metaKey||i.ctrlKey||i.altKey||s.isFocused()&&(i.preventDefault(),i.stopPropagation(),Q("\x1B"))}e.addEventListener("paste",tn),e.addEventListener("dragenter",nn),e.addEventListener("dragover",sn),e.addEventListener("dragleave",an),e.addEventListener("drop",on),document.addEventListener("keydown",pt,!0);function ln(i){if(i.touches.length!==1){a=null;return}let o=i.touches[0];if(!o)return;W();let d=e.getBoundingClientRect(),p=o.clientX-d.left<d.width*.4?"tui":"history",v=o.clientY-d.top<d.height*.5;a={startX:o.clientX,startY:o.clientY,lastX:o.clientX,lastY:o.clientY,lastT:performance.now(),velocity:0,scrolling:!1,zone:p,tuiUp:v,sent:!1},i.stopPropagation()}function rn(i){if(!a||i.touches.length!==1)return;let o=i.touches[0];if(!o)return;let d=o.clientY-a.startY,p=o.clientX-a.startX;if(!a.scrolling){if(Math.abs(d)<8||Math.abs(d)<Math.abs(p))return;a.scrolling=!0,ke=Date.now()+500}i.preventDefault(),i.stopPropagation();let v=performance.now(),A=o.clientY-a.lastY;if(a.zone==="tui"){a.sent||(a.sent=!0,Q(a.tuiUp?"\x1B[5~":"\x1B[6~"),se(a.tuiUp)),a.lastX=o.clientX,a.lastY=o.clientY,a.lastT=v;return}let V=v-a.lastT;V>0&&(a.velocity=A/V),pn(-A),a.lastX=o.clientX,a.lastY=o.clientY,a.lastT=v}function cn(i){if(!a)return;let o=a.scrolling,d=a.zone,p=a.tuiUp,v=a.velocity;if(a=null,i.stopPropagation(),d==="tui"){o||(Q(p?"\x1B[5~":"\x1B[6~"),se(p),ke=Date.now()+500,i.preventDefault());return}if(!o){s.focus();return}ke=Date.now()+500,i.preventDefault(),v&&Ut(v)}function dn(i){a=null,W(),i.stopPropagation()}function un(i){i.pointerType==="touch"&&Date.now()<ke&&(i.preventDefault(),i.stopPropagation())}function pn(i){let o=e.querySelector(".xterm-rows > div"),d=o?o.getBoundingClientRect().height:16,p=d>0?Math.round(i/d):i>0?1:-1;p===0&&(p=i>0?1:-1),s.scrollLines(p)}e.addEventListener("touchstart",ln,{passive:!0,capture:!0}),e.addEventListener("touchmove",rn,{passive:!1,capture:!0}),e.addEventListener("touchend",cn,{passive:!1,capture:!0}),e.addEventListener("touchcancel",dn,{passive:!0,capture:!0}),e.addEventListener("pointerup",un,!0);function ft(){T()}window.addEventListener("resize",ft),window.visualViewport?.addEventListener("resize",T),window.visualViewport?.addEventListener("scroll",T);let mt=new ResizeObserver(()=>T());mt.observe(e),document.fonts?.ready.then(()=>{T(),setTimeout(T,50),setTimeout(T,200)}),e.addEventListener("focusin",Jt,!0);async function fn(){if(document.fonts?.ready)try{await Promise.race([document.fonts.ready,new Promise(i=>setTimeout(i,1e3))])}catch{}}return requestAnimationFrame(()=>{K()&&Me(),(async()=>(await fn(),K()&&q({type:"resize",cols:s.cols,rows:s.rows}),ot(),T()))()}),{destroy(){if(j=!0,W(),ee(!1),ce&&clearTimeout(ce),document.removeEventListener("mousemove",Ke,!0),document.removeEventListener("mouseup",Ve,!0),l){try{l.close(1e3,"session switch")}catch{}l=void 0}e.innerHTML="",e.classList.remove("terminal-pending"),window.removeEventListener("resize",ft),mt.disconnect(),document.removeEventListener("keydown",pt,!0)},focus(){s.focus()},sendInput(i){Q(i)},toggleCopyMode(){dt()}}})()}var fe=window.__TMUX_WEB_SHELL__,g=null,y=null,me=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null,B=me;window.__tmuxWebScopeAgent=me;var Je="sessions",U="",He="",Pe="",vt="";function C(e){return B?"/a/"+encodeURIComponent(B)+e:e}var m=document.getElementById("sidebar-content"),X=document.getElementById("main-placeholder"),I=document.getElementById("terminal-container"),he=document.getElementById("file-editor"),wn=document.getElementById("fe-path"),H=document.getElementById("fe-status"),ve=document.getElementById("fe-content"),J=document.getElementById("fe-save"),bn=document.getElementById("fe-delete"),En=document.getElementById("fe-back"),gt=document.getElementById("fe-new-name"),Ln=document.getElementById("fe-new-btn"),Ze=document.getElementById("settings-backdrop"),yt=document.getElementById("settings-popover"),wt=document.querySelector(".sidebar");function xn(){return window.matchMedia("(max-width: 640px)").matches}function Ge(){xn()&&wt.classList.add("collapsed")}function bt(){wt.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{bt(),Et("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{bt(),Et("files")});function Et(e){Je=e,document.querySelectorAll(".mode-btn").forEach(n=>n.classList.remove("active"));let t=document.getElementById(e==="sessions"?"mode-sessions":"mode-files");t&&t.classList.add("active"),e==="sessions"?(Qe="",_()):Rn()}var Qe="";async function _(){if(Je!=="sessions")return;try{let t=await(await fetch("/api/sidebar/sessions")).json(),n=JSON.stringify({recent:t.recent,agents:t.agents,tombstones:t.tombstones});if(n===Qe)return;Qe=n,Bn(t)}catch{m.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}let e=document.getElementById("ns-btn");e&&e.addEventListener("click",()=>{let t=window.__openNewSessionModal;typeof t=="function"?t():document.getElementById("new-session-modal")?.classList.add("open")}),at()}var Lt="tmux-web-sidebar-group-collapsed";function xt(){try{return new Set(JSON.parse(localStorage.getItem(Lt)||"[]"))}catch{return new Set}}function Cn(e){try{localStorage.setItem(Lt,JSON.stringify([...e]))}catch{}}function kn(e,t,n){if(!e)return"\u2026";let s=n&&(e===n||e.startsWith(n+"/"))?"~"+e.slice(n.length):e,l=s.split("/").filter(Boolean);if(l.length===0)return e;for(let r=1;r<=l.length;r++){let c=l.slice(-r).join("/");if(!t.some(a=>!a||a===e?!1:(n&&(a===n||a.startsWith(n+"/"))?"~"+a.slice(n.length):a).split("/").filter(Boolean).slice(-r).join("/")===c))return c}return s}function Ct(e,t,n,s=!0){let l=new Map;for(let a of e){let u=a.path||"";l.has(u)||l.set(u,[]),l.get(u).push(a)}let r=[...l.keys()],c=xt();for(let[a,u]of l){let f=kn(a,r,t),S=c.has(a),L=document.createElement("div");L.className="session-group-header"+(S?" collapsed":""),L.title=a||"No working directory",L.innerHTML=`<span class="sg-folder">&#128193;</span><span class="sg-label">${h(f)}</span>`,L.addEventListener("click",()=>{let M=xt(),b=L.nextElementSibling;M.has(a)?(M.delete(a),L.classList.remove("collapsed"),b?.classList.remove("collapsed")):(M.add(a),L.classList.add("collapsed"),b?.classList.add("collapsed")),Cn(M)});let F=document.createElement("div");F.className="session-group-body"+(S?" collapsed":"");for(let M of u)F.appendChild(n?Sn(M,n,s):Tn(M));m.appendChild(L),m.appendChild(F)}}function Sn(e,t,n){let s=document.createElement("div");return s.className="session-item"+(e.name===y&&B===t?" active":"")+(n?"":" session-offline"),s.setAttribute("data-session",e.name),s.setAttribute("data-agent",t),s.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${e.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
9
- <span>${h(e.name)}</span>${n?"":'<span class="meta">offline</span>'}`,n&&s.addEventListener("click",l=>{l.target.closest(".session-edit-btn")||ge(e.name,t)}),s}function Tn(e){let t=document.createElement("div");t.className="session-item"+(e.name===y&&!B?" active":""),t.setAttribute("data-session",e.name),t.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${e.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
8
+ `).join("\r")}async function nn(i){let o=Zt(i.clipboardData);if(o){i.preventDefault(),i.stopPropagation();let v=Date.now();if(v-Fe<50)return;Fe=v,await Ke(o);return}let d=i.clipboardData?.getData("text/plain");if(!d)return;i.preventDefault(),i.stopPropagation();let p=Date.now();p-Fe<50||(Fe=p,n.pasteText(pt(d)))}let ue=0;function sn(i){i.preventDefault(),ue++,ct(i.dataTransfer)&&e.classList.add("terminal-drag-over")}function an(i){i.preventDefault(),i.dataTransfer&&(i.dataTransfer.dropEffect="copy")}function on(i){i.preventDefault(),ue--,ue<=0&&(ue=0,e.classList.remove("terminal-drag-over"))}async function ln(i){i.preventDefault(),ue=0,e.classList.remove("terminal-drag-over");let o=ct(i.dataTransfer);o&&await Ke(o)}function ft(i){!Kt||i.key!=="Escape"||le||i.defaultPrevented||i.repeat||i.metaKey||i.ctrlKey||i.altKey||n.isFocused()&&(i.preventDefault(),i.stopPropagation(),Q("\x1B"))}e.addEventListener("paste",nn),e.addEventListener("dragenter",sn),e.addEventListener("dragover",an),e.addEventListener("dragleave",on),e.addEventListener("drop",ln),document.addEventListener("keydown",ft,!0);function rn(i){if(i.touches.length!==1){a=null;return}let o=i.touches[0];if(!o)return;O();let d=e.getBoundingClientRect(),p=o.clientX-d.left<d.width*.4?"tui":"history",v=o.clientY-d.top<d.height*.5;a={startX:o.clientX,startY:o.clientY,lastX:o.clientX,lastY:o.clientY,lastT:performance.now(),velocity:0,scrolling:!1,zone:p,tuiUp:v,sent:!1},i.stopPropagation()}function cn(i){if(!a||i.touches.length!==1)return;let o=i.touches[0];if(!o)return;let d=o.clientY-a.startY,p=o.clientX-a.startX;if(!a.scrolling){if(Math.abs(d)<8||Math.abs(d)<Math.abs(p))return;a.scrolling=!0,ke=Date.now()+500}i.preventDefault(),i.stopPropagation();let v=performance.now(),A=o.clientY-a.lastY;if(a.zone==="tui"){a.sent||(a.sent=!0,Q(a.tuiUp?"\x1B[5~":"\x1B[6~"),ie(a.tuiUp)),a.lastX=o.clientX,a.lastY=o.clientY,a.lastT=v;return}let V=v-a.lastT;V>0&&(a.velocity=A/V),fn(-A),a.lastX=o.clientX,a.lastY=o.clientY,a.lastT=v}function dn(i){if(!a)return;let o=a.scrolling,d=a.zone,p=a.tuiUp,v=a.velocity;if(a=null,i.stopPropagation(),d==="tui"){o||(Q(p?"\x1B[5~":"\x1B[6~"),ie(p),ke=Date.now()+500,i.preventDefault());return}if(!o){n.focus();return}ke=Date.now()+500,i.preventDefault(),v&&_t(v)}function un(i){a=null,O(),i.stopPropagation()}function pn(i){i.pointerType==="touch"&&Date.now()<ke&&(i.preventDefault(),i.stopPropagation())}function fn(i){let o=e.querySelector(".xterm-rows > div"),d=o?o.getBoundingClientRect().height:16,p=d>0?Math.round(i/d):i>0?1:-1;p===0&&(p=i>0?1:-1),n.scrollLines(p)}e.addEventListener("touchstart",rn,{passive:!0,capture:!0}),e.addEventListener("touchmove",cn,{passive:!1,capture:!0}),e.addEventListener("touchend",dn,{passive:!1,capture:!0}),e.addEventListener("touchcancel",un,{passive:!0,capture:!0}),e.addEventListener("pointerup",pn,!0);function mt(){T()}window.addEventListener("resize",mt),window.visualViewport?.addEventListener("resize",T),window.visualViewport?.addEventListener("scroll",T);let ht=new ResizeObserver(()=>T());ht.observe(e),document.fonts?.ready.then(()=>{T(),setTimeout(T,50),setTimeout(T,200)}),e.addEventListener("focusin",Gt,!0);async function mn(){if(document.fonts?.ready)try{await Promise.race([document.fonts.ready,new Promise(i=>setTimeout(i,1e3))])}catch{}}return requestAnimationFrame(()=>{K()&&Me(),(async()=>(await mn(),K()&&W({type:"resize",cols:n.cols,rows:n.rows}),lt(),T()))()}),{destroy(){if(j=!0,O(),ee(!1),de&&clearTimeout(de),document.removeEventListener("mousemove",Ve,!0),document.removeEventListener("mouseup",Xe,!0),l){try{l.close(1e3,"session switch")}catch{}l=void 0}e.innerHTML="",e.classList.remove("terminal-pending"),window.removeEventListener("resize",mt),ht.disconnect(),document.removeEventListener("keydown",ft,!0)},focus(){n.focus()},sendInput(i){Q(i)},toggleCopyMode(){ut()}}})()}var me=window.__TMUX_WEB_SHELL__,g=null,y=null,he=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null,B=he;window.__tmuxWebScopeAgent=he;var Ge="sessions",U="",He="",Pe="",gt="";function C(e){return B?"/a/"+encodeURIComponent(B)+e:e}var m=document.getElementById("sidebar-content"),X=document.getElementById("main-placeholder"),I=document.getElementById("terminal-container"),ve=document.getElementById("file-editor"),bn=document.getElementById("fe-path"),H=document.getElementById("fe-status"),ge=document.getElementById("fe-content"),J=document.getElementById("fe-save"),En=document.getElementById("fe-delete"),Ln=document.getElementById("fe-back"),yt=document.getElementById("fe-new-name"),xn=document.getElementById("fe-new-btn"),Ze=document.getElementById("settings-backdrop"),wt=document.getElementById("settings-popover"),bt=document.querySelector(".sidebar");function Cn(){return window.matchMedia("(max-width: 640px)").matches}function Qe(){Cn()&&bt.classList.add("collapsed")}function Et(){bt.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{Et(),Lt("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{Et(),Lt("files")});function Lt(e){Ge=e,document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));let t=document.getElementById(e==="sessions"?"mode-sessions":"mode-files");t&&t.classList.add("active"),e==="sessions"?(et="",_()):$n()}var et="";async function _(){if(Ge!=="sessions")return;try{let t=await(await fetch("/api/sidebar/sessions")).json(),s=JSON.stringify({recent:t.recent,agents:t.agents,tombstones:t.tombstones});if(s===et)return;et=s,Mn(t)}catch{m.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}let e=document.getElementById("ns-btn");e&&e.addEventListener("click",()=>{let t=window.__openNewSessionModal;typeof t=="function"?t():document.getElementById("new-session-modal")?.classList.add("open")}),ot()}var xt="tmux-web-sidebar-group-collapsed";function Ct(){try{return new Set(JSON.parse(localStorage.getItem(xt)||"[]"))}catch{return new Set}}function kn(e){try{localStorage.setItem(xt,JSON.stringify([...e]))}catch{}}function Sn(e,t,s){if(!e)return"\u2026";let n=s&&(e===s||e.startsWith(s+"/"))?"~"+e.slice(s.length):e,l=n.split("/").filter(Boolean);if(l.length===0)return e;for(let r=1;r<=l.length;r++){let c=l.slice(-r).join("/");if(!t.some(a=>!a||a===e?!1:(s&&(a===s||a.startsWith(s+"/"))?"~"+a.slice(s.length):a).split("/").filter(Boolean).slice(-r).join("/")===c))return c}return n}function kt(e,t,s,n=!0){let l=new Map;for(let a of e){let u=a.path||"";l.has(u)||l.set(u,[]),l.get(u).push(a)}let r=[...l.keys()],c=Ct();for(let[a,u]of l){let f=Sn(a,r,t),S=c.has(a),L=document.createElement("div");L.className="session-group-header"+(S?" collapsed":""),L.title=a||"No working directory",L.innerHTML=`<span class="sg-folder">&#128193;</span><span class="sg-label">${h(f)}</span>`,L.addEventListener("click",()=>{let M=Ct(),b=L.nextElementSibling;M.has(a)?(M.delete(a),L.classList.remove("collapsed"),b?.classList.remove("collapsed")):(M.add(a),L.classList.add("collapsed"),b?.classList.add("collapsed")),kn(M)});let F=document.createElement("div");F.className="session-group-body"+(S?" collapsed":"");for(let M of u)F.appendChild(s?Tn(M,s,n):Bn(M));m.appendChild(L),m.appendChild(F)}}function Tn(e,t,s){let n=document.createElement("div");return n.className="session-item"+(e.name===y&&B===t?" active":"")+(s?"":" session-offline"),n.setAttribute("data-session",e.name),n.setAttribute("data-agent",t),n.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${e.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
9
+ <span>${h(e.name)}</span>${s?"":'<span class="meta">offline</span>'}`,s&&n.addEventListener("click",l=>{l.target.closest(".session-edit-btn")||ye(e.name,t)}),n}function Bn(e){let t=document.createElement("div");t.className="session-item"+(e.name===y&&!B?" active":""),t.setAttribute("data-session",e.name),t.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${e.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
10
10
  <span>${h(e.name)}</span>
11
11
  <button class="session-edit-btn" data-session="${h(e.name)}" title="Edit session"><svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 000-1.41l-2.34-2.34a1 1 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>
12
- `,t.addEventListener("click",s=>{s.target.closest(".session-edit-btn")||ge(e.name)});let n=t.querySelector(".session-edit-btn");return n&&n.addEventListener("click",s=>{s.stopPropagation(),Pt(e.name,n)}),t}function Bn(e){m.innerHTML='<button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';let t=e.recent||[],n=document.createElement("div");n.className="sidebar-section-label"+(me?" clickable":""),n.textContent=me?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",me&&(n.title="Back to hub",n.addEventListener("click",()=>{window.location.href="/"})),m.appendChild(n),Ct(t,e.home);let s=e.agents||[];for(let r of s){if(!r.sessions||r.sessions.length===0)continue;let c=r.agentId===B,a=document.createElement("div");a.className="sidebar-section-label machine-label"+(r.online?"":" agent-offline-label")+(!c&&r.online?" clickable":""),a.textContent=r.agentName+(r.online?"":" (offline)"),!c&&r.online&&(a.title="\u6253\u5F00 "+r.agentName,a.addEventListener("click",()=>{window.location.href="/a/"+encodeURIComponent(r.agentId)+"/"})),m.appendChild(a),Ct(r.sessions,e.home,r.agentId,r.online)}let l=e.tombstones||[];if(l.length>0){let r=document.createElement("div");r.className="sidebar-section-label tombstone-label",r.textContent="\u5931\u6548\u4F1A\u8BDD",m.appendChild(r);for(let c of l){let a=document.createElement("div");a.className="session-item session-tombstone",a.setAttribute("data-session",c.name),c.agentId&&a.setAttribute("data-agent",c.agentId);let u=c.agentId?c.agentOnline?" (agent)":" (agent \u79BB\u7EBF)":"";a.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"></svg>
12
+ `,t.addEventListener("click",n=>{n.target.closest(".session-edit-btn")||ye(e.name)});let s=t.querySelector(".session-edit-btn");return s&&s.addEventListener("click",n=>{n.stopPropagation(),Nt(e.name,s)}),t}function Mn(e){m.innerHTML='<button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';let t=e.recent||[],s=document.createElement("div");s.className="sidebar-section-label"+(he?" clickable":""),s.textContent=he?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",he&&(s.title="Back to hub",s.addEventListener("click",()=>{window.location.href="/"})),m.appendChild(s),kt(t,e.home);let n=e.agents||[];for(let r of n){if(!r.sessions||r.sessions.length===0)continue;let c=r.agentId===B,a=document.createElement("div");a.className="sidebar-section-label machine-label"+(r.online?"":" agent-offline-label")+(!c&&r.online?" clickable":""),a.textContent=r.agentName+(r.online?"":" (offline)"),!c&&r.online&&(a.title="\u6253\u5F00 "+r.agentName,a.addEventListener("click",()=>{window.location.href="/a/"+encodeURIComponent(r.agentId)+"/"})),m.appendChild(a),kt(r.sessions,e.home,r.agentId,r.online)}let l=e.tombstones||[];if(l.length>0){let r=document.createElement("div");r.className="sidebar-section-label tombstone-label",r.textContent="\u5931\u6548\u4F1A\u8BDD",m.appendChild(r);for(let c of l){let a=document.createElement("div");a.className="session-item session-tombstone",a.setAttribute("data-session",c.name),c.agentId&&a.setAttribute("data-agent",c.agentId);let u=c.agentId?c.agentOnline?" (agent)":" (agent \u79BB\u7EBF)":"";a.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"></svg>
13
13
  <span>${h(c.name)}</span><span class="meta">\u5931\u6548${u}</span>
14
- <button class="session-edit-btn" data-session="${h(c.name)}" title="\u6062\u590D\u4F1A\u8BDD"><svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 000-1.41l-2.34-2.34a1 1 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>`;let f=a.querySelector(".session-edit-btn");f&&f.addEventListener("click",S=>{S.stopPropagation(),Pt(c.name,f,{tombstone:!0,agentId:c.agentId})}),m.appendChild(a)}}}function h(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function ge(e,t){if(!(y===e&&g&&B===(t||null))){he.style.display="none",X.style.display="none",I.style.display="",I.classList.add("terminal-pending"),Ge(),g&&(Ye(),g.destroy(),g=null),y=e,B=t||null,window.__tmuxWebScopeAgent=B,window.__tmuxWebCurrentSession=y,Xn(e,t);for(let n of m.querySelectorAll(".session-item"))n.classList.toggle("active",n.getAttribute("data-session")===e&&(n.getAttribute("data-agent")||null)===B);try{g=await yn(I,e,{terminal:fe.terminal,scrollback:fe.scrollback,theme:fe.theme,wsBase:B?"/ws/a/"+encodeURIComponent(B):fe.wsBase||"/ws"}),Ht()}catch(n){console.error("[shell] terminal init failed:",n);let s=n&&typeof n=="object"&&"message"in n?n.message:String(n);I.textContent="Failed to open terminal for "+e+": "+s,I.classList.remove("terminal-pending")}}}var Ne=[],w=null,kt=!1;function Mn(e){if(!w||!w.files.length)return null;let t=w.repoRoot;if(!t||!e.startsWith(t))return null;let n=e.slice(t.length+1);for(let s of w.files)if(s.path===n)return s.status;return null}async function In(e){kt=!0;try{let t=await fetch(C("/api/git/status?path="+encodeURIComponent(e)));t.ok?w=await t.json():w=null}catch{w=null}finally{kt=!1}}function Dn(){if(!w||!w.branch)return"";let{branch:e,linesAdded:t,linesRemoved:n}=w,s=`<div class="sidebar-git"><div class="git-branch-badge">
14
+ <button class="session-edit-btn" data-session="${h(c.name)}" title="\u6062\u590D\u4F1A\u8BDD"><svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 000-1.41l-2.34-2.34a1 1 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>`;let f=a.querySelector(".session-edit-btn");f&&f.addEventListener("click",S=>{S.stopPropagation(),Nt(c.name,f,{tombstone:!0,agentId:c.agentId})}),m.appendChild(a)}}}function h(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function ye(e,t){if(y===e&&g&&B===(t||null))return;ve.style.display="none",X.style.display="none",I.style.display="",I.classList.add("terminal-pending"),Qe(),g&&(Ye(),g.destroy(),g=null),y=e,B=t||null,window.__tmuxWebScopeAgent=B,window.__tmuxWebCurrentSession=y,Gn(e,t);let s=at(e,t);zt.set(s,Date.now()+Xn);let n=se.get(s);if(n&&n.state==="working"){n.state="idle",n.doneUntil=void 0,n.unread=!1;let l=m.querySelector(t?'.session-item[data-agent="'+CSS.escape(t)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])');l&&We(l,s,n)}for(let l of m.querySelectorAll(".session-item"))l.classList.toggle("active",l.getAttribute("data-session")===e&&(l.getAttribute("data-agent")||null)===B);try{g=await wn(I,e,{terminal:me.terminal,scrollback:me.scrollback,theme:me.theme,wsBase:B?"/ws/a/"+encodeURIComponent(B):me.wsBase||"/ws"}),Pt()}catch(l){console.error("[shell] terminal init failed:",l);let r=l&&typeof l=="object"&&"message"in l?l.message:String(l);I.textContent="Failed to open terminal for "+e+": "+r,I.classList.remove("terminal-pending")}}var Ne=[],w=null,St=!1;function In(e){if(!w||!w.files.length)return null;let t=w.repoRoot;if(!t||!e.startsWith(t))return null;let s=e.slice(t.length+1);for(let n of w.files)if(n.path===s)return n.status;return null}async function Dn(e){St=!0;try{let t=await fetch(C("/api/git/status?path="+encodeURIComponent(e)));t.ok?w=await t.json():w=null}catch{w=null}finally{St=!1}}function Fn(){if(!w||!w.branch)return"";let{branch:e,linesAdded:t,linesRemoved:s}=w,n=`<div class="sidebar-git"><div class="git-branch-badge">
15
15
  <svg viewBox="0 0 16 16" fill="currentColor"><path d="M11.63 1.22a.75.75 0 00-1.06 0L5.99 5.8 4.12 3.93a.75.75 0 10-1.06 1.06L4.93 6.87A3.5 3.5 0 003 10.09v3.16a.75.75 0 001 0v-3.16a2.5 2.5 0 012.5-2.5h.27l-1.06 1.06a.75.75 0 001.06 1.06l2.37-2.38a.75.75 0 000-1.06L7.59 5.27l4.04-4.05a.75.75 0 000-1.06L11.63 1.22zM3.25 13.94a.75.75 0 01-.75-.75v-.69a.75.75 0 011.5 0v.69c0 .414-.336.75-.75.75z"/></svg>
16
- ${h(e)}</div>`;return(t>0||n>0)&&(s+=`<div class="git-diff-stats">+<span class="add">${t}</span> <span class="del">-${n}</span></div>`),s+"</div>"}m.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&te&&w&&(e.stopPropagation(),It())});var ye=document.getElementById("header-git"),St=document.getElementById("git-diff-view"),Tt=document.getElementById("gd-path"),Re=document.getElementById("gd-status"),$e=document.getElementById("gd-content"),Fn=document.getElementById("gd-back"),te=null;function we(e){if(!e||!e.branch){ye.style.display="none",te=null;return}te=e.repoRoot;let{branch:t,linesAdded:n,linesRemoved:s}=e,l=`<span class="branch">${h(t)}</span>`;(n>0||s>0)&&(l+=`<span class="sep">\xB7</span>+<span class="diff-add">${n}</span> <span class="diff-del">-${s}</span>`),ye.innerHTML=l,ye.style.display="block"}ye.addEventListener("click",e=>{te&&w&&(e.stopPropagation(),It())});var k=document.getElementById("git-popover"),ze=document.getElementById("git-popover-backdrop");function et(){let e=ye.getBoundingClientRect();k.style.top=e.bottom+4+"px",k.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function Bt(){let e=te,t=document.getElementById("git-popover-browse");t&&t.addEventListener("click",()=>{if(Ue(),e){Je="files",document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));let n=document.getElementById("mode-files");n&&n.classList.add("active"),Y(e)}})}var Mt="";function It(){if(!w)return;let e=w.files||[];if(!e.length)k.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
17
- <div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let t=`<div class="git-popover-header">${e.length} file${e.length>1?"s":""} changed</div>`;for(let n=0;n<e.length;n++){let s=e[n],l=s.additions||0,r=s.deletions||0,c="";switch(s.status){case"M":c='<span class="git-file-status M">M</span>';break;case"A":c='<span class="git-file-status A">A</span>';break;case"D":c='<span class="git-file-status D">D</span>';break;case"?":c='<span class="git-file-status ?">?</span>';break;case"R":c='<span class="git-file-status R">R</span>';break}t+=`<div class="git-popover-item" data-idx="${n}">
18
- ${c}<span class="file-path">${h(s.path)}</span>
16
+ ${h(e)}</div>`;return(t>0||s>0)&&(n+=`<div class="git-diff-stats">+<span class="add">${t}</span> <span class="del">-${s}</span></div>`),n+"</div>"}m.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&te&&w&&(e.stopPropagation(),Dt())});var we=document.getElementById("header-git"),Tt=document.getElementById("git-diff-view"),Bt=document.getElementById("gd-path"),Re=document.getElementById("gd-status"),$e=document.getElementById("gd-content"),An=document.getElementById("gd-back"),te=null;function be(e){if(!e||!e.branch){we.style.display="none",te=null;return}te=e.repoRoot;let{branch:t,linesAdded:s,linesRemoved:n}=e,l=`<span class="branch">${h(t)}</span>`;(s>0||n>0)&&(l+=`<span class="sep">\xB7</span>+<span class="diff-add">${s}</span> <span class="diff-del">-${n}</span>`),we.innerHTML=l,we.style.display="block"}we.addEventListener("click",e=>{te&&w&&(e.stopPropagation(),Dt())});var k=document.getElementById("git-popover"),ze=document.getElementById("git-popover-backdrop");function tt(){let e=we.getBoundingClientRect();k.style.top=e.bottom+4+"px",k.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function Mt(){let e=te,t=document.getElementById("git-popover-browse");t&&t.addEventListener("click",()=>{if(Ue(),e){Ge="files",document.querySelectorAll(".mode-btn").forEach(n=>n.classList.remove("active"));let s=document.getElementById("mode-files");s&&s.classList.add("active"),Y(e)}})}var It="";function Dt(){if(!w)return;let e=w.files||[];if(!e.length)k.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
17
+ <div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let t=`<div class="git-popover-header">${e.length} file${e.length>1?"s":""} changed</div>`;for(let s=0;s<e.length;s++){let n=e[s],l=n.additions||0,r=n.deletions||0,c="";switch(n.status){case"M":c='<span class="git-file-status M">M</span>';break;case"A":c='<span class="git-file-status A">A</span>';break;case"D":c='<span class="git-file-status D">D</span>';break;case"?":c='<span class="git-file-status ?">?</span>';break;case"R":c='<span class="git-file-status R">R</span>';break}t+=`<div class="git-popover-item" data-idx="${s}">
18
+ ${c}<span class="file-path">${h(n.path)}</span>
19
19
  <span class="file-add">${l>0?"+"+l:""}</span>
20
20
  <span class="file-del">${r>0?"-"+r:""}</span>
21
- </div>`}t+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',Mt=t,k.innerHTML=t,k.querySelectorAll(".git-popover-item").forEach(n=>{let s=parseInt(n.dataset.idx||"0",10),l=e[s];!l||l.status==="D"||(n.classList.add("clickable"),n.style.cursor="pointer",n.addEventListener("click",()=>Ft(l.path)))})}et(),k.classList.add("open"),ze.classList.add("open"),Bt()}function An(){return window.innerWidth>=1024}function Dt(e,t){let n=(e+(t&&e?`
21
+ </div>`}t+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',It=t,k.innerHTML=t,k.querySelectorAll(".git-popover-item").forEach(s=>{let n=parseInt(s.dataset.idx||"0",10),l=e[n];!l||l.status==="D"||(s.classList.add("clickable"),s.style.cursor="pointer",s.addEventListener("click",()=>At(l.path)))})}tt(),k.classList.add("open"),ze.classList.add("open"),Mt()}function Hn(){return window.innerWidth>=1024}function Ft(e,t){let s=(e+(t&&e?`
22
22
  `:"")+t).split(`
23
- `),s="",l=0,r=0;for(let c of n){let a=c;if(a.startsWith("@@")){let u=a.match(/@@\s+-(\d+)[^+]*\+(\d+)/);u&&(l=parseInt(u[1],10)-1,r=parseInt(u[2],10)-1),s+=`<div class="diff-line hunk"><span class="ln-body">${h(a)}</span></div>`;continue}if(a.startsWith("diff --git")||a.startsWith("index ")||a.startsWith("--- ")||a.startsWith("+++ ")||a.startsWith("new file")||a.startsWith("deleted file")){a.startsWith("diff --git")&&(s+=`<div class="diff-line header"><span class="ln-body">${h(a)}</span></div>`);continue}if(a.startsWith("+"))r++,s+=`<div class="diff-line add"><span class="ln-no">${r}</span><span class="ln-body">${h(a)}</span></div>`;else if(a.startsWith("-"))l++,s+=`<div class="diff-line del"><span class="ln-no">${l}</span><span class="ln-body">${h(a)}</span></div>`;else if(a.startsWith("\\"))s+=`<div class="diff-line"><span class="ln-body">${h(a)}</span></div>`;else{if(!a&&!l&&!r)continue;l++,r++,s+=`<div class="diff-line"><span class="ln-no">${l}</span><span class="ln-no" style="margin-right:12px">${r}</span><span class="ln-body">${h(a)}</span></div>`}}return{html:s,lineCount:n.length}}var je=null;async function Ft(e){let t=te;if(t)if(Ue(),An()){I.style.display="none",g&&(Ye(),g.destroy(),g=null,je=y,y=null),we(null),he.style.display="none",X.style.display="none",St.style.display="flex",Tt.textContent=e,Re.textContent="Loading\u2026",$e.innerHTML="";try{let n=await fetch(C("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e)));if(!n.ok)throw new Error(await n.text());let s=await n.json();Pn(e,s.diff,s.stagedDiff)}catch(n){let s=n instanceof Error?n.message:String(n);Re.textContent="Error",$e.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${h(s)}</div>`}}else{k.innerHTML=`<div class="git-diff-view"><div class="diff-header">
23
+ `),n="",l=0,r=0;for(let c of s){let a=c;if(a.startsWith("@@")){let u=a.match(/@@\s+-(\d+)[^+]*\+(\d+)/);u&&(l=parseInt(u[1],10)-1,r=parseInt(u[2],10)-1),n+=`<div class="diff-line hunk"><span class="ln-body">${h(a)}</span></div>`;continue}if(a.startsWith("diff --git")||a.startsWith("index ")||a.startsWith("--- ")||a.startsWith("+++ ")||a.startsWith("new file")||a.startsWith("deleted file")){a.startsWith("diff --git")&&(n+=`<div class="diff-line header"><span class="ln-body">${h(a)}</span></div>`);continue}if(a.startsWith("+"))r++,n+=`<div class="diff-line add"><span class="ln-no">${r}</span><span class="ln-body">${h(a)}</span></div>`;else if(a.startsWith("-"))l++,n+=`<div class="diff-line del"><span class="ln-no">${l}</span><span class="ln-body">${h(a)}</span></div>`;else if(a.startsWith("\\"))n+=`<div class="diff-line"><span class="ln-body">${h(a)}</span></div>`;else{if(!a&&!l&&!r)continue;l++,r++,n+=`<div class="diff-line"><span class="ln-no">${l}</span><span class="ln-no" style="margin-right:12px">${r}</span><span class="ln-body">${h(a)}</span></div>`}}return{html:n,lineCount:s.length}}var je=null;async function At(e){let t=te;if(t)if(Ue(),Hn()){I.style.display="none",g&&(Ye(),g.destroy(),g=null,je=y,y=null),be(null),ve.style.display="none",X.style.display="none",Tt.style.display="flex",Bt.textContent=e,Re.textContent="Loading\u2026",$e.innerHTML="";try{let s=await fetch(C("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e)));if(!s.ok)throw new Error(await s.text());let n=await s.json();Nn(e,n.diff,n.stagedDiff)}catch(s){let n=s instanceof Error?s.message:String(s);Re.textContent="Error",$e.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${h(n)}</div>`}}else{k.innerHTML=`<div class="git-diff-view"><div class="diff-header">
24
24
  <button class="diff-back">\u2190 Back</button>
25
25
  <span class="diff-filename">${h(e)}</span>
26
26
  <span class="diff-loading">Loading\u2026</span>
27
- </div></div>`,et(),k.classList.add("open"),ze.classList.add("open");try{let n=await fetch(C("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e)));if(!n.ok)throw new Error(await n.text());let s=await n.json();Hn(e,s.diff,s.stagedDiff)}catch(n){let s=n instanceof Error?n.message:String(n);k.innerHTML=`<div class="git-diff-view"><div class="diff-header">
27
+ </div></div>`,tt(),k.classList.add("open"),ze.classList.add("open");try{let s=await fetch(C("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e)));if(!s.ok)throw new Error(await s.text());let n=await s.json();Pn(e,n.diff,n.stagedDiff)}catch(s){let n=s instanceof Error?s.message:String(s);k.innerHTML=`<div class="git-diff-view"><div class="diff-header">
28
28
  <button class="diff-back">\u2190 Back</button>
29
29
  <span class="diff-filename">${h(e)}</span>
30
- </div><div class="diff-empty">Error loading diff: ${h(s)}</div></div>`}}}function Hn(e,t,n){if(!t&&!n){k.innerHTML=`<div class="git-diff-view"><div class="diff-header">
30
+ </div><div class="diff-empty">Error loading diff: ${h(n)}</div></div>`}}}function Pn(e,t,s){if(!t&&!s){k.innerHTML=`<div class="git-diff-view"><div class="diff-header">
31
31
  <button class="diff-back">\u2190 Back</button>
32
32
  <span class="diff-filename">${h(e)}</span>
33
- </div><div class="diff-empty">No diff content available</div></div>`;return}let{html:s,lineCount:l}=Dt(t,n);k.innerHTML=`<div class="git-diff-view"><div class="diff-header">
33
+ </div><div class="diff-empty">No diff content available</div></div>`;return}let{html:n,lineCount:l}=Ft(t,s);k.innerHTML=`<div class="git-diff-view"><div class="diff-header">
34
34
  <button class="diff-back">\u2190 Back</button>
35
35
  <span class="diff-filename">${h(e)}</span>
36
36
  <span class="diff-filesize">${l} lines</span>
37
- </div><div class="diff-content">${s}</div></div>`,k.querySelector(".diff-back")?.addEventListener("click",()=>{k.innerHTML=Mt,et(),w&&k.querySelectorAll(".git-popover-item").forEach(r=>{let c=parseInt(r.dataset.idx||"0",10),a=w.files[c];!a||a.status==="D"||(r.style.cursor="pointer",r.addEventListener("click",()=>Ft(a.path)))}),Bt()})}function Pn(e,t,n){if(!t&&!n){Re.textContent="No diff",$e.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}let{html:s,lineCount:l}=Dt(t,n);Tt.textContent=e,Re.textContent=l+" lines",$e.innerHTML=s}function Nn(){St.style.display="none",je?(I.style.display="",I.classList.add("terminal-pending"),ge(je),je=null):y?(I.style.display="",I.classList.add("terminal-pending"),Ht()):X.style.display="flex"}Fn.addEventListener("click",Nn);function Ue(){k.classList.remove("open"),ze.classList.remove("open")}ze.addEventListener("click",Ue),document.addEventListener("keydown",e=>{e.key==="Escape"&&(Ue(),We())});async function At(e){try{let t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(t.path){let n=await fetch("/api/git/status?path="+encodeURIComponent(t.path));if(n.ok){let s=await n.json();w=s,we(s);return}}}catch{}w=null,we(null)}var _e=null;function Ht(){Ye(),y&&(At(y),_e=setInterval(()=>{y&&At(y)},3e4))}function Ye(){_e&&(clearInterval(_e),_e=null)}function Rn(){if(Ne=fe.fsRoots||[],Ne.length===0){if(y){$n(y);return}m.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}m.innerHTML="";for(let e of Ne){let t=document.createElement("div");t.className="file-tree-item",t.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${h(e)}`,t.addEventListener("click",()=>Y(e)),m.appendChild(t)}}async function $n(e){m.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let t=await(await fetch(C("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?Y(t.path):m.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{m.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function Y(e){U=e,m.innerHTML='<div class="file-tree-empty">Loading...</div>';let t=In(e);try{let n=await(await fetch(C("/api/fs/list?path="+encodeURIComponent(e)))).json();await t,m.innerHTML="",we(w),m.innerHTML+=Dn();let s=zn(e);if(s&&!Ne.some(r=>e===r)){let r=document.createElement("div");r.className="file-tree-item",r.innerHTML="..",r.addEventListener("click",()=>Y(s)),m.appendChild(r)}if(n.dirs)for(let r of n.dirs){let c=r.split("/").pop()||r.split("\\").pop(),a=document.createElement("div");a.className="file-tree-item",a.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${h(c)}`,a.addEventListener("click",()=>Y(r)),m.appendChild(a)}else m.innerHTML="";let l=n.files||[];for(let r of l){let c=r.split("/").pop()||r.split("\\").pop(),a=Mn(r),u="",f="";if(a)switch(a){case"M":u="git-mod",f='<span class="git-file-status M">M</span>';break;case"A":u="git-add",f='<span class="git-file-status A">A</span>';break;case"D":u="git-del",f='<span class="git-file-status D">D</span>';break;case"?":u="git-untracked",f='<span class="git-file-status ?">?</span>';break;case"R":u="git-mod",f='<span class="git-file-status R">R</span>';break}let S=document.createElement("div");S.className="file-tree-item file"+(u?" "+u:""),S.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${f}${h(c)}`,S.addEventListener("click",()=>jn(r)),m.appendChild(S)}}catch{m.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function zn(e){let t=e.lastIndexOf("/");return t<=0?null:e.substring(0,t)}async function jn(e){He=e,I.style.display="none",g&&(Ye(),g.destroy(),g=null,y=null),we(null),Ge(),X.style.display="none",he.style.display="flex",wn.textContent=e,H.textContent="Loading...",J.style.display="none";try{let t=await fetch(C("/api/file?path="+encodeURIComponent(e)));if(!t.ok){let s=await t.json();H.textContent=s.error||"Failed to load",ve.value="";return}let n=await t.json();ve.value=n.content,Pe=n.content,vt=n.content,H.textContent=n.size+" bytes",J.style.display=""}catch{H.textContent="Failed to load",ve.value=""}}ve.addEventListener("input",()=>{Pe=ve.value}),J.addEventListener("click",async()=>{J.textContent="Saving...",J.disabled=!0;try{let e=await fetch(C("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:He,content:Pe})});if(!e.ok){let t=await e.json();H.textContent=t.error||"Save failed";return}vt=Pe,H.textContent="Saved"}catch{H.textContent="Save failed"}finally{J.disabled=!1,J.textContent="Save"}}),bn.addEventListener("click",async()=>{if(confirm("Delete "+He+"?"))try{let e=await fetch(C("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:He})});if(!e.ok){let t=await e.json();H.textContent=t.error||"Delete failed";return}he.style.display="none",X.style.display="flex",U&&Y(U)}catch{H.textContent="Delete failed"}}),En.addEventListener("click",()=>{he.style.display="none",X.style.display="flex",U&&Y(U)}),Ln.addEventListener("click",async()=>{let e=gt.value.trim();if(!e)return;let t=U+"/"+e;try{let n=await fetch(C("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:t})});if(!n.ok){let s=await n.json();H.textContent=s.error||"Create failed";return}gt.value="",U&&Y(U)}catch{H.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let e=yt.classList.toggle("open");Ze.classList.toggle("open",e)}),Ze.addEventListener("click",()=>{yt.classList.remove("open"),Ze.classList.remove("open")});var D="",z=!1,Oe,be=document.createElement("div");be.className="session-popover-backdrop",document.body.appendChild(be);var P=document.createElement("div");P.id="session-popover",P.className="session-popover",P.style.display="none",P.innerHTML=`<div class="sp-field">
37
+ </div><div class="diff-content">${n}</div></div>`,k.querySelector(".diff-back")?.addEventListener("click",()=>{k.innerHTML=It,tt(),w&&k.querySelectorAll(".git-popover-item").forEach(r=>{let c=parseInt(r.dataset.idx||"0",10),a=w.files[c];!a||a.status==="D"||(r.style.cursor="pointer",r.addEventListener("click",()=>At(a.path)))}),Mt()})}function Nn(e,t,s){if(!t&&!s){Re.textContent="No diff",$e.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}let{html:n,lineCount:l}=Ft(t,s);Bt.textContent=e,Re.textContent=l+" lines",$e.innerHTML=n}function Rn(){Tt.style.display="none",je?(I.style.display="",I.classList.add("terminal-pending"),ye(je),je=null):y?(I.style.display="",I.classList.add("terminal-pending"),Pt()):X.style.display="flex"}An.addEventListener("click",Rn);function Ue(){k.classList.remove("open"),ze.classList.remove("open")}ze.addEventListener("click",Ue),document.addEventListener("keydown",e=>{e.key==="Escape"&&(Ue(),Oe())});async function Ht(e){try{let t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(t.path){let s=await fetch("/api/git/status?path="+encodeURIComponent(t.path));if(s.ok){let n=await s.json();w=n,be(n);return}}}catch{}w=null,be(null)}var _e=null;function Pt(){Ye(),y&&(Ht(y),_e=setInterval(()=>{y&&Ht(y)},3e4))}function Ye(){_e&&(clearInterval(_e),_e=null)}function $n(){if(Ne=me.fsRoots||[],Ne.length===0){if(y){zn(y);return}m.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}m.innerHTML="";for(let e of Ne){let t=document.createElement("div");t.className="file-tree-item",t.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${h(e)}`,t.addEventListener("click",()=>Y(e)),m.appendChild(t)}}async function zn(e){m.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let t=await(await fetch(C("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?Y(t.path):m.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{m.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function Y(e){U=e,m.innerHTML='<div class="file-tree-empty">Loading...</div>';let t=Dn(e);try{let s=await(await fetch(C("/api/fs/list?path="+encodeURIComponent(e)))).json();await t,m.innerHTML="",be(w),m.innerHTML+=Fn();let n=jn(e);if(n&&!Ne.some(r=>e===r)){let r=document.createElement("div");r.className="file-tree-item",r.innerHTML="..",r.addEventListener("click",()=>Y(n)),m.appendChild(r)}if(s.dirs)for(let r of s.dirs){let c=r.split("/").pop()||r.split("\\").pop(),a=document.createElement("div");a.className="file-tree-item",a.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${h(c)}`,a.addEventListener("click",()=>Y(r)),m.appendChild(a)}else m.innerHTML="";let l=s.files||[];for(let r of l){let c=r.split("/").pop()||r.split("\\").pop(),a=In(r),u="",f="";if(a)switch(a){case"M":u="git-mod",f='<span class="git-file-status M">M</span>';break;case"A":u="git-add",f='<span class="git-file-status A">A</span>';break;case"D":u="git-del",f='<span class="git-file-status D">D</span>';break;case"?":u="git-untracked",f='<span class="git-file-status ?">?</span>';break;case"R":u="git-mod",f='<span class="git-file-status R">R</span>';break}let S=document.createElement("div");S.className="file-tree-item file"+(u?" "+u:""),S.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${f}${h(c)}`,S.addEventListener("click",()=>Un(r)),m.appendChild(S)}}catch{m.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function jn(e){let t=e.lastIndexOf("/");return t<=0?null:e.substring(0,t)}async function Un(e){He=e,I.style.display="none",g&&(Ye(),g.destroy(),g=null,y=null),be(null),Qe(),X.style.display="none",ve.style.display="flex",bn.textContent=e,H.textContent="Loading...",J.style.display="none";try{let t=await fetch(C("/api/file?path="+encodeURIComponent(e)));if(!t.ok){let n=await t.json();H.textContent=n.error||"Failed to load",ge.value="";return}let s=await t.json();ge.value=s.content,Pe=s.content,gt=s.content,H.textContent=s.size+" bytes",J.style.display=""}catch{H.textContent="Failed to load",ge.value=""}}ge.addEventListener("input",()=>{Pe=ge.value}),J.addEventListener("click",async()=>{J.textContent="Saving...",J.disabled=!0;try{let e=await fetch(C("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:He,content:Pe})});if(!e.ok){let t=await e.json();H.textContent=t.error||"Save failed";return}gt=Pe,H.textContent="Saved"}catch{H.textContent="Save failed"}finally{J.disabled=!1,J.textContent="Save"}}),En.addEventListener("click",async()=>{if(confirm("Delete "+He+"?"))try{let e=await fetch(C("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:He})});if(!e.ok){let t=await e.json();H.textContent=t.error||"Delete failed";return}ve.style.display="none",X.style.display="flex",U&&Y(U)}catch{H.textContent="Delete failed"}}),Ln.addEventListener("click",()=>{ve.style.display="none",X.style.display="flex",U&&Y(U)}),xn.addEventListener("click",async()=>{let e=yt.value.trim();if(!e)return;let t=U+"/"+e;try{let s=await fetch(C("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:t})});if(!s.ok){let n=await s.json();H.textContent=n.error||"Create failed";return}yt.value="",U&&Y(U)}catch{H.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let e=wt.classList.toggle("open");Ze.classList.toggle("open",e)}),Ze.addEventListener("click",()=>{wt.classList.remove("open"),Ze.classList.remove("open")});var D="",z=!1,qe,Ee=document.createElement("div");Ee.className="session-popover-backdrop",document.body.appendChild(Ee);var P=document.createElement("div");P.id="session-popover",P.className="session-popover",P.style.display="none",P.innerHTML=`<div class="sp-field">
38
38
  <label>Rename session</label>
39
39
  <input type="text" id="sp-name" />
40
40
  </div>
@@ -44,5 +44,5 @@ var mn=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function
44
44
  </div>
45
45
  <hr class="sp-divider" />
46
46
  <button class="btn primary" id="sp-rebuild" style="display:none">\u91CD\u5EFA\u540C\u540D\u540C\u8DEF\u5F84 session</button>
47
- <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(P);function Un(e){let t=e.getBoundingClientRect();P.style.top=t.bottom+4+"px",P.style.left=Math.max(8,Math.min(t.left,window.innerWidth-240))+"px"}function Pt(e,t,n){D=e,z=!!n?.tombstone,Oe=n?.agentId;let s=document.getElementById("sp-name");s.value=e;let l=P.querySelector(".sp-field label");l.textContent=z?"\u91CD\u547D\u540D (\u4EC5\u8BB0\u5F55\uFF0C\u4E0D\u91CD\u5EFA)":"Rename session";let r=document.getElementById("sp-rebuild");r.style.display=z?"":"none";let c=document.getElementById("sp-delete");c.textContent=z?"\u5220\u9664\u5931\u6548\u8BB0\u5F55 (\u5893\u7891\u6D88\u5931)":"Delete session";let a=document.getElementById("sp-save");a.textContent=z?"\u91CD\u547D\u540D":"Save",be.classList.add("open"),P.style.display="block",Un(t),setTimeout(()=>s.focus(),50)}function Z(){be.classList.remove("open"),P.style.display="none"}be.addEventListener("click",Z),document.getElementById("sp-save").addEventListener("click",async()=>{let e=document.getElementById("sp-name").value.trim();if(!e||e===D){Z();return}try{let t=z?{oldName:D,newName:e,agentId:Oe}:{oldName:D,newName:e};if(!(await fetch(C(z?"/api/sessions/rename-tombstone":"/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).ok)return;Z(),_(),!z&&y===D&&ge(e)}catch{}}),document.getElementById("sp-rebuild").addEventListener("click",async()=>{if(confirm('\u91CD\u5EFA\u4F1A\u8BDD "'+D+'" (\u540C\u540D\u540C\u8DEF\u5F84\uFF0C\u5185\u5BB9\u4E3A\u7A7A)\uFF1F'))try{let e=await fetch(C("/api/sessions/rebuild"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:D,agentId:Oe})});if(!e.ok){let t=await e.json();alert(t.error||"\u91CD\u5EFA\u5931\u8D25");return}Z(),_()}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(z){if(!confirm('\u5220\u9664\u5931\u6548\u8BB0\u5F55 "'+D+'"\uFF1F'))return;try{if(!(await fetch(C("/api/sessions/dismiss"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:D,agentId:Oe})})).ok)return;Z(),_()}catch{}return}if(confirm('Delete session "'+D+'"?'))try{if(!(await fetch(C("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:D})})).ok)return;Z(),y===D&&(g&&(g.destroy(),g=null),y=null,I.style.display="none",X.style.display="flex"),_()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",Z);var _n={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r","ctrl-r":"","ctrl-c":"",btw:"/btw ",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",e=>{let t=e.target.closest("button[data-key]");if(!t||!g)return;let n=_n[t.dataset.key];n&&(g.sendInput(n),g.focus())});var N=document.getElementById("mk-input"),Nt=document.getElementById("mk-send");function Rt(){if(!N||!g)return;let e=N.value;e&&(g.sendInput(e+"\r"),N.value="",N.rows=1,g.focus())}if(N&&(N.addEventListener("input",()=>{N.rows=1;let e=N.value.split(`
48
- `).length;N.rows=Math.min(e,4)}),N.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),Rt())})),Nt&&Nt.addEventListener("click",Rt),window.visualViewport){let e=document.querySelector(".app-layout"),t=()=>{e&&(e.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",t),window.addEventListener("orientationchange",()=>setTimeout(t,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}var tt=document.getElementById("process-panel"),Ee=document.getElementById("proc-list"),Yn=document.getElementById("proc-close"),nt=document.getElementById("proc-backdrop"),On=document.getElementById("header-ram");function Wn(){Ee.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(C("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){Ee.innerHTML='<div class="proc-empty">No processes</div>';return}let t="";for(let n of e){let s=n.rss,l=s<1048576?(s/1024).toFixed(0)+"K":(s/1048576).toFixed(1)+"M";t+='<div class="proc-row" data-pid="'+n.pid+'"><span class="proc-mem">'+n.mem+'%</span><span class="proc-rss">'+l+'</span><span class="proc-cmd" title="'+h(n.command)+'">'+h(n.command)+'</span><button class="proc-kill" title="Kill PID '+n.pid+'">&times;</button></div>'}Ee.innerHTML=t;for(let n of Ee.querySelectorAll(".proc-row")){let s=n.querySelector(".proc-kill");s&&s.addEventListener("click",l=>{let r=l.target.closest(".proc-row");!r||!confirm("Kill PID "+r.dataset.pid+"?")||fetch(C("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(r.dataset.pid,10)})}).then(c=>c.json()).then(c=>{c.ok?r.style.opacity="0.3":alert("Failed: "+(c.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{Ee.innerHTML='<div class="proc-empty">Failed to load</div>'})}function qn(){tt.classList.add("open"),nt.classList.add("open"),Wn()}function We(){tt.classList.remove("open"),nt.classList.remove("open")}On.addEventListener("click",e=>{e.stopPropagation(),tt.classList.contains("open")?We():qn()}),Yn.addEventListener("click",We),nt.addEventListener("click",We);var Kn=2e3,Le=null,st=null,ne=new Map,xe=new Map;function $t(e,t){return(t?"a:"+t+":":"l:")+e}function Vn(e,t,n){let s=$t(e,t),l=xe.get(s),r=e===y&&(t??null)===B,c=n==="idle"&&l?.state==="working";xe.set(s,{session:e,agentId:t,state:n,doneUntil:c?Date.now()+Kn:void 0,unread:c?!r:!1});let a=t?'.session-item[data-agent="'+CSS.escape(t)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])',u=m.querySelector(a);u&&it(u,s,xe.get(s))}function it(e,t,n){let s=ne.get(t);s&&(clearTimeout(s),ne.delete(t)),e.classList.remove("working","just-done");let l=n.session===y&&(n.agentId??null)===B;n.state==="working"?e.classList.add("working"):l&&n.doneUntil&&Date.now()<n.doneUntil?(e.classList.add("just-done"),ne.set(t,setTimeout(()=>{e.classList.remove("just-done"),ne.delete(t)},Math.max(0,n.doneUntil-Date.now())))):!l&&n.unread&&e.classList.add("just-done")}function at(){for(let[e,t]of xe){let n=t.agentId?'.session-item[data-agent="'+CSS.escape(t.agentId)+'"][data-session="'+CSS.escape(t.session)+'"]':'.session-item[data-session="'+CSS.escape(t.session)+'"]:not([data-agent])',s=m.querySelector(n);s&&it(s,e,t)}}function Xn(e,t){let n=$t(e,t),s=xe.get(n);if(!s)return;let l=ne.get(n);l&&(clearTimeout(l),ne.delete(n)),s.doneUntil=void 0,s.unread=!1;let r=t?'.session-item[data-agent="'+CSS.escape(t)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])',c=m.querySelector(r);c&&it(c,n,s)}function zt(){if(Le||st)return;let e=localStorage.getItem("tmux-web-token"),t=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/activity";e&&(t+="?token="+encodeURIComponent(e));try{let n=new WebSocket(t);Le=n,n.onmessage=s=>{try{let l=JSON.parse(String(s.data));l&&l.type==="session.activity"&&typeof l.session=="string"&&Vn(l.session,l.agentId||void 0,l.state==="working"?"working":"idle")}catch{}},n.onclose=()=>{Le===n&&(Le=null,st=setTimeout(()=>{st=null,zt()},5e3))},n.onerror=()=>{try{n.close()}catch{}}}catch{Le=null}}window.__openSession=ge,window.__refreshSidebar=_,_(),Ge(),at(),zt(),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&at()}),setInterval(()=>{document.visibilityState==="visible"&&_()},1e4);
47
+ <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(P);function _n(e){let t=e.getBoundingClientRect();P.style.top=t.bottom+4+"px",P.style.left=Math.max(8,Math.min(t.left,window.innerWidth-240))+"px"}function Nt(e,t,s){D=e,z=!!s?.tombstone,qe=s?.agentId;let n=document.getElementById("sp-name");n.value=e;let l=P.querySelector(".sp-field label");l.textContent=z?"\u91CD\u547D\u540D (\u4EC5\u8BB0\u5F55\uFF0C\u4E0D\u91CD\u5EFA)":"Rename session";let r=document.getElementById("sp-rebuild");r.style.display=z?"":"none";let c=document.getElementById("sp-delete");c.textContent=z?"\u5220\u9664\u5931\u6548\u8BB0\u5F55 (\u5893\u7891\u6D88\u5931)":"Delete session";let a=document.getElementById("sp-save");a.textContent=z?"\u91CD\u547D\u540D":"Save",Ee.classList.add("open"),P.style.display="block",_n(t),setTimeout(()=>n.focus(),50)}function G(){Ee.classList.remove("open"),P.style.display="none"}Ee.addEventListener("click",G),document.getElementById("sp-save").addEventListener("click",async()=>{let e=document.getElementById("sp-name").value.trim();if(!e||e===D){G();return}try{let t=z?{oldName:D,newName:e,agentId:qe}:{oldName:D,newName:e};if(!(await fetch(C(z?"/api/sessions/rename-tombstone":"/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).ok)return;G(),_(),!z&&y===D&&ye(e)}catch{}}),document.getElementById("sp-rebuild").addEventListener("click",async()=>{if(confirm('\u91CD\u5EFA\u4F1A\u8BDD "'+D+'" (\u540C\u540D\u540C\u8DEF\u5F84\uFF0C\u5185\u5BB9\u4E3A\u7A7A)\uFF1F'))try{let e=await fetch(C("/api/sessions/rebuild"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:D,agentId:qe})});if(!e.ok){let t=await e.json();alert(t.error||"\u91CD\u5EFA\u5931\u8D25");return}G(),_()}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(z){if(!confirm('\u5220\u9664\u5931\u6548\u8BB0\u5F55 "'+D+'"\uFF1F'))return;try{if(!(await fetch(C("/api/sessions/dismiss"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:D,agentId:qe})})).ok)return;G(),_()}catch{}return}if(confirm('Delete session "'+D+'"?'))try{if(!(await fetch(C("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:D})})).ok)return;G(),y===D&&(g&&(g.destroy(),g=null),y=null,I.style.display="none",X.style.display="flex"),_()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",G);var Yn={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r","ctrl-r":"","ctrl-c":"",btw:"/btw ",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",e=>{let t=e.target.closest("button[data-key]");if(!t||!g)return;let s=Yn[t.dataset.key];s&&(g.sendInput(s),g.focus())});var N=document.getElementById("mk-input"),Rt=document.getElementById("mk-send");function $t(){if(!N||!g)return;let e=N.value;e&&(g.sendInput(e+"\r"),N.value="",N.rows=1,g.focus())}if(N&&(N.addEventListener("input",()=>{N.rows=1;let e=N.value.split(`
48
+ `).length;N.rows=Math.min(e,4)}),N.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),$t())})),Rt&&Rt.addEventListener("click",$t),window.visualViewport){let e=document.querySelector(".app-layout"),t=()=>{e&&(e.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",t),window.addEventListener("orientationchange",()=>setTimeout(t,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}var nt=document.getElementById("process-panel"),Le=document.getElementById("proc-list"),qn=document.getElementById("proc-close"),st=document.getElementById("proc-backdrop"),On=document.getElementById("header-ram");function Wn(){Le.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(C("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){Le.innerHTML='<div class="proc-empty">No processes</div>';return}let t="";for(let s of e){let n=s.rss,l=n<1048576?(n/1024).toFixed(0)+"K":(n/1048576).toFixed(1)+"M";t+='<div class="proc-row" data-pid="'+s.pid+'"><span class="proc-mem">'+s.mem+'%</span><span class="proc-rss">'+l+'</span><span class="proc-cmd" title="'+h(s.command)+'">'+h(s.command)+'</span><button class="proc-kill" title="Kill PID '+s.pid+'">&times;</button></div>'}Le.innerHTML=t;for(let s of Le.querySelectorAll(".proc-row")){let n=s.querySelector(".proc-kill");n&&n.addEventListener("click",l=>{let r=l.target.closest(".proc-row");!r||!confirm("Kill PID "+r.dataset.pid+"?")||fetch(C("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(r.dataset.pid,10)})}).then(c=>c.json()).then(c=>{c.ok?r.style.opacity="0.3":alert("Failed: "+(c.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{Le.innerHTML='<div class="proc-empty">Failed to load</div>'})}function Kn(){nt.classList.add("open"),st.classList.add("open"),Wn()}function Oe(){nt.classList.remove("open"),st.classList.remove("open")}On.addEventListener("click",e=>{e.stopPropagation(),nt.classList.contains("open")?Oe():Kn()}),qn.addEventListener("click",Oe),st.addEventListener("click",Oe);var Vn=2e3,xe=null,it=null,ne=new Map,se=new Map,Xn=5e3,zt=new Map;function at(e,t){return(t?"a:"+t+":":"l:")+e}function Jn(e,t,s){let n=at(e,t);if(s==="working"){let f=zt.get(n);if(f&&Date.now()<f)return}let l=se.get(n),r=e===y&&(t??null)===B,c=s==="idle"&&l?.state==="working";se.set(n,{session:e,agentId:t,state:s,doneUntil:c?Date.now()+Vn:void 0,unread:c?!r:!1});let a=t?'.session-item[data-agent="'+CSS.escape(t)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])',u=m.querySelector(a);u&&We(u,n,se.get(n))}function We(e,t,s){let n=ne.get(t);n&&(clearTimeout(n),ne.delete(t)),e.classList.remove("working","just-done");let l=s.session===y&&(s.agentId??null)===B;s.state==="working"?e.classList.add("working"):l&&s.doneUntil&&Date.now()<s.doneUntil?(e.classList.add("just-done"),ne.set(t,setTimeout(()=>{e.classList.remove("just-done"),ne.delete(t)},Math.max(0,s.doneUntil-Date.now())))):!l&&s.unread&&e.classList.add("just-done")}function ot(){for(let[e,t]of se){let s=t.agentId?'.session-item[data-agent="'+CSS.escape(t.agentId)+'"][data-session="'+CSS.escape(t.session)+'"]':'.session-item[data-session="'+CSS.escape(t.session)+'"]:not([data-agent])',n=m.querySelector(s);n&&We(n,e,t)}}function Gn(e,t){let s=at(e,t),n=se.get(s);if(!n)return;let l=ne.get(s);l&&(clearTimeout(l),ne.delete(s)),n.doneUntil=void 0,n.unread=!1;let r=t?'.session-item[data-agent="'+CSS.escape(t)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])',c=m.querySelector(r);c&&We(c,s,n)}function jt(){if(xe||it)return;let e=localStorage.getItem("tmux-web-token"),t=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/activity";e&&(t+="?token="+encodeURIComponent(e));try{let s=new WebSocket(t);xe=s,s.onmessage=n=>{try{let l=JSON.parse(String(n.data));l&&l.type==="session.activity"&&typeof l.session=="string"&&Jn(l.session,l.agentId||void 0,l.state==="working"?"working":"idle")}catch{}},s.onclose=()=>{xe===s&&(xe=null,it=setTimeout(()=>{it=null,jt()},5e3))},s.onerror=()=>{try{s.close()}catch{}}}catch{xe=null}}window.__openSession=ye,window.__refreshSidebar=_,_(),Qe(),ot(),jt(),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&ot()}),setInterval(()=>{document.visibilityState==="visible"&&_()},1e4);
@@ -1,33 +1,33 @@
1
- import{initTerminal as Oe}from"./terminal-core.js";const A=window.__TMUX_WEB_SHELL__;let p=null,f=null;const _=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null;let h=_;window.__tmuxWebScopeAgent=_;let oe="sessions",I="",q="",V="",ge="";function v(e){return h?"/a/"+encodeURIComponent(h)+e:e}const r=document.getElementById("sidebar-content"),T=document.getElementById("main-placeholder"),b=document.getElementById("terminal-container"),D=document.getElementById("file-editor"),We=document.getElementById("fe-path"),E=document.getElementById("fe-status"),P=document.getElementById("fe-content"),H=document.getElementById("fe-save"),Ue=document.getElementById("fe-delete"),ze=document.getElementById("fe-back"),ye=document.getElementById("fe-new-name"),Fe=document.getElementById("fe-new-btn"),ae=document.getElementById("settings-backdrop"),he=document.getElementById("settings-popover"),be=document.querySelector(".sidebar");function Ge(){return window.matchMedia("(max-width: 640px)").matches}function le(){Ge()&&be.classList.add("collapsed")}function we(){be.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{we(),Le("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{we(),Le("files")});function Le(e){oe=e,document.querySelectorAll(".mode-btn").forEach(t=>t.classList.remove("active"));const n=document.getElementById(e==="sessions"?"mode-sessions":"mode-files");n&&n.classList.add("active"),e==="sessions"?(ce="",B()):ot()}let ce="";async function B(){if(oe!=="sessions")return;try{const t=await(await fetch("/api/sidebar/sessions")).json(),s=JSON.stringify({recent:t.recent,agents:t.agents,tombstones:t.tombstones});if(s===ce)return;ce=s,Xe(t)}catch{r.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}const e=document.getElementById("ns-btn");e&&e.addEventListener("click",()=>{const n=window.__openNewSessionModal;typeof n=="function"?n():document.getElementById("new-session-modal")?.classList.add("open")}),me()}const Ee="tmux-web-sidebar-group-collapsed";function ke(){try{return new Set(JSON.parse(localStorage.getItem(Ee)||"[]"))}catch{return new Set}}function qe(e){try{localStorage.setItem(Ee,JSON.stringify([...e]))}catch{}}function Ve(e,n,t){if(!e)return"\u2026";const s=t&&(e===t||e.startsWith(t+"/"))?"~"+e.slice(t.length):e,i=s.split("/").filter(Boolean);if(i.length===0)return e;for(let o=1;o<=i.length;o++){const a=i.slice(-o).join("/");if(!n.some(l=>!l||l===e?!1:(t&&(l===t||l.startsWith(t+"/"))?"~"+l.slice(t.length):l).split("/").filter(Boolean).slice(-o).join("/")===a))return a}return s}function Se(e,n,t,s=!0){const i=new Map;for(const c of e){const l=c.path||"";i.has(l)||i.set(l,[]),i.get(l).push(c)}const o=[...i.keys()],a=ke();for(const[c,l]of i){const m=Ve(c,o,n),L=a.has(c),y=document.createElement("div");y.className="session-group-header"+(L?" collapsed":""),y.title=c||"No working directory",y.innerHTML=`<span class="sg-folder">&#128193;</span><span class="sg-label">${d(m)}</span>`,y.addEventListener("click",()=>{const M=ke(),ve=y.nextElementSibling;M.has(c)?(M.delete(c),y.classList.remove("collapsed"),ve?.classList.remove("collapsed")):(M.add(c),y.classList.add("collapsed"),ve?.classList.add("collapsed")),qe(M)});const ie=document.createElement("div");ie.className="session-group-body"+(L?" collapsed":"");for(const M of l)ie.appendChild(t?Je(M,t,s):Ke(M));r.appendChild(y),r.appendChild(ie)}}function Je(e,n,t){const s=document.createElement("div");return s.className="session-item"+(e.name===f&&h===n?" active":"")+(t?"":" session-offline"),s.setAttribute("data-session",e.name),s.setAttribute("data-agent",n),s.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${e.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
2
- <span>${d(e.name)}</span>${t?"":'<span class="meta">offline</span>'}`,t&&s.addEventListener("click",i=>{i.target.closest(".session-edit-btn")||j(e.name,n)}),s}function Ke(e){const n=document.createElement("div");n.className="session-item"+(e.name===f&&!h?" active":""),n.setAttribute("data-session",e.name),n.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${e.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
1
+ import{initTerminal as Ue}from"./terminal-core.js";const _=window.__TMUX_WEB_SHELL__;let f=null,u=null;const D=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null;let h=D;window.__tmuxWebScopeAgent=D;let ae="sessions",I="",q="",V="",ye="";function v(e){return h?"/a/"+encodeURIComponent(h)+e:e}const r=document.getElementById("sidebar-content"),M=document.getElementById("main-placeholder"),b=document.getElementById("terminal-container"),P=document.getElementById("file-editor"),We=document.getElementById("fe-path"),E=document.getElementById("fe-status"),j=document.getElementById("fe-content"),H=document.getElementById("fe-save"),ze=document.getElementById("fe-delete"),Fe=document.getElementById("fe-back"),he=document.getElementById("fe-new-name"),Ge=document.getElementById("fe-new-btn"),le=document.getElementById("settings-backdrop"),be=document.getElementById("settings-popover"),we=document.querySelector(".sidebar");function qe(){return window.matchMedia("(max-width: 640px)").matches}function ce(){qe()&&we.classList.add("collapsed")}function Le(){we.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{Le(),Ee("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{Le(),Ee("files")});function Ee(e){ae=e,document.querySelectorAll(".mode-btn").forEach(t=>t.classList.remove("active"));const n=document.getElementById(e==="sessions"?"mode-sessions":"mode-files");n&&n.classList.add("active"),e==="sessions"?(re="",B()):at()}let re="";async function B(){if(ae!=="sessions")return;try{const t=await(await fetch("/api/sidebar/sessions")).json(),s=JSON.stringify({recent:t.recent,agents:t.agents,tombstones:t.tombstones});if(s===re)return;re=s,Xe(t)}catch{r.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}const e=document.getElementById("ns-btn");e&&e.addEventListener("click",()=>{const n=window.__openNewSessionModal;typeof n=="function"?n():document.getElementById("new-session-modal")?.classList.add("open")}),ve()}const ke="tmux-web-sidebar-group-collapsed";function Se(){try{return new Set(JSON.parse(localStorage.getItem(ke)||"[]"))}catch{return new Set}}function Ve(e){try{localStorage.setItem(ke,JSON.stringify([...e]))}catch{}}function Je(e,n,t){if(!e)return"\u2026";const s=t&&(e===t||e.startsWith(t+"/"))?"~"+e.slice(t.length):e,i=s.split("/").filter(Boolean);if(i.length===0)return e;for(let o=1;o<=i.length;o++){const a=i.slice(-o).join("/");if(!n.some(l=>!l||l===e?!1:(t&&(l===t||l.startsWith(t+"/"))?"~"+l.slice(t.length):l).split("/").filter(Boolean).slice(-o).join("/")===a))return a}return s}function Ce(e,n,t,s=!0){const i=new Map;for(const c of e){const l=c.path||"";i.has(l)||i.set(l,[]),i.get(l).push(c)}const o=[...i.keys()],a=Se();for(const[c,l]of i){const p=Je(c,o,n),L=a.has(c),y=document.createElement("div");y.className="session-group-header"+(L?" collapsed":""),y.title=c||"No working directory",y.innerHTML=`<span class="sg-folder">&#128193;</span><span class="sg-label">${d(p)}</span>`,y.addEventListener("click",()=>{const T=Se(),ge=y.nextElementSibling;T.has(c)?(T.delete(c),y.classList.remove("collapsed"),ge?.classList.remove("collapsed")):(T.add(c),y.classList.add("collapsed"),ge?.classList.add("collapsed")),Ve(T)});const oe=document.createElement("div");oe.className="session-group-body"+(L?" collapsed":"");for(const T of l)oe.appendChild(t?Ke(T,t,s):Ye(T));r.appendChild(y),r.appendChild(oe)}}function Ke(e,n,t){const s=document.createElement("div");return s.className="session-item"+(e.name===u&&h===n?" active":"")+(t?"":" session-offline"),s.setAttribute("data-session",e.name),s.setAttribute("data-agent",n),s.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${e.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
2
+ <span>${d(e.name)}</span>${t?"":'<span class="meta">offline</span>'}`,t&&s.addEventListener("click",i=>{i.target.closest(".session-edit-btn")||O(e.name,n)}),s}function Ye(e){const n=document.createElement("div");n.className="session-item"+(e.name===u&&!h?" active":""),n.setAttribute("data-session",e.name),n.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${e.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
3
3
  <span>${d(e.name)}</span>
4
4
  <button class="session-edit-btn" data-session="${d(e.name)}" title="Edit session"><svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 000-1.41l-2.34-2.34a1 1 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>
5
- `,n.addEventListener("click",s=>{s.target.closest(".session-edit-btn")||j(e.name)});const t=n.querySelector(".session-edit-btn");return t&&t.addEventListener("click",s=>{s.stopPropagation(),Ae(e.name,t)}),n}function Xe(e){r.innerHTML='<button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';const n=e.recent||[],t=document.createElement("div");t.className="sidebar-section-label"+(_?" clickable":""),t.textContent=_?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",_&&(t.title="Back to hub",t.addEventListener("click",()=>{window.location.href="/"})),r.appendChild(t),Se(n,e.home);const s=e.agents||[];for(const o of s){if(!o.sessions||o.sessions.length===0)continue;const a=o.agentId===h,c=document.createElement("div");c.className="sidebar-section-label machine-label"+(o.online?"":" agent-offline-label")+(!a&&o.online?" clickable":""),c.textContent=o.agentName+(o.online?"":" (offline)"),!a&&o.online&&(c.title="\u6253\u5F00 "+o.agentName,c.addEventListener("click",()=>{window.location.href="/a/"+encodeURIComponent(o.agentId)+"/"})),r.appendChild(c),Se(o.sessions,e.home,o.agentId,o.online)}const i=e.tombstones||[];if(i.length>0){const o=document.createElement("div");o.className="sidebar-section-label tombstone-label",o.textContent="\u5931\u6548\u4F1A\u8BDD",r.appendChild(o);for(const a of i){const c=document.createElement("div");c.className="session-item session-tombstone",c.setAttribute("data-session",a.name),a.agentId&&c.setAttribute("data-agent",a.agentId);const l=a.agentId?a.agentOnline?" (agent)":" (agent \u79BB\u7EBF)":"";c.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"></svg>
5
+ `,n.addEventListener("click",s=>{s.target.closest(".session-edit-btn")||O(e.name)});const t=n.querySelector(".session-edit-btn");return t&&t.addEventListener("click",s=>{s.stopPropagation(),_e(e.name,t)}),n}function Xe(e){r.innerHTML='<button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';const n=e.recent||[],t=document.createElement("div");t.className="sidebar-section-label"+(D?" clickable":""),t.textContent=D?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",D&&(t.title="Back to hub",t.addEventListener("click",()=>{window.location.href="/"})),r.appendChild(t),Ce(n,e.home);const s=e.agents||[];for(const o of s){if(!o.sessions||o.sessions.length===0)continue;const a=o.agentId===h,c=document.createElement("div");c.className="sidebar-section-label machine-label"+(o.online?"":" agent-offline-label")+(!a&&o.online?" clickable":""),c.textContent=o.agentName+(o.online?"":" (offline)"),!a&&o.online&&(c.title="\u6253\u5F00 "+o.agentName,c.addEventListener("click",()=>{window.location.href="/a/"+encodeURIComponent(o.agentId)+"/"})),r.appendChild(c),Ce(o.sessions,e.home,o.agentId,o.online)}const i=e.tombstones||[];if(i.length>0){const o=document.createElement("div");o.className="sidebar-section-label tombstone-label",o.textContent="\u5931\u6548\u4F1A\u8BDD",r.appendChild(o);for(const a of i){const c=document.createElement("div");c.className="session-item session-tombstone",c.setAttribute("data-session",a.name),a.agentId&&c.setAttribute("data-agent",a.agentId);const l=a.agentId?a.agentOnline?" (agent)":" (agent \u79BB\u7EBF)":"";c.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"></svg>
6
6
  <span>${d(a.name)}</span><span class="meta">\u5931\u6548${l}</span>
7
- <button class="session-edit-btn" data-session="${d(a.name)}" title="\u6062\u590D\u4F1A\u8BDD"><svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 000-1.41l-2.34-2.34a1 1 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>`;const m=c.querySelector(".session-edit-btn");m&&m.addEventListener("click",L=>{L.stopPropagation(),Ae(a.name,m,{tombstone:!0,agentId:a.agentId})}),r.appendChild(c)}}}function d(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function j(e,n){if(!(f===e&&p&&h===(n||null))){D.style.display="none",T.style.display="none",b.style.display="",b.classList.add("terminal-pending"),le(),p&&(te(),p.destroy(),p=null),f=e,h=n||null,window.__tmuxWebScopeAgent=h,window.__tmuxWebCurrentSession=f,yt(e,n);for(const t of r.querySelectorAll(".session-item"))t.classList.toggle("active",t.getAttribute("data-session")===e&&(t.getAttribute("data-agent")||null)===h);try{p=await Oe(b,e,{terminal:A.terminal,scrollback:A.scrollback,theme:A.theme,wsBase:h?"/ws/a/"+encodeURIComponent(h):A.wsBase||"/ws"}),$e()}catch(t){console.error("[shell] terminal init failed:",t);const s=t&&typeof t=="object"&&"message"in t?t.message:String(t);b.textContent="Failed to open terminal for "+e+": "+s,b.classList.remove("terminal-pending")}}}let J=[],u=null,Ce=!1;function Ye(e){if(!u||!u.files.length)return null;const n=u.repoRoot;if(!n||!e.startsWith(n))return null;const t=e.slice(n.length+1);for(const s of u.files)if(s.path===t)return s.status;return null}async function Ze(e){Ce=!0;try{const n=await fetch(v("/api/git/status?path="+encodeURIComponent(e)));n.ok?u=await n.json():u=null}catch{u=null}finally{Ce=!1}}function Qe(){if(!u||!u.branch)return"";const{branch:e,linesAdded:n,linesRemoved:t}=u;let s=`<div class="sidebar-git"><div class="git-branch-badge">
7
+ <button class="session-edit-btn" data-session="${d(a.name)}" title="\u6062\u590D\u4F1A\u8BDD"><svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 000-1.41l-2.34-2.34a1 1 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>`;const p=c.querySelector(".session-edit-btn");p&&p.addEventListener("click",L=>{L.stopPropagation(),_e(a.name,p,{tombstone:!0,agentId:a.agentId})}),r.appendChild(c)}}}function d(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function O(e,n){if(u===e&&f&&h===(n||null))return;P.style.display="none",M.style.display="none",b.style.display="",b.classList.add("terminal-pending"),ce(),f&&(te(),f.destroy(),f=null),u=e,h=n||null,window.__tmuxWebScopeAgent=h,window.__tmuxWebCurrentSession=u,bt(e,n);const t=me(e,n);je.set(t,Date.now()+yt);const s=A.get(t);if(s&&s.state==="working"){s.state="idle",s.doneUntil=void 0,s.unread=!1;const i=r.querySelector(n?'.session-item[data-agent="'+CSS.escape(n)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])');i&&ie(i,t,s)}for(const i of r.querySelectorAll(".session-item"))i.classList.toggle("active",i.getAttribute("data-session")===e&&(i.getAttribute("data-agent")||null)===h);try{f=await Ue(b,e,{terminal:_.terminal,scrollback:_.scrollback,theme:_.theme,wsBase:h?"/ws/a/"+encodeURIComponent(h):_.wsBase||"/ws"}),Ae()}catch(i){console.error("[shell] terminal init failed:",i);const o=i&&typeof i=="object"&&"message"in i?i.message:String(i);b.textContent="Failed to open terminal for "+e+": "+o,b.classList.remove("terminal-pending")}}let J=[],m=null,Ie=!1;function Ze(e){if(!m||!m.files.length)return null;const n=m.repoRoot;if(!n||!e.startsWith(n))return null;const t=e.slice(n.length+1);for(const s of m.files)if(s.path===t)return s.status;return null}async function Qe(e){Ie=!0;try{const n=await fetch(v("/api/git/status?path="+encodeURIComponent(e)));n.ok?m=await n.json():m=null}catch{m=null}finally{Ie=!1}}function et(){if(!m||!m.branch)return"";const{branch:e,linesAdded:n,linesRemoved:t}=m;let s=`<div class="sidebar-git"><div class="git-branch-badge">
8
8
  <svg viewBox="0 0 16 16" fill="currentColor"><path d="M11.63 1.22a.75.75 0 00-1.06 0L5.99 5.8 4.12 3.93a.75.75 0 10-1.06 1.06L4.93 6.87A3.5 3.5 0 003 10.09v3.16a.75.75 0 001 0v-3.16a2.5 2.5 0 012.5-2.5h.27l-1.06 1.06a.75.75 0 001.06 1.06l2.37-2.38a.75.75 0 000-1.06L7.59 5.27l4.04-4.05a.75.75 0 000-1.06L11.63 1.22zM3.25 13.94a.75.75 0 01-.75-.75v-.69a.75.75 0 011.5 0v.69c0 .414-.336.75-.75.75z"/></svg>
9
- ${d(e)}</div>`;return(n>0||t>0)&&(s+=`<div class="git-diff-stats">+<span class="add">${n}</span> <span class="del">-${t}</span></div>`),s+"</div>"}r.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&R&&u&&(e.stopPropagation(),Te())});const O=document.getElementById("header-git"),Ie=document.getElementById("git-diff-view"),Be=document.getElementById("gd-path"),K=document.getElementById("gd-status"),X=document.getElementById("gd-content"),et=document.getElementById("gd-back");let R=null;function W(e){if(!e||!e.branch){O.style.display="none",R=null;return}R=e.repoRoot;const{branch:n,linesAdded:t,linesRemoved:s}=e;let i=`<span class="branch">${d(n)}</span>`;(t>0||s>0)&&(i+=`<span class="sep">\xB7</span>+<span class="diff-add">${t}</span> <span class="diff-del">-${s}</span>`),O.innerHTML=i,O.style.display="block"}O.addEventListener("click",e=>{R&&u&&(e.stopPropagation(),Te())});const g=document.getElementById("git-popover"),Y=document.getElementById("git-popover-backdrop");function re(){const e=O.getBoundingClientRect();g.style.top=e.bottom+4+"px",g.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function xe(){const e=R,n=document.getElementById("git-popover-browse");n&&n.addEventListener("click",()=>{if(Q(),e){oe="files",document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));const t=document.getElementById("mode-files");t&&t.classList.add("active"),x(e)}})}let Me="";function Te(){if(!u)return;const e=u.files||[];if(!e.length)g.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
9
+ ${d(e)}</div>`;return(n>0||t>0)&&(s+=`<div class="git-diff-stats">+<span class="add">${n}</span> <span class="del">-${t}</span></div>`),s+"</div>"}r.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&R&&m&&(e.stopPropagation(),He())});const U=document.getElementById("header-git"),Be=document.getElementById("git-diff-view"),xe=document.getElementById("gd-path"),K=document.getElementById("gd-status"),Y=document.getElementById("gd-content"),tt=document.getElementById("gd-back");let R=null;function W(e){if(!e||!e.branch){U.style.display="none",R=null;return}R=e.repoRoot;const{branch:n,linesAdded:t,linesRemoved:s}=e;let i=`<span class="branch">${d(n)}</span>`;(t>0||s>0)&&(i+=`<span class="sep">\xB7</span>+<span class="diff-add">${t}</span> <span class="diff-del">-${s}</span>`),U.innerHTML=i,U.style.display="block"}U.addEventListener("click",e=>{R&&m&&(e.stopPropagation(),He())});const g=document.getElementById("git-popover"),X=document.getElementById("git-popover-backdrop");function de(){const e=U.getBoundingClientRect();g.style.top=e.bottom+4+"px",g.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function Te(){const e=R,n=document.getElementById("git-popover-browse");n&&n.addEventListener("click",()=>{if(Q(),e){ae="files",document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));const t=document.getElementById("mode-files");t&&t.classList.add("active"),x(e)}})}let Me="";function He(){if(!m)return;const e=m.files||[];if(!e.length)g.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
10
10
  <div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let n=`<div class="git-popover-header">${e.length} file${e.length>1?"s":""} changed</div>`;for(let t=0;t<e.length;t++){const s=e[t],i=s.additions||0,o=s.deletions||0;let a="";switch(s.status){case"M":a='<span class="git-file-status M">M</span>';break;case"A":a='<span class="git-file-status A">A</span>';break;case"D":a='<span class="git-file-status D">D</span>';break;case"?":a='<span class="git-file-status ?">?</span>';break;case"R":a='<span class="git-file-status R">R</span>';break}n+=`<div class="git-popover-item" data-idx="${t}">
11
11
  ${a}<span class="file-path">${d(s.path)}</span>
12
12
  <span class="file-add">${i>0?"+"+i:""}</span>
13
13
  <span class="file-del">${o>0?"-"+o:""}</span>
14
- </div>`}n+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',Me=n,g.innerHTML=n,g.querySelectorAll(".git-popover-item").forEach(t=>{const s=parseInt(t.dataset.idx||"0",10),i=e[s];!i||i.status==="D"||(t.classList.add("clickable"),t.style.cursor="pointer",t.addEventListener("click",()=>Ne(i.path)))})}re(),g.classList.add("open"),Y.classList.add("open"),xe()}function tt(){return window.innerWidth>=1024}function He(e,n){const s=(e+(n&&e?`
14
+ </div>`}n+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',Me=n,g.innerHTML=n,g.querySelectorAll(".git-popover-item").forEach(t=>{const s=parseInt(t.dataset.idx||"0",10),i=e[s];!i||i.status==="D"||(t.classList.add("clickable"),t.style.cursor="pointer",t.addEventListener("click",()=>Re(i.path)))})}de(),g.classList.add("open"),X.classList.add("open"),Te()}function nt(){return window.innerWidth>=1024}function Ne(e,n){const s=(e+(n&&e?`
15
15
  `:"")+n).split(`
16
- `);let i="",o=0,a=0;for(const c of s){const l=c;if(l.startsWith("@@")){const m=l.match(/@@\s+-(\d+)[^+]*\+(\d+)/);m&&(o=parseInt(m[1],10)-1,a=parseInt(m[2],10)-1),i+=`<div class="diff-line hunk"><span class="ln-body">${d(l)}</span></div>`;continue}if(l.startsWith("diff --git")||l.startsWith("index ")||l.startsWith("--- ")||l.startsWith("+++ ")||l.startsWith("new file")||l.startsWith("deleted file")){l.startsWith("diff --git")&&(i+=`<div class="diff-line header"><span class="ln-body">${d(l)}</span></div>`);continue}if(l.startsWith("+"))a++,i+=`<div class="diff-line add"><span class="ln-no">${a}</span><span class="ln-body">${d(l)}</span></div>`;else if(l.startsWith("-"))o++,i+=`<div class="diff-line del"><span class="ln-no">${o}</span><span class="ln-body">${d(l)}</span></div>`;else if(l.startsWith("\\"))i+=`<div class="diff-line"><span class="ln-body">${d(l)}</span></div>`;else{if(!l&&!o&&!a)continue;o++,a++,i+=`<div class="diff-line"><span class="ln-no">${o}</span><span class="ln-no" style="margin-right:12px">${a}</span><span class="ln-body">${d(l)}</span></div>`}}return{html:i,lineCount:s.length}}let Z=null;async function Ne(e){const n=R;if(n)if(Q(),tt()){b.style.display="none",p&&(te(),p.destroy(),p=null,Z=f,f=null),W(null),D.style.display="none",T.style.display="none",Ie.style.display="flex",Be.textContent=e,K.textContent="Loading\u2026",X.innerHTML="";try{const t=await fetch(v("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();st(e,s.diff,s.stagedDiff)}catch(t){const s=t instanceof Error?t.message:String(t);K.textContent="Error",X.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${d(s)}</div>`}}else{g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
16
+ `);let i="",o=0,a=0;for(const c of s){const l=c;if(l.startsWith("@@")){const p=l.match(/@@\s+-(\d+)[^+]*\+(\d+)/);p&&(o=parseInt(p[1],10)-1,a=parseInt(p[2],10)-1),i+=`<div class="diff-line hunk"><span class="ln-body">${d(l)}</span></div>`;continue}if(l.startsWith("diff --git")||l.startsWith("index ")||l.startsWith("--- ")||l.startsWith("+++ ")||l.startsWith("new file")||l.startsWith("deleted file")){l.startsWith("diff --git")&&(i+=`<div class="diff-line header"><span class="ln-body">${d(l)}</span></div>`);continue}if(l.startsWith("+"))a++,i+=`<div class="diff-line add"><span class="ln-no">${a}</span><span class="ln-body">${d(l)}</span></div>`;else if(l.startsWith("-"))o++,i+=`<div class="diff-line del"><span class="ln-no">${o}</span><span class="ln-body">${d(l)}</span></div>`;else if(l.startsWith("\\"))i+=`<div class="diff-line"><span class="ln-body">${d(l)}</span></div>`;else{if(!l&&!o&&!a)continue;o++,a++,i+=`<div class="diff-line"><span class="ln-no">${o}</span><span class="ln-no" style="margin-right:12px">${a}</span><span class="ln-body">${d(l)}</span></div>`}}return{html:i,lineCount:s.length}}let Z=null;async function Re(e){const n=R;if(n)if(Q(),nt()){b.style.display="none",f&&(te(),f.destroy(),f=null,Z=u,u=null),W(null),P.style.display="none",M.style.display="none",Be.style.display="flex",xe.textContent=e,K.textContent="Loading\u2026",Y.innerHTML="";try{const t=await fetch(v("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();it(e,s.diff,s.stagedDiff)}catch(t){const s=t instanceof Error?t.message:String(t);K.textContent="Error",Y.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${d(s)}</div>`}}else{g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
17
17
  <button class="diff-back">\u2190 Back</button>
18
18
  <span class="diff-filename">${d(e)}</span>
19
19
  <span class="diff-loading">Loading\u2026</span>
20
- </div></div>`,re(),g.classList.add("open"),Y.classList.add("open");try{const t=await fetch(v("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();nt(e,s.diff,s.stagedDiff)}catch(t){const s=t instanceof Error?t.message:String(t);g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
20
+ </div></div>`,de(),g.classList.add("open"),X.classList.add("open");try{const t=await fetch(v("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();st(e,s.diff,s.stagedDiff)}catch(t){const s=t instanceof Error?t.message:String(t);g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
21
21
  <button class="diff-back">\u2190 Back</button>
22
22
  <span class="diff-filename">${d(e)}</span>
23
- </div><div class="diff-empty">Error loading diff: ${d(s)}</div></div>`}}}function nt(e,n,t){if(!n&&!t){g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
23
+ </div><div class="diff-empty">Error loading diff: ${d(s)}</div></div>`}}}function st(e,n,t){if(!n&&!t){g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
24
24
  <button class="diff-back">\u2190 Back</button>
25
25
  <span class="diff-filename">${d(e)}</span>
26
- </div><div class="diff-empty">No diff content available</div></div>`;return}const{html:s,lineCount:i}=He(n,t);g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
26
+ </div><div class="diff-empty">No diff content available</div></div>`;return}const{html:s,lineCount:i}=Ne(n,t);g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
27
27
  <button class="diff-back">\u2190 Back</button>
28
28
  <span class="diff-filename">${d(e)}</span>
29
29
  <span class="diff-filesize">${i} lines</span>
30
- </div><div class="diff-content">${s}</div></div>`,g.querySelector(".diff-back")?.addEventListener("click",()=>{g.innerHTML=Me,re(),u&&g.querySelectorAll(".git-popover-item").forEach(o=>{const a=parseInt(o.dataset.idx||"0",10),c=u.files[a];!c||c.status==="D"||(o.style.cursor="pointer",o.addEventListener("click",()=>Ne(c.path)))}),xe()})}function st(e,n,t){if(!n&&!t){K.textContent="No diff",X.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}const{html:s,lineCount:i}=He(n,t);Be.textContent=e,K.textContent=i+" lines",X.innerHTML=s}function it(){Ie.style.display="none",Z?(b.style.display="",b.classList.add("terminal-pending"),j(Z),Z=null):f?(b.style.display="",b.classList.add("terminal-pending"),$e()):T.style.display="flex"}et.addEventListener("click",it);function Q(){g.classList.remove("open"),Y.classList.remove("open")}Y.addEventListener("click",Q),document.addEventListener("keydown",e=>{e.key==="Escape"&&(Q(),se())});async function Re(e){try{const t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(t.path){const s=await fetch("/api/git/status?path="+encodeURIComponent(t.path));if(s.ok){const i=await s.json();u=i,W(i);return}}}catch{}u=null,W(null)}let ee=null;function $e(){te(),f&&(Re(f),ee=setInterval(()=>{f&&Re(f)},3e4))}function te(){ee&&(clearInterval(ee),ee=null)}function ot(){if(J=A.fsRoots||[],J.length===0){if(f){at(f);return}r.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}r.innerHTML="";for(const e of J){const n=document.createElement("div");n.className="file-tree-item",n.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${d(e)}`,n.addEventListener("click",()=>x(e)),r.appendChild(n)}}async function at(e){r.innerHTML='<div class="file-tree-empty">Loading...</div>';try{const t=await(await fetch(v("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?x(t.path):r.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{r.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function x(e){I=e,r.innerHTML='<div class="file-tree-empty">Loading...</div>';const n=Ze(e);try{const s=await(await fetch(v("/api/fs/list?path="+encodeURIComponent(e)))).json();await n,r.innerHTML="",W(u),r.innerHTML+=Qe();const i=lt(e);if(i&&!J.some(c=>e===c)){const c=document.createElement("div");c.className="file-tree-item",c.innerHTML="..",c.addEventListener("click",()=>x(i)),r.appendChild(c)}if(s.dirs)for(const a of s.dirs){const c=a.split("/").pop()||a.split("\\").pop(),l=document.createElement("div");l.className="file-tree-item",l.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${d(c)}`,l.addEventListener("click",()=>x(a)),r.appendChild(l)}else r.innerHTML="";const o=s.files||[];for(const a of o){const c=a.split("/").pop()||a.split("\\").pop(),l=Ye(a);let m="",L="";if(l)switch(l){case"M":m="git-mod",L='<span class="git-file-status M">M</span>';break;case"A":m="git-add",L='<span class="git-file-status A">A</span>';break;case"D":m="git-del",L='<span class="git-file-status D">D</span>';break;case"?":m="git-untracked",L='<span class="git-file-status ?">?</span>';break;case"R":m="git-mod",L='<span class="git-file-status R">R</span>';break}const y=document.createElement("div");y.className="file-tree-item file"+(m?" "+m:""),y.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${L}${d(c)}`,y.addEventListener("click",()=>ct(a)),r.appendChild(y)}}catch{r.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function lt(e){const n=e.lastIndexOf("/");return n<=0?null:e.substring(0,n)}async function ct(e){q=e,b.style.display="none",p&&(te(),p.destroy(),p=null,f=null),W(null),le(),T.style.display="none",D.style.display="flex",We.textContent=e,E.textContent="Loading...",H.style.display="none";try{const n=await fetch(v("/api/file?path="+encodeURIComponent(e)));if(!n.ok){const s=await n.json();E.textContent=s.error||"Failed to load",P.value="";return}const t=await n.json();P.value=t.content,V=t.content,ge=t.content,E.textContent=t.size+" bytes",H.style.display=""}catch{E.textContent="Failed to load",P.value=""}}P.addEventListener("input",()=>{V=P.value}),H.addEventListener("click",async()=>{H.textContent="Saving...",H.disabled=!0;try{const e=await fetch(v("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:q,content:V})});if(!e.ok){const n=await e.json();E.textContent=n.error||"Save failed";return}ge=V,E.textContent="Saved"}catch{E.textContent="Save failed"}finally{H.disabled=!1,H.textContent="Save"}}),Ue.addEventListener("click",async()=>{if(confirm("Delete "+q+"?"))try{const e=await fetch(v("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:q})});if(!e.ok){const n=await e.json();E.textContent=n.error||"Delete failed";return}D.style.display="none",T.style.display="flex",I&&x(I)}catch{E.textContent="Delete failed"}}),ze.addEventListener("click",()=>{D.style.display="none",T.style.display="flex",I&&x(I)}),Fe.addEventListener("click",async()=>{const e=ye.value.trim();if(!e)return;const n=I+"/"+e;try{const t=await fetch(v("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:n})});if(!t.ok){const s=await t.json();E.textContent=s.error||"Create failed";return}ye.value="",I&&x(I)}catch{E.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{const e=he.classList.toggle("open");ae.classList.toggle("open",e)}),ae.addEventListener("click",()=>{he.classList.remove("open"),ae.classList.remove("open")});let w="",C=!1,ne;const U=document.createElement("div");U.className="session-popover-backdrop",document.body.appendChild(U);const k=document.createElement("div");k.id="session-popover",k.className="session-popover",k.style.display="none",k.innerHTML=`<div class="sp-field">
30
+ </div><div class="diff-content">${s}</div></div>`,g.querySelector(".diff-back")?.addEventListener("click",()=>{g.innerHTML=Me,de(),m&&g.querySelectorAll(".git-popover-item").forEach(o=>{const a=parseInt(o.dataset.idx||"0",10),c=m.files[a];!c||c.status==="D"||(o.style.cursor="pointer",o.addEventListener("click",()=>Re(c.path)))}),Te()})}function it(e,n,t){if(!n&&!t){K.textContent="No diff",Y.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}const{html:s,lineCount:i}=Ne(n,t);xe.textContent=e,K.textContent=i+" lines",Y.innerHTML=s}function ot(){Be.style.display="none",Z?(b.style.display="",b.classList.add("terminal-pending"),O(Z),Z=null):u?(b.style.display="",b.classList.add("terminal-pending"),Ae()):M.style.display="flex"}tt.addEventListener("click",ot);function Q(){g.classList.remove("open"),X.classList.remove("open")}X.addEventListener("click",Q),document.addEventListener("keydown",e=>{e.key==="Escape"&&(Q(),se())});async function $e(e){try{const t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(t.path){const s=await fetch("/api/git/status?path="+encodeURIComponent(t.path));if(s.ok){const i=await s.json();m=i,W(i);return}}}catch{}m=null,W(null)}let ee=null;function Ae(){te(),u&&($e(u),ee=setInterval(()=>{u&&$e(u)},3e4))}function te(){ee&&(clearInterval(ee),ee=null)}function at(){if(J=_.fsRoots||[],J.length===0){if(u){lt(u);return}r.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}r.innerHTML="";for(const e of J){const n=document.createElement("div");n.className="file-tree-item",n.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${d(e)}`,n.addEventListener("click",()=>x(e)),r.appendChild(n)}}async function lt(e){r.innerHTML='<div class="file-tree-empty">Loading...</div>';try{const t=await(await fetch(v("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?x(t.path):r.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{r.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function x(e){I=e,r.innerHTML='<div class="file-tree-empty">Loading...</div>';const n=Qe(e);try{const s=await(await fetch(v("/api/fs/list?path="+encodeURIComponent(e)))).json();await n,r.innerHTML="",W(m),r.innerHTML+=et();const i=ct(e);if(i&&!J.some(c=>e===c)){const c=document.createElement("div");c.className="file-tree-item",c.innerHTML="..",c.addEventListener("click",()=>x(i)),r.appendChild(c)}if(s.dirs)for(const a of s.dirs){const c=a.split("/").pop()||a.split("\\").pop(),l=document.createElement("div");l.className="file-tree-item",l.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${d(c)}`,l.addEventListener("click",()=>x(a)),r.appendChild(l)}else r.innerHTML="";const o=s.files||[];for(const a of o){const c=a.split("/").pop()||a.split("\\").pop(),l=Ze(a);let p="",L="";if(l)switch(l){case"M":p="git-mod",L='<span class="git-file-status M">M</span>';break;case"A":p="git-add",L='<span class="git-file-status A">A</span>';break;case"D":p="git-del",L='<span class="git-file-status D">D</span>';break;case"?":p="git-untracked",L='<span class="git-file-status ?">?</span>';break;case"R":p="git-mod",L='<span class="git-file-status R">R</span>';break}const y=document.createElement("div");y.className="file-tree-item file"+(p?" "+p:""),y.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${L}${d(c)}`,y.addEventListener("click",()=>rt(a)),r.appendChild(y)}}catch{r.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function ct(e){const n=e.lastIndexOf("/");return n<=0?null:e.substring(0,n)}async function rt(e){q=e,b.style.display="none",f&&(te(),f.destroy(),f=null,u=null),W(null),ce(),M.style.display="none",P.style.display="flex",We.textContent=e,E.textContent="Loading...",H.style.display="none";try{const n=await fetch(v("/api/file?path="+encodeURIComponent(e)));if(!n.ok){const s=await n.json();E.textContent=s.error||"Failed to load",j.value="";return}const t=await n.json();j.value=t.content,V=t.content,ye=t.content,E.textContent=t.size+" bytes",H.style.display=""}catch{E.textContent="Failed to load",j.value=""}}j.addEventListener("input",()=>{V=j.value}),H.addEventListener("click",async()=>{H.textContent="Saving...",H.disabled=!0;try{const e=await fetch(v("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:q,content:V})});if(!e.ok){const n=await e.json();E.textContent=n.error||"Save failed";return}ye=V,E.textContent="Saved"}catch{E.textContent="Save failed"}finally{H.disabled=!1,H.textContent="Save"}}),ze.addEventListener("click",async()=>{if(confirm("Delete "+q+"?"))try{const e=await fetch(v("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:q})});if(!e.ok){const n=await e.json();E.textContent=n.error||"Delete failed";return}P.style.display="none",M.style.display="flex",I&&x(I)}catch{E.textContent="Delete failed"}}),Fe.addEventListener("click",()=>{P.style.display="none",M.style.display="flex",I&&x(I)}),Ge.addEventListener("click",async()=>{const e=he.value.trim();if(!e)return;const n=I+"/"+e;try{const t=await fetch(v("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:n})});if(!t.ok){const s=await t.json();E.textContent=s.error||"Create failed";return}he.value="",I&&x(I)}catch{E.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{const e=be.classList.toggle("open");le.classList.toggle("open",e)}),le.addEventListener("click",()=>{be.classList.remove("open"),le.classList.remove("open")});let w="",C=!1,ne;const z=document.createElement("div");z.className="session-popover-backdrop",document.body.appendChild(z);const k=document.createElement("div");k.id="session-popover",k.className="session-popover",k.style.display="none",k.innerHTML=`<div class="sp-field">
31
31
  <label>Rename session</label>
32
32
  <input type="text" id="sp-name" />
33
33
  </div>
@@ -37,5 +37,5 @@ import{initTerminal as Oe}from"./terminal-core.js";const A=window.__TMUX_WEB_SHE
37
37
  </div>
38
38
  <hr class="sp-divider" />
39
39
  <button class="btn primary" id="sp-rebuild" style="display:none">\u91CD\u5EFA\u540C\u540D\u540C\u8DEF\u5F84 session</button>
40
- <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(k);function rt(e){const n=e.getBoundingClientRect();k.style.top=n.bottom+4+"px",k.style.left=Math.max(8,Math.min(n.left,window.innerWidth-240))+"px"}function Ae(e,n,t){w=e,C=!!t?.tombstone,ne=t?.agentId;const s=document.getElementById("sp-name");s.value=e;const i=k.querySelector(".sp-field label");i.textContent=C?"\u91CD\u547D\u540D (\u4EC5\u8BB0\u5F55\uFF0C\u4E0D\u91CD\u5EFA)":"Rename session";const o=document.getElementById("sp-rebuild");o.style.display=C?"":"none";const a=document.getElementById("sp-delete");a.textContent=C?"\u5220\u9664\u5931\u6548\u8BB0\u5F55 (\u5893\u7891\u6D88\u5931)":"Delete session";const c=document.getElementById("sp-save");c.textContent=C?"\u91CD\u547D\u540D":"Save",U.classList.add("open"),k.style.display="block",rt(n),setTimeout(()=>s.focus(),50)}function N(){U.classList.remove("open"),k.style.display="none"}U.addEventListener("click",N),document.getElementById("sp-save").addEventListener("click",async()=>{const e=document.getElementById("sp-name").value.trim();if(!e||e===w){N();return}try{const n=C?{oldName:w,newName:e,agentId:ne}:{oldName:w,newName:e};if(!(await fetch(v(C?"/api/sessions/rename-tombstone":"/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).ok)return;N(),B(),!C&&f===w&&j(e)}catch{}}),document.getElementById("sp-rebuild").addEventListener("click",async()=>{if(confirm('\u91CD\u5EFA\u4F1A\u8BDD "'+w+'" (\u540C\u540D\u540C\u8DEF\u5F84\uFF0C\u5185\u5BB9\u4E3A\u7A7A)\uFF1F'))try{const e=await fetch(v("/api/sessions/rebuild"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:w,agentId:ne})});if(!e.ok){const n=await e.json();alert(n.error||"\u91CD\u5EFA\u5931\u8D25");return}N(),B()}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(C){if(!confirm('\u5220\u9664\u5931\u6548\u8BB0\u5F55 "'+w+'"\uFF1F'))return;try{if(!(await fetch(v("/api/sessions/dismiss"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:w,agentId:ne})})).ok)return;N(),B()}catch{}return}if(confirm('Delete session "'+w+'"?'))try{if(!(await fetch(v("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:w})})).ok)return;N(),f===w&&(p&&(p.destroy(),p=null),f=null,b.style.display="none",T.style.display="flex"),B()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",N);const dt={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r","ctrl-r":"","ctrl-c":"",btw:"/btw ",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",e=>{const n=e.target.closest("button[data-key]");if(!n||!p)return;const t=dt[n.dataset.key];t&&(p.sendInput(t),p.focus())});const S=document.getElementById("mk-input"),_e=document.getElementById("mk-send");function De(){if(!S||!p)return;const e=S.value;e&&(p.sendInput(e+"\r"),S.value="",S.rows=1,p.focus())}if(S&&(S.addEventListener("input",()=>{S.rows=1;const e=S.value.split(`
41
- `).length;S.rows=Math.min(e,4)}),S.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),De())})),_e&&_e.addEventListener("click",De),window.visualViewport){const e=document.querySelector(".app-layout"),n=()=>{e&&(e.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",n),window.addEventListener("orientationchange",()=>setTimeout(n,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}const de=document.getElementById("process-panel"),z=document.getElementById("proc-list"),pt=document.getElementById("proc-close"),pe=document.getElementById("proc-backdrop"),ft=document.getElementById("header-ram");function ut(){z.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(v("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){z.innerHTML='<div class="proc-empty">No processes</div>';return}let n="";for(const t of e){const s=t.rss,i=s<1048576?(s/1024).toFixed(0)+"K":(s/1048576).toFixed(1)+"M";n+='<div class="proc-row" data-pid="'+t.pid+'"><span class="proc-mem">'+t.mem+'%</span><span class="proc-rss">'+i+'</span><span class="proc-cmd" title="'+d(t.command)+'">'+d(t.command)+'</span><button class="proc-kill" title="Kill PID '+t.pid+'">&times;</button></div>'}z.innerHTML=n;for(const t of z.querySelectorAll(".proc-row")){const s=t.querySelector(".proc-kill");s&&s.addEventListener("click",i=>{const o=i.target.closest(".proc-row");!o||!confirm("Kill PID "+o.dataset.pid+"?")||fetch(v("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(o.dataset.pid,10)})}).then(a=>a.json()).then(a=>{a.ok?o.style.opacity="0.3":alert("Failed: "+(a.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{z.innerHTML='<div class="proc-empty">Failed to load</div>'})}function mt(){de.classList.add("open"),pe.classList.add("open"),ut()}function se(){de.classList.remove("open"),pe.classList.remove("open")}ft.addEventListener("click",e=>{e.stopPropagation(),de.classList.contains("open")?se():mt()}),pt.addEventListener("click",se),pe.addEventListener("click",se);const vt=2e3;let F=null,fe=null;const $=new Map,G=new Map;function Pe(e,n){return(n?"a:"+n+":":"l:")+e}function gt(e,n,t){const s=Pe(e,n),i=G.get(s),o=e===f&&(n??null)===h,a=t==="idle"&&i?.state==="working";G.set(s,{session:e,agentId:n,state:t,doneUntil:a?Date.now()+vt:void 0,unread:a?!o:!1});const c=n?'.session-item[data-agent="'+CSS.escape(n)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])',l=r.querySelector(c);l&&ue(l,s,G.get(s))}function ue(e,n,t){const s=$.get(n);s&&(clearTimeout(s),$.delete(n)),e.classList.remove("working","just-done");const i=t.session===f&&(t.agentId??null)===h;t.state==="working"?e.classList.add("working"):i&&t.doneUntil&&Date.now()<t.doneUntil?(e.classList.add("just-done"),$.set(n,setTimeout(()=>{e.classList.remove("just-done"),$.delete(n)},Math.max(0,t.doneUntil-Date.now())))):!i&&t.unread&&e.classList.add("just-done")}function me(){for(const[e,n]of G){const t=n.agentId?'.session-item[data-agent="'+CSS.escape(n.agentId)+'"][data-session="'+CSS.escape(n.session)+'"]':'.session-item[data-session="'+CSS.escape(n.session)+'"]:not([data-agent])',s=r.querySelector(t);s&&ue(s,e,n)}}function yt(e,n){const t=Pe(e,n),s=G.get(t);if(!s)return;const i=$.get(t);i&&(clearTimeout(i),$.delete(t)),s.doneUntil=void 0,s.unread=!1;const o=n?'.session-item[data-agent="'+CSS.escape(n)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])',a=r.querySelector(o);a&&ue(a,t,s)}function je(){if(F||fe)return;const e=localStorage.getItem("tmux-web-token");let t=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/activity";e&&(t+="?token="+encodeURIComponent(e));try{const s=new WebSocket(t);F=s,s.onmessage=i=>{try{const o=JSON.parse(String(i.data));o&&o.type==="session.activity"&&typeof o.session=="string"&&gt(o.session,o.agentId||void 0,o.state==="working"?"working":"idle")}catch{}},s.onclose=()=>{F===s&&(F=null,fe=setTimeout(()=>{fe=null,je()},5e3))},s.onerror=()=>{try{s.close()}catch{}}}catch{F=null}}window.__openSession=j,window.__refreshSidebar=B,B(),le(),me(),je(),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&me()}),setInterval(()=>{document.visibilityState==="visible"&&B()},1e4);
40
+ <button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(k);function dt(e){const n=e.getBoundingClientRect();k.style.top=n.bottom+4+"px",k.style.left=Math.max(8,Math.min(n.left,window.innerWidth-240))+"px"}function _e(e,n,t){w=e,C=!!t?.tombstone,ne=t?.agentId;const s=document.getElementById("sp-name");s.value=e;const i=k.querySelector(".sp-field label");i.textContent=C?"\u91CD\u547D\u540D (\u4EC5\u8BB0\u5F55\uFF0C\u4E0D\u91CD\u5EFA)":"Rename session";const o=document.getElementById("sp-rebuild");o.style.display=C?"":"none";const a=document.getElementById("sp-delete");a.textContent=C?"\u5220\u9664\u5931\u6548\u8BB0\u5F55 (\u5893\u7891\u6D88\u5931)":"Delete session";const c=document.getElementById("sp-save");c.textContent=C?"\u91CD\u547D\u540D":"Save",z.classList.add("open"),k.style.display="block",dt(n),setTimeout(()=>s.focus(),50)}function N(){z.classList.remove("open"),k.style.display="none"}z.addEventListener("click",N),document.getElementById("sp-save").addEventListener("click",async()=>{const e=document.getElementById("sp-name").value.trim();if(!e||e===w){N();return}try{const n=C?{oldName:w,newName:e,agentId:ne}:{oldName:w,newName:e};if(!(await fetch(v(C?"/api/sessions/rename-tombstone":"/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).ok)return;N(),B(),!C&&u===w&&O(e)}catch{}}),document.getElementById("sp-rebuild").addEventListener("click",async()=>{if(confirm('\u91CD\u5EFA\u4F1A\u8BDD "'+w+'" (\u540C\u540D\u540C\u8DEF\u5F84\uFF0C\u5185\u5BB9\u4E3A\u7A7A)\uFF1F'))try{const e=await fetch(v("/api/sessions/rebuild"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:w,agentId:ne})});if(!e.ok){const n=await e.json();alert(n.error||"\u91CD\u5EFA\u5931\u8D25");return}N(),B()}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(C){if(!confirm('\u5220\u9664\u5931\u6548\u8BB0\u5F55 "'+w+'"\uFF1F'))return;try{if(!(await fetch(v("/api/sessions/dismiss"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:w,agentId:ne})})).ok)return;N(),B()}catch{}return}if(confirm('Delete session "'+w+'"?'))try{if(!(await fetch(v("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:w})})).ok)return;N(),u===w&&(f&&(f.destroy(),f=null),u=null,b.style.display="none",M.style.display="flex"),B()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",N);const pt={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r","ctrl-r":"","ctrl-c":"",btw:"/btw ",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",e=>{const n=e.target.closest("button[data-key]");if(!n||!f)return;const t=pt[n.dataset.key];t&&(f.sendInput(t),f.focus())});const S=document.getElementById("mk-input"),De=document.getElementById("mk-send");function Pe(){if(!S||!f)return;const e=S.value;e&&(f.sendInput(e+"\r"),S.value="",S.rows=1,f.focus())}if(S&&(S.addEventListener("input",()=>{S.rows=1;const e=S.value.split(`
41
+ `).length;S.rows=Math.min(e,4)}),S.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),Pe())})),De&&De.addEventListener("click",Pe),window.visualViewport){const e=document.querySelector(".app-layout"),n=()=>{e&&(e.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",n),window.addEventListener("orientationchange",()=>setTimeout(n,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}const pe=document.getElementById("process-panel"),F=document.getElementById("proc-list"),ft=document.getElementById("proc-close"),fe=document.getElementById("proc-backdrop"),ut=document.getElementById("header-ram");function mt(){F.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(v("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){F.innerHTML='<div class="proc-empty">No processes</div>';return}let n="";for(const t of e){const s=t.rss,i=s<1048576?(s/1024).toFixed(0)+"K":(s/1048576).toFixed(1)+"M";n+='<div class="proc-row" data-pid="'+t.pid+'"><span class="proc-mem">'+t.mem+'%</span><span class="proc-rss">'+i+'</span><span class="proc-cmd" title="'+d(t.command)+'">'+d(t.command)+'</span><button class="proc-kill" title="Kill PID '+t.pid+'">&times;</button></div>'}F.innerHTML=n;for(const t of F.querySelectorAll(".proc-row")){const s=t.querySelector(".proc-kill");s&&s.addEventListener("click",i=>{const o=i.target.closest(".proc-row");!o||!confirm("Kill PID "+o.dataset.pid+"?")||fetch(v("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(o.dataset.pid,10)})}).then(a=>a.json()).then(a=>{a.ok?o.style.opacity="0.3":alert("Failed: "+(a.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{F.innerHTML='<div class="proc-empty">Failed to load</div>'})}function vt(){pe.classList.add("open"),fe.classList.add("open"),mt()}function se(){pe.classList.remove("open"),fe.classList.remove("open")}ut.addEventListener("click",e=>{e.stopPropagation(),pe.classList.contains("open")?se():vt()}),ft.addEventListener("click",se),fe.addEventListener("click",se);const gt=2e3;let G=null,ue=null;const $=new Map,A=new Map,yt=5e3,je=new Map;function me(e,n){return(n?"a:"+n+":":"l:")+e}function ht(e,n,t){const s=me(e,n);if(t==="working"){const p=je.get(s);if(p&&Date.now()<p)return}const i=A.get(s),o=e===u&&(n??null)===h,a=t==="idle"&&i?.state==="working";A.set(s,{session:e,agentId:n,state:t,doneUntil:a?Date.now()+gt:void 0,unread:a?!o:!1});const c=n?'.session-item[data-agent="'+CSS.escape(n)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])',l=r.querySelector(c);l&&ie(l,s,A.get(s))}function ie(e,n,t){const s=$.get(n);s&&(clearTimeout(s),$.delete(n)),e.classList.remove("working","just-done");const i=t.session===u&&(t.agentId??null)===h;t.state==="working"?e.classList.add("working"):i&&t.doneUntil&&Date.now()<t.doneUntil?(e.classList.add("just-done"),$.set(n,setTimeout(()=>{e.classList.remove("just-done"),$.delete(n)},Math.max(0,t.doneUntil-Date.now())))):!i&&t.unread&&e.classList.add("just-done")}function ve(){for(const[e,n]of A){const t=n.agentId?'.session-item[data-agent="'+CSS.escape(n.agentId)+'"][data-session="'+CSS.escape(n.session)+'"]':'.session-item[data-session="'+CSS.escape(n.session)+'"]:not([data-agent])',s=r.querySelector(t);s&&ie(s,e,n)}}function bt(e,n){const t=me(e,n),s=A.get(t);if(!s)return;const i=$.get(t);i&&(clearTimeout(i),$.delete(t)),s.doneUntil=void 0,s.unread=!1;const o=n?'.session-item[data-agent="'+CSS.escape(n)+'"][data-session="'+CSS.escape(e)+'"]':'.session-item[data-session="'+CSS.escape(e)+'"]:not([data-agent])',a=r.querySelector(o);a&&ie(a,t,s)}function Oe(){if(G||ue)return;const e=localStorage.getItem("tmux-web-token");let t=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/activity";e&&(t+="?token="+encodeURIComponent(e));try{const s=new WebSocket(t);G=s,s.onmessage=i=>{try{const o=JSON.parse(String(i.data));o&&o.type==="session.activity"&&typeof o.session=="string"&&ht(o.session,o.agentId||void 0,o.state==="working"?"working":"idle")}catch{}},s.onclose=()=>{G===s&&(G=null,ue=setTimeout(()=>{ue=null,Oe()},5e3))},s.onerror=()=>{try{s.close()}catch{}}}catch{G=null}}window.__openSession=O,window.__refreshSidebar=B,B(),ce(),ve(),Oe(),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&ve()}),setInterval(()=>{document.visibilityState==="visible"&&B()},1e4);
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import{chmodSync as he,existsSync as z,readdirSync as ge,statSync as ye}from"node:fs";import{createRequire as G}from"node:module";import{hostname as U,networkInterfaces as ke}from"node:os";const ve=G(import.meta.url),{version:Te}=ve("../package.json");import $ from"node:path";import{fileURLToPath as _e}from"node:url";import{serve as we}from"@hono/node-server";import{WebSocketServer as Se,WebSocket as E}from"ws";try{const e=$.dirname(G(import.meta.url).resolve("node-pty/package.json")),t=$.join(e,"prebuilds");if(z(t))for(const o of ge(t)){const n=$.join(t,o,"spawn-helper");z(n)&&!(ye(n).mode&73)&&he(n,493)}}catch{}import{loadSecurityConfig as Ae}from"./lib/security-config.js";import{TokenStore as Ce,hashPassword as be,verifyPassword as xe,validatePassword as Ie}from"./lib/auth.js";import{RateLimiter as Pe}from"./lib/rateLimiter.js";import{audit as d}from"./lib/auditLog.js";import{db as N}from"./lib/db.js";import{handleClientMessage as V}from"./lib/ws-message.js";import{loadDotEnv as Re}from"./lib/load-env.js";import{listSessions as Me,resolveTmuxSocketPath as Oe}from"./sessions.js";import{cmdSetup as $e,cmdTheme as Ne,printUsage as X,printVersion as Le}from"./lib/cli.js";import{readSettings as Ue}from"./lib/settings.js";import{readActiveTheme as Ee}from"./lib/theme-store.js";import{buildApp as Y}from"./lib/build-app.js";import{attachTerminal as Fe,killAllAttachedPtys as je}from"./lib/attach-terminal.js";import{acquireControlClient as K,killAllControlClients as He}from"./lib/tmux-control.js";import{getSessionPaneTarget as Q,capturePaneTail as Z,capturePaneHistoryChunk as q,toCrlf as ee}from"./lib/tmux-capture.js";import{readTerminalBufferConfig as Be}from"./lib/terminal-config.js";import{AgentRegistry as We}from"./lib/agent-registry.js";import{AgentChannel as Je,MAX_RELAY_CONNS_PER_AGENT as De}from"./lib/agent-relay.js";import{findAgentTokenByPlaintext as ze,listAgentTokens as Ge}from"./lib/agent-tokens.js";import{saveSecurityConfig as Ve}from"./lib/security-config.js";import{readFederationConfig as Xe,writeFederationConfig as Ye}from"./lib/federation-config.js";import{startAgentClient as Ke}from"./lib/agent-client.js";import{ActivityProbe as Qe}from"./lib/activity-probe.js";import{upsertSessionState as te}from"./lib/session-state.js";import{sessionWorkingPath as Ze}from"./lib/sessions-sidebar.js";Re();const F=Be(),g=Ae(),v=new Ce,A=new Pe;let J=!1;const qe="tmux-web-token";function j(e){if(g.security.trustProxy){const t=(e.headers["x-forwarded-for"]||"").split(",")[0].trim();if(t)return t}return e.socket.remoteAddress||"unknown"}function H(e){const t=e.headers.authorization;if(typeof t=="string"&&t.startsWith("Bearer "))return t.slice(7);const o=e.headers.cookie;if(typeof o!="string")return null;const n=o.match(new RegExp(`(?:^|;\\s*)${qe}=([^;]+)`));return n?decodeURIComponent(n[1]):null}function Vt(e){const t=H(e);return t?v.validateToken(t):null}const R=new Map;function et(e){let t=0;for(const o of R.values())o.ip===e&&t++;return t}function b(e,t,o){try{e.close(t,o)}catch{}}function ne(e,t){e.readyState===E.OPEN&&e.send(JSON.stringify(t))}const oe=process.argv.slice(2),L=oe;if(L.length>0){const[e,t]=L;switch(e){case"setup":await $e(L),process.exit(0);case"theme":await Ne(L.slice(1)),process.exit(0);case"agent":{const o=$.join($.dirname(_e(import.meta.url)),"agent.js"),{spawn:n}=await import("node:child_process"),f=n(process.execPath,[o,...oe],{stdio:"inherit"});f.on("error",s=>{console.error(s),process.exit(1)}),f.on("exit",s=>process.exit(s??0)),await new Promise(()=>{})}case"agent-token":await pt(L.slice(1)),process.exit(0);case"help":case"--help":case"-h":X(),process.exit(0);case"-V":case"--version":case"-v":Le(),process.exit(0);default:console.error(`unknown argument: ${e}`),X(),process.exit(1)}}function P(e,t){e.readyState===E.OPEN&&e.send(JSON.stringify(t))}const re=new Set;await N.read(),N.data.sessionAccess??=[],N.data.pinnedViews??=[],N.data.watchedPanes??=[],N.data.quickCommands??=[];const se=await Ue();let tt=await Ee();const k=new We({onChange:()=>{}}),_=new Map;function ae(e){return k.get(e)?_.get(e)??null:null}const ie={activeTheme:tt,settingUpPassword:!1},nt=Y({mode:"hub",securityConfig:g,tokenStore:v,rateLimiter:A,settings:se,terminalBufferConfig:F,state:ie,federation:{getConfig:()=>x,setConfig:async e=>{x=e,await Ye(e),le(e)},status:rt,hostname:U()},hub:{agents:k,getChannel:ae}}),ot=Y({mode:"agent",securityConfig:g,tokenStore:v,rateLimiter:A,settings:se,terminalBufferConfig:F,state:ie});let x=await Xe(),M=null;function ce(){M&&(M.stop(),M=null)}function le(e){ce(),!(!e.enabled||!e.hub||!e.token)&&(M=Ke({hub:e.hub,token:e.token,name:e.name?.trim()||U(),version:Te,app:ot,listSessions:Me,terminalBufferConfig:F,getSessionPaneTarget:Q,capturePaneTail:Z,capturePaneHistoryChunk:q,toCrlf:ee,handleClientMessage:V,acquireControlClient:K}))}function rt(){return x.enabled?M?M.status():{state:"idle",agentId:null,hub:x.hub??"",name:x.name??U()}:{state:"idle",agentId:null,hub:x.hub??"",name:x.name??U()}}le(x);const ue=new Qe,B=new Map;setInterval(()=>{(async()=>{try{const e=await ue.scan(),t=[];for(const[o,n]of e)B.get(o)!==n&&t.push({session:o,state:n});for(const o of B.keys())e.has(o)||t.push({session:o,state:"idle"});B.clear();for(const[o,n]of e)B.set(o,n);for(const o of t)de({type:"session.activity",session:o.session,state:o.state})}catch{}})()},ue.scanMs);const st=parseInt(process.env.PORT||"21000",10);function at(){const e=[];try{for(const t of Object.values(ke()))for(const o of t??[])o.family==="IPv4"&&!o.internal&&e.push(o.address)}catch{}return e}const it=we({fetch:nt.fetch,port:st,hostname:"0.0.0.0"},e=>{console.log(`tmux-web running at http://localhost:${e.port}`);for(const t of at())console.log(` also on http://${t}:${e.port}`)}),O=new Se({noServer:!0}),D=new Set;function de(e){const t=JSON.stringify(e);for(const o of D)o.readyState===E.OPEN&&o.send(t)}function I(e,t,o){e.write(`HTTP/1.1 ${t} ${o}\r
2
+ import{chmodSync as he,existsSync as G,readdirSync as ge,statSync as ye}from"node:fs";import{createRequire as V}from"node:module";import{hostname as U,networkInterfaces as ke}from"node:os";const ve=V(import.meta.url),{version:Te}=ve("../package.json");import $ from"node:path";import{fileURLToPath as _e}from"node:url";import{serve as we}from"@hono/node-server";import{WebSocketServer as Se,WebSocket as E}from"ws";try{const e=$.dirname(V(import.meta.url).resolve("node-pty/package.json")),t=$.join(e,"prebuilds");if(G(t))for(const o of ge(t)){const n=$.join(t,o,"spawn-helper");G(n)&&!(ye(n).mode&73)&&he(n,493)}}catch{}import{loadSecurityConfig as Ae}from"./lib/security-config.js";import{TokenStore as Ce,hashPassword as be,verifyPassword as xe,validatePassword as Ie}from"./lib/auth.js";import{RateLimiter as Pe}from"./lib/rateLimiter.js";import{audit as d}from"./lib/auditLog.js";import{db as N}from"./lib/db.js";import{handleClientMessage as X}from"./lib/ws-message.js";import{loadDotEnv as Re}from"./lib/load-env.js";import{listSessions as Me,resolveTmuxSocketPath as Oe}from"./sessions.js";import{cmdSetup as $e,cmdTheme as Ne,printUsage as Y,printVersion as Le}from"./lib/cli.js";import{readSettings as Ue}from"./lib/settings.js";import{readActiveTheme as Ee}from"./lib/theme-store.js";import{buildApp as K}from"./lib/build-app.js";import{attachTerminal as Fe,killAllAttachedPtys as je}from"./lib/attach-terminal.js";import{acquireControlClient as Q,killAllControlClients as He}from"./lib/tmux-control.js";import{getSessionPaneTarget as Z,capturePaneTail as q,capturePaneHistoryChunk as ee,toCrlf as te}from"./lib/tmux-capture.js";import{readTerminalBufferConfig as Be}from"./lib/terminal-config.js";import{AgentRegistry as We}from"./lib/agent-registry.js";import{AgentChannel as Je,MAX_RELAY_CONNS_PER_AGENT as De}from"./lib/agent-relay.js";import{findAgentTokenByPlaintext as ze,listAgentTokens as Ge}from"./lib/agent-tokens.js";import{saveSecurityConfig as Ve}from"./lib/security-config.js";import{readFederationConfig as Xe,writeFederationConfig as Ye}from"./lib/federation-config.js";import{startAgentClient as Ke}from"./lib/agent-client.js";import{ActivityProbe as Qe}from"./lib/activity-probe.js";import{upsertSessionState as ne}from"./lib/session-state.js";import{sessionWorkingPath as Ze}from"./lib/sessions-sidebar.js";Re();const F=Be(),g=Ae(),v=new Ce,A=new Pe;let J=!1;const qe="tmux-web-token";function j(e){if(g.security.trustProxy){const t=(e.headers["x-forwarded-for"]||"").split(",")[0].trim();if(t)return t}return e.socket.remoteAddress||"unknown"}function H(e){const t=e.headers.authorization;if(typeof t=="string"&&t.startsWith("Bearer "))return t.slice(7);const o=e.headers.cookie;if(typeof o!="string")return null;const n=o.match(new RegExp(`(?:^|;\\s*)${qe}=([^;]+)`));return n?decodeURIComponent(n[1]):null}function Vt(e){const t=H(e);return t?v.validateToken(t):null}const R=new Map;function et(e){let t=0;for(const o of R.values())o.ip===e&&t++;return t}function b(e,t,o){try{e.close(t,o)}catch{}}function oe(e,t){e.readyState===E.OPEN&&e.send(JSON.stringify(t))}const re=process.argv.slice(2),L=re;if(L.length>0){const[e,t]=L;switch(e){case"setup":await $e(L),process.exit(0);case"theme":await Ne(L.slice(1)),process.exit(0);case"agent":{const o=$.join($.dirname(_e(import.meta.url)),"agent.js"),{spawn:n}=await import("node:child_process"),f=n(process.execPath,[o,...re],{stdio:"inherit"});f.on("error",s=>{console.error(s),process.exit(1)}),f.on("exit",s=>process.exit(s??0)),await new Promise(()=>{})}case"agent-token":await pt(L.slice(1)),process.exit(0);case"help":case"--help":case"-h":Y(),process.exit(0);case"-V":case"--version":case"-v":Le(),process.exit(0);default:console.error(`unknown argument: ${e}`),Y(),process.exit(1)}}function P(e,t){e.readyState===E.OPEN&&e.send(JSON.stringify(t))}const se=new Set;await N.read(),N.data.sessionAccess??=[],N.data.pinnedViews??=[],N.data.watchedPanes??=[],N.data.quickCommands??=[];const ae=await Ue();let tt=await Ee();const k=new We({onChange:()=>{}}),_=new Map;function ie(e){return k.get(e)?_.get(e)??null:null}const ce={activeTheme:tt,settingUpPassword:!1},nt=K({mode:"hub",securityConfig:g,tokenStore:v,rateLimiter:A,settings:ae,terminalBufferConfig:F,state:ce,federation:{getConfig:()=>x,setConfig:async e=>{x=e,await Ye(e),ue(e)},status:rt,hostname:U()},hub:{agents:k,getChannel:ie}}),ot=K({mode:"agent",securityConfig:g,tokenStore:v,rateLimiter:A,settings:ae,terminalBufferConfig:F,state:ce});let x=await Xe(),M=null;function le(){M&&(M.stop(),M=null)}function ue(e){le(),!(!e.enabled||!e.hub||!e.token)&&(M=Ke({hub:e.hub,token:e.token,name:e.name?.trim()||U(),version:Te,app:ot,listSessions:Me,terminalBufferConfig:F,getSessionPaneTarget:Z,capturePaneTail:q,capturePaneHistoryChunk:ee,toCrlf:te,handleClientMessage:X,acquireControlClient:Q}))}function rt(){return x.enabled?M?M.status():{state:"idle",agentId:null,hub:x.hub??"",name:x.name??U()}:{state:"idle",agentId:null,hub:x.hub??"",name:x.name??U()}}ue(x);const D=new Qe,B=new Map;setInterval(()=>{(async()=>{try{const e=await D.scan(),t=[];for(const[o,n]of e)B.get(o)!==n&&t.push({session:o,state:n});for(const o of B.keys())e.has(o)||t.push({session:o,state:"idle"});B.clear();for(const[o,n]of e)B.set(o,n);for(const o of t)de({type:"session.activity",session:o.session,state:o.state})}catch{}})()},D.scanMs);const st=parseInt(process.env.PORT||"21000",10);function at(){const e=[];try{for(const t of Object.values(ke()))for(const o of t??[])o.family==="IPv4"&&!o.internal&&e.push(o.address)}catch{}return e}const it=we({fetch:nt.fetch,port:st,hostname:"0.0.0.0"},e=>{console.log(`tmux-web running at http://localhost:${e.port}`);for(const t of at())console.log(` also on http://${t}:${e.port}`)}),O=new Se({noServer:!0}),z=new Set;function de(e){const t=JSON.stringify(e);for(const o of z)o.readyState===E.OPEN&&o.send(t)}function I(e,t,o){e.write(`HTTP/1.1 ${t} ${o}\r
3
3
  \r
4
- `),e.destroy()}it.on("upgrade",(e,t,o)=>{const n=new URL(e.url||"/",`http://${e.headers.host}`),f=n.pathname;if(f==="/ws/activity"){const w=j(e),a=H(e)||n.searchParams.get("token")||"";if(!(a?v.validateToken(a):null)){I(t,401,"Unauthorized");return}O.handleUpgrade(e,t,o,c=>{c.on("close",()=>D.delete(c)),D.add(c)});return}if(f==="/agent/ws"){dt(e,t,o);return}const s=f.match(/^\/ws\/a\/([^/]+)\/(.+)$/);if(s){ct(e,t,o,decodeURIComponent(s[1]),decodeURIComponent(s[2]));return}const i=f.match(/^\/ws\/(.+)$/);if(!i){t.destroy();return}const l=j(e),u=e.headers.origin,p=g.security.allowedOrigins;if(u&&!(u===`http://${e.headers.host}`||u===`https://${e.headers.host}`)&&(p.length===0||!p.includes(u))){d("ws_rejected_origin",{ip:l,origin:u}),I(t,403,"Origin not allowed");return}const m=et(l);if(m>=g.security.maxConnectionsPerIp){d("ws_rejected_per_ip_cap",{ip:l,liveFromIp:m}),I(t,429,"Too many connections");return}const y=H(e)||n.searchParams.get("token")||"",T=y?v.validateToken(y):null;O.handleUpgrade(e,t,o,w=>{O.emit("connection",w,e,decodeURIComponent(i[1]),l,T)})}),O.on("connection",(e,t,o,n,f)=>{const s={ws:e,ip:n,authenticated:!1,authTimeout:null},i=(()=>{try{const a=new URL(t.url||"/",`http://${t.headers.host}`).searchParams,r=Number(a.get("cols")),c=Number(a.get("rows"));if(Number.isInteger(r)&&Number.isInteger(c)&&r>0&&c>0&&r<=1e3&&c<=1e3)return{cols:r,rows:c}}catch{}})();R.set(e,s),d("ws_connected",{ip:n});let l=null,u=null;function p(a,r){s.authenticated=!0,s.authTimeout&&(clearTimeout(s.authTimeout),s.authTimeout=null),d("auth_success",{ip:n,method:a,tokenName:r}),P(e,{type:"auth.ok",setupMode:!g.passwordHash}),y()}function m(a,r={}){P(e,{type:"auth.failed",message:a,...r})}function y(){l||(te(o,{path:Ze(o)}),l=Fe(o,{send:a=>P(e,a),onMessage:a=>{u=a},onClose:a=>{T=a}},{terminalBufferConfig:F,getSessionPaneTarget:Q,capturePaneTail:Z,capturePaneHistoryChunk:q,toCrlf:ee,handleClientMessage:V,acquireControlClient:K,initialCols:i?.cols,initialRows:i?.rows,tmuxSocketPath:Oe()??void 0,onPtyExit:(a,r)=>b(e,1e3,"pty exited"),onSpawnError:(a,r)=>b(e,1011,"pty spawn failed")}))}let T=null;async function w(a){let r;try{r=JSON.parse(a)}catch{return!1}if(r.type==="auth.token"&&typeof r.token=="string"){const c=v.validateToken(r.token);if(!c){d("token_auth_failed",{ip:n});const h=A.recordFailure(n);return h.permanentLock&&(v.revokeAll(),d("permanent_lock",{ip:n,failures:h.failures})),m("Invalid or expired token",{permanentLock:h.permanentLock,retryAfterMs:h.retryAfterMs}),!0}return v.touch(c.tokenHash),p("token",c.name),!0}if(r.type==="auth"&&typeof r.password=="string"){const c=A.check(n);if(!c.allowed)return d("rate_limited",{ip:n,retryAfterMs:c.retryAfterMs,permanentLock:c.permanentLock}),m("Too many attempts",{permanentLock:c.permanentLock,retryAfterMs:c.retryAfterMs}),!0;if(!g.passwordHash){if(!g.security.allowRemoteSetup&&!mt(n))return d("setup_rejected_remote",{ip:n}),m("First-run setup must be performed from localhost"),!0;if(J)return m("Password setup in progress"),!0;const C=Ie(r.password);if(C)return m(C),!0;J=!0;try{g.passwordHash=await be(r.password),Ve(g),d("password_set",{ip:n})}finally{J=!1}const{stored:W,plaintext:pe}=v.createAccessToken("setup",g.security.tokenTtlDays);return P(e,{type:"auth.ok",setupMode:!0,token:pe}),d("token_created",{ip:n,name:W.name,tokenId:W.id}),d("auth_success",{ip:n,method:"password",tokenName:W.name}),y(),!0}if(!g.passwordHash)return m("Server not configured"),!0;if(!await xe(r.password,g.passwordHash)){const C=A.recordFailure(n);return d("auth_failed",{ip:n,method:"password",failures:C.failures,permanentLock:C.permanentLock}),C.permanentLock&&(v.revokeAll(),d("permanent_lock",{ip:n,failures:C.failures})),m("Incorrect password",{permanentLock:C.permanentLock,retryAfterMs:C.retryAfterMs}),!0}A.recordSuccess(n);const S=`ws-${n}`,{plaintext:me}=v.createAccessToken(S,g.security.tokenTtlDays);return p("password",S),P(e,{type:"auth.ok",setupMode:!1,token:me}),!0}return!1}f?(v.touch(f.tokenHash),p("token",f.name)):(P(e,{type:"auth.required",setupMode:!g.passwordHash}),s.authTimeout=setTimeout(()=>{s.authenticated||(d("auth_timeout",{ip:n}),m("Authentication timeout"),b(e,4e3,"Auth timeout"))},g.security.authTimeoutMs)),e.on("message",async a=>{const r=typeof a=="string"?a:a.toString("utf-8");if(!s.authenticated){if(r.length>1e6){P(e,{type:"auth.failed",message:"Message too large"});return}await w(r)||m("Authentication required");return}if(u)try{u(r)}catch{}}),e.on("close",()=>{s.authTimeout&&clearTimeout(s.authTimeout),R.delete(e),d("ws_disconnected",{ip:n}),T?.(),l&&(l.dispose(),l=null)}),e.on("error",()=>{s.authTimeout&&clearTimeout(s.authTimeout),R.delete(e),T?.(),l&&(l.dispose(),l=null)})});function ct(e,t,o,n,f){const s=j(e),i=new URL(e.url||"/",`http://${e.headers.host}`),l=H(e)||i.searchParams.get("token")||"";if(!(l?v.validateToken(l):null)){d("ws_rejected_remote_auth",{ip:s,agentId:n}),I(t,401,"Unauthorized");return}const p=ae(n);if(!p){d("ws_rejected_remote_offline",{ip:s,agentId:n}),I(t,503,"Agent offline");return}if(p.relayCount>=De){I(t,429,"Too many connections to agent");return}const m=e.headers.origin,y=g.security.allowedOrigins;if(m&&!(m===`http://${e.headers.host}`||m===`https://${e.headers.host}`)&&(y.length===0||!y.includes(m))){I(t,403,"Origin not allowed");return}const T=ut(),w=(()=>{try{const a=new URL(e.url||"/",`http://${e.headers.host}`).searchParams,r=Number(a.get("cols")),c=Number(a.get("rows"));if(Number.isInteger(r)&&Number.isInteger(c)&&r>0&&c>0&&r<=1e3&&c<=1e3)return{cols:r,rows:c}}catch{}})();O.handleUpgrade(e,t,o,a=>{if(p.registerRelay(T,f,a),k.bumpRelayConns(n,1),te(f,{agentId:n}),!p.attach(T,f,w)){a.send(JSON.stringify({type:"data",data:`\r
4
+ `),e.destroy()}it.on("upgrade",(e,t,o)=>{const n=new URL(e.url||"/",`http://${e.headers.host}`),f=n.pathname;if(f==="/ws/activity"){const w=j(e),a=H(e)||n.searchParams.get("token")||"";if(!(a?v.validateToken(a):null)){I(t,401,"Unauthorized");return}O.handleUpgrade(e,t,o,c=>{c.on("close",()=>z.delete(c)),z.add(c)});return}if(f==="/agent/ws"){dt(e,t,o);return}const s=f.match(/^\/ws\/a\/([^/]+)\/(.+)$/);if(s){ct(e,t,o,decodeURIComponent(s[1]),decodeURIComponent(s[2]));return}const i=f.match(/^\/ws\/(.+)$/);if(!i){t.destroy();return}const l=j(e),u=e.headers.origin,p=g.security.allowedOrigins;if(u&&!(u===`http://${e.headers.host}`||u===`https://${e.headers.host}`)&&(p.length===0||!p.includes(u))){d("ws_rejected_origin",{ip:l,origin:u}),I(t,403,"Origin not allowed");return}const m=et(l);if(m>=g.security.maxConnectionsPerIp){d("ws_rejected_per_ip_cap",{ip:l,liveFromIp:m}),I(t,429,"Too many connections");return}const y=H(e)||n.searchParams.get("token")||"",T=y?v.validateToken(y):null;O.handleUpgrade(e,t,o,w=>{O.emit("connection",w,e,decodeURIComponent(i[1]),l,T)})}),O.on("connection",(e,t,o,n,f)=>{const s={ws:e,ip:n,authenticated:!1,authTimeout:null},i=(()=>{try{const a=new URL(t.url||"/",`http://${t.headers.host}`).searchParams,r=Number(a.get("cols")),c=Number(a.get("rows"));if(Number.isInteger(r)&&Number.isInteger(c)&&r>0&&c>0&&r<=1e3&&c<=1e3)return{cols:r,rows:c}}catch{}})();R.set(e,s),d("ws_connected",{ip:n});let l=null,u=null;function p(a,r){s.authenticated=!0,s.authTimeout&&(clearTimeout(s.authTimeout),s.authTimeout=null),d("auth_success",{ip:n,method:a,tokenName:r}),P(e,{type:"auth.ok",setupMode:!g.passwordHash}),y()}function m(a,r={}){P(e,{type:"auth.failed",message:a,...r})}function y(){l||(D.suppress(o),ne(o,{path:Ze(o)}),l=Fe(o,{send:a=>P(e,a),onMessage:a=>{u=a},onClose:a=>{T=a}},{terminalBufferConfig:F,getSessionPaneTarget:Z,capturePaneTail:q,capturePaneHistoryChunk:ee,toCrlf:te,handleClientMessage:X,acquireControlClient:Q,initialCols:i?.cols,initialRows:i?.rows,tmuxSocketPath:Oe()??void 0,onPtyExit:(a,r)=>b(e,1e3,"pty exited"),onSpawnError:(a,r)=>b(e,1011,"pty spawn failed")}))}let T=null;async function w(a){let r;try{r=JSON.parse(a)}catch{return!1}if(r.type==="auth.token"&&typeof r.token=="string"){const c=v.validateToken(r.token);if(!c){d("token_auth_failed",{ip:n});const h=A.recordFailure(n);return h.permanentLock&&(v.revokeAll(),d("permanent_lock",{ip:n,failures:h.failures})),m("Invalid or expired token",{permanentLock:h.permanentLock,retryAfterMs:h.retryAfterMs}),!0}return v.touch(c.tokenHash),p("token",c.name),!0}if(r.type==="auth"&&typeof r.password=="string"){const c=A.check(n);if(!c.allowed)return d("rate_limited",{ip:n,retryAfterMs:c.retryAfterMs,permanentLock:c.permanentLock}),m("Too many attempts",{permanentLock:c.permanentLock,retryAfterMs:c.retryAfterMs}),!0;if(!g.passwordHash){if(!g.security.allowRemoteSetup&&!mt(n))return d("setup_rejected_remote",{ip:n}),m("First-run setup must be performed from localhost"),!0;if(J)return m("Password setup in progress"),!0;const C=Ie(r.password);if(C)return m(C),!0;J=!0;try{g.passwordHash=await be(r.password),Ve(g),d("password_set",{ip:n})}finally{J=!1}const{stored:W,plaintext:pe}=v.createAccessToken("setup",g.security.tokenTtlDays);return P(e,{type:"auth.ok",setupMode:!0,token:pe}),d("token_created",{ip:n,name:W.name,tokenId:W.id}),d("auth_success",{ip:n,method:"password",tokenName:W.name}),y(),!0}if(!g.passwordHash)return m("Server not configured"),!0;if(!await xe(r.password,g.passwordHash)){const C=A.recordFailure(n);return d("auth_failed",{ip:n,method:"password",failures:C.failures,permanentLock:C.permanentLock}),C.permanentLock&&(v.revokeAll(),d("permanent_lock",{ip:n,failures:C.failures})),m("Incorrect password",{permanentLock:C.permanentLock,retryAfterMs:C.retryAfterMs}),!0}A.recordSuccess(n);const S=`ws-${n}`,{plaintext:me}=v.createAccessToken(S,g.security.tokenTtlDays);return p("password",S),P(e,{type:"auth.ok",setupMode:!1,token:me}),!0}return!1}f?(v.touch(f.tokenHash),p("token",f.name)):(P(e,{type:"auth.required",setupMode:!g.passwordHash}),s.authTimeout=setTimeout(()=>{s.authenticated||(d("auth_timeout",{ip:n}),m("Authentication timeout"),b(e,4e3,"Auth timeout"))},g.security.authTimeoutMs)),e.on("message",async a=>{const r=typeof a=="string"?a:a.toString("utf-8");if(!s.authenticated){if(r.length>1e6){P(e,{type:"auth.failed",message:"Message too large"});return}await w(r)||m("Authentication required");return}if(u)try{u(r)}catch{}}),e.on("close",()=>{s.authTimeout&&clearTimeout(s.authTimeout),R.delete(e),d("ws_disconnected",{ip:n}),T?.(),l&&(l.dispose(),l=null)}),e.on("error",()=>{s.authTimeout&&clearTimeout(s.authTimeout),R.delete(e),T?.(),l&&(l.dispose(),l=null)})});function ct(e,t,o,n,f){const s=j(e),i=new URL(e.url||"/",`http://${e.headers.host}`),l=H(e)||i.searchParams.get("token")||"";if(!(l?v.validateToken(l):null)){d("ws_rejected_remote_auth",{ip:s,agentId:n}),I(t,401,"Unauthorized");return}const p=ie(n);if(!p){d("ws_rejected_remote_offline",{ip:s,agentId:n}),I(t,503,"Agent offline");return}if(p.relayCount>=De){I(t,429,"Too many connections to agent");return}const m=e.headers.origin,y=g.security.allowedOrigins;if(m&&!(m===`http://${e.headers.host}`||m===`https://${e.headers.host}`)&&(y.length===0||!y.includes(m))){I(t,403,"Origin not allowed");return}const T=ut(),w=(()=>{try{const a=new URL(e.url||"/",`http://${e.headers.host}`).searchParams,r=Number(a.get("cols")),c=Number(a.get("rows"));if(Number.isInteger(r)&&Number.isInteger(c)&&r>0&&c>0&&r<=1e3&&c<=1e3)return{cols:r,rows:c}}catch{}})();O.handleUpgrade(e,t,o,a=>{if(p.registerRelay(T,f,a),k.bumpRelayConns(n,1),ne(f,{agentId:n}),!p.attach(T,f,w)){a.send(JSON.stringify({type:"data",data:`\r
5
5
  \x1B[31magent offline\x1B[0m\r
6
- `})),a.close(1011,"agent offline"),p.relayClosed(T),k.bumpRelayConns(n,-1);return}a.on("message",c=>{const h=typeof c=="string"?c:c.toString("utf-8");let S;try{S=JSON.parse(h)}catch{return}(S.type==="input"||S.type==="resize"||S.type==="load_history")&&p.sendToAgent(T,S)});const r=()=>{p.relayClosed(T),k.bumpRelayConns(n,-1)};a.on("close",r),a.on("error",r)})}let lt=1;function ut(){return lt++}function dt(e,t,o){const n=j(e),f=e.headers.origin;if(f){d("agent_rejected_origin",{ip:n,origin:f}),I(t,403,"Origin not allowed");return}O.handleUpgrade(e,t,o,s=>{let i=null,l=setTimeout(()=>{d("agent_hello_timeout",{ip:n}),b(s,4e3,"hello timeout")},1e4);s.on("message",(u,p)=>{if(i){k.touch(i.agentId),p?i.handleData(Array.isArray(u)?Buffer.concat(u):Buffer.from(u)):i.handleMessage(typeof u=="string"?u:Buffer.from(u).toString("utf-8"));return}const m=typeof u=="string"?u:u.toString("utf-8");let y;try{y=JSON.parse(m)}catch{b(s,1008,"bad hello");return}if(y.type!=="hello"||typeof y.token!="string"){b(s,1008,"expected hello");return}if(!A.check(n).allowed){d("agent_rate_limited",{ip:n}),b(s,429,"Too many attempts");return}const w=ze(y.token);if(!w){A.recordFailure(n),d("agent_bad_token",{ip:n}),ne(s,{type:"hello_err",reason:"invalid token"}),b(s,4003,"invalid token");return}const a=typeof y.name=="string"&&y.name.trim()?y.name.trim().slice(0,64):"agent",r=w.id;if(!k.register(r,a,s,w.tokenHash)){d("agent_cap_reached",{ip:n}),ne(s,{type:"hello_err",reason:"too many agents"}),b(s,4004,"too many agents");return}clearTimeout(l),A.recordSuccess(n),d("agent_connected",{ip:n,agentId:r,name:a}),i=new Je(s,r,{onSessions:h=>k.setSessions(r,h),onActivity:h=>{for(const S of h)de({type:"session.activity",agentId:r,session:S.session,state:S.state})},onRebuildResult:h=>{console.log(`[agent ${r}] rebuild ${h.session}: ${h.ok?"ok":h.message}`)},onClose:h=>{_.get(r)===h&&_.delete(r)}}),_.set(r,i),k.touch(r),s.send(JSON.stringify({type:"hello_ok",agentId:r})),i.sendSessionsReq()}),s.on("close",()=>{i&&(i.dispose(),_.delete(i.agentId),k.unregister(i.agentId),d("agent_disconnected",{ip:n,agentId:i.agentId})),clearTimeout(l)}),s.on("error",()=>{i&&(i.dispose(),_.delete(i.agentId),k.unregister(i.agentId)),clearTimeout(l)})})}const ft=parseInt(process.env.TMUX_WEB_AGENT_OFFLINE_MS||"45000",10);setInterval(()=>{const e=k.pruneOffline(ft);for(const o of e){const n=k.peek(o);n&&n.ws.readyState===E.CLOSED&&(k.unregister(o),_.get(o)?.dispose(),_.delete(o))}const t=new Set(Ge().map(o=>o.tokenHash));for(const[o,n]of _){const f=k.peek(o);if(!(!f||t.has(f.tokenHash))){d("agent_token_revoked",{agentId:o}),n.dispose(),_.delete(o),k.unregister(o);try{f.ws.close(4003,"token revoked")}catch{}}}v.purgeExpired()},10*1e3).unref();function fe(){ce(),He(),je();for(const e of _.values())e.dispose();_.clear();for(const[e,t]of R){try{e.close(1001,"Server shutting down")}catch{}t.authTimeout&&clearTimeout(t.authTimeout)}R.clear();for(const e of re)try{e.kill()}catch{}re.clear(),A.dispose(),process.exit(0)}process.on("SIGINT",fe),process.on("SIGTERM",fe);function mt(e){return e==="127.0.0.1"||e==="::1"||e==="::ffff:127.0.0.1"}async function pt(e){const{createAgentToken:t,listAgentTokens:o,removeAgentToken:n}=await import("./lib/agent-tokens.js"),[f,...s]=e;switch(f){case"add":{const i=s.find(p=>!p.startsWith("-"))||"agent",{id:l,token:u}=t(i);console.log("\u2713 agent token created"),console.log(` id: ${l}`),console.log(` name: ${i}`),console.log(` token: ${u}`),console.log(`
6
+ `})),a.close(1011,"agent offline"),p.relayClosed(T),k.bumpRelayConns(n,-1);return}a.on("message",c=>{const h=typeof c=="string"?c:c.toString("utf-8");let S;try{S=JSON.parse(h)}catch{return}(S.type==="input"||S.type==="resize"||S.type==="load_history")&&p.sendToAgent(T,S)});const r=()=>{p.relayClosed(T),k.bumpRelayConns(n,-1)};a.on("close",r),a.on("error",r)})}let lt=1;function ut(){return lt++}function dt(e,t,o){const n=j(e),f=e.headers.origin;if(f){d("agent_rejected_origin",{ip:n,origin:f}),I(t,403,"Origin not allowed");return}O.handleUpgrade(e,t,o,s=>{let i=null,l=setTimeout(()=>{d("agent_hello_timeout",{ip:n}),b(s,4e3,"hello timeout")},1e4);s.on("message",(u,p)=>{if(i){k.touch(i.agentId),p?i.handleData(Array.isArray(u)?Buffer.concat(u):Buffer.from(u)):i.handleMessage(typeof u=="string"?u:Buffer.from(u).toString("utf-8"));return}const m=typeof u=="string"?u:u.toString("utf-8");let y;try{y=JSON.parse(m)}catch{b(s,1008,"bad hello");return}if(y.type!=="hello"||typeof y.token!="string"){b(s,1008,"expected hello");return}if(!A.check(n).allowed){d("agent_rate_limited",{ip:n}),b(s,429,"Too many attempts");return}const w=ze(y.token);if(!w){A.recordFailure(n),d("agent_bad_token",{ip:n}),oe(s,{type:"hello_err",reason:"invalid token"}),b(s,4003,"invalid token");return}const a=typeof y.name=="string"&&y.name.trim()?y.name.trim().slice(0,64):"agent",r=w.id;if(!k.register(r,a,s,w.tokenHash)){d("agent_cap_reached",{ip:n}),oe(s,{type:"hello_err",reason:"too many agents"}),b(s,4004,"too many agents");return}clearTimeout(l),A.recordSuccess(n),d("agent_connected",{ip:n,agentId:r,name:a}),i=new Je(s,r,{onSessions:h=>k.setSessions(r,h),onActivity:h=>{for(const S of h)de({type:"session.activity",agentId:r,session:S.session,state:S.state})},onRebuildResult:h=>{console.log(`[agent ${r}] rebuild ${h.session}: ${h.ok?"ok":h.message}`)},onClose:h=>{_.get(r)===h&&_.delete(r)}}),_.set(r,i),k.touch(r),s.send(JSON.stringify({type:"hello_ok",agentId:r})),i.sendSessionsReq()}),s.on("close",()=>{i&&(i.dispose(),_.delete(i.agentId),k.unregister(i.agentId),d("agent_disconnected",{ip:n,agentId:i.agentId})),clearTimeout(l)}),s.on("error",()=>{i&&(i.dispose(),_.delete(i.agentId),k.unregister(i.agentId)),clearTimeout(l)})})}const ft=parseInt(process.env.TMUX_WEB_AGENT_OFFLINE_MS||"45000",10);setInterval(()=>{const e=k.pruneOffline(ft);for(const o of e){const n=k.peek(o);n&&n.ws.readyState===E.CLOSED&&(k.unregister(o),_.get(o)?.dispose(),_.delete(o))}const t=new Set(Ge().map(o=>o.tokenHash));for(const[o,n]of _){const f=k.peek(o);if(!(!f||t.has(f.tokenHash))){d("agent_token_revoked",{agentId:o}),n.dispose(),_.delete(o),k.unregister(o);try{f.ws.close(4003,"token revoked")}catch{}}}v.purgeExpired()},10*1e3).unref();function fe(){le(),He(),je();for(const e of _.values())e.dispose();_.clear();for(const[e,t]of R){try{e.close(1001,"Server shutting down")}catch{}t.authTimeout&&clearTimeout(t.authTimeout)}R.clear();for(const e of se)try{e.kill()}catch{}se.clear(),A.dispose(),process.exit(0)}process.on("SIGINT",fe),process.on("SIGTERM",fe);function mt(e){return e==="127.0.0.1"||e==="::1"||e==="::ffff:127.0.0.1"}async function pt(e){const{createAgentToken:t,listAgentTokens:o,removeAgentToken:n}=await import("./lib/agent-tokens.js"),[f,...s]=e;switch(f){case"add":{const i=s.find(p=>!p.startsWith("-"))||"agent",{id:l,token:u}=t(i);console.log("\u2713 agent token created"),console.log(` id: ${l}`),console.log(` name: ${i}`),console.log(` token: ${u}`),console.log(`
7
7
  Run on the remote machine:`),console.log(` tmux-web agent --hub wss://YOUR_HUB --token ${u} --name "${i}"`);return}case"list":{const i=o();if(!i.length){console.log("No agent tokens. Create one with: tmux-web agent-token add --name <name>");return}console.log("Agent tokens:");for(const l of i)console.log(` ${l.id} ${l.name} (created ${new Date(l.createdAt).toISOString()})`);return}case"remove":{const i=s.find(u=>!u.startsWith("-"));i||(console.error("usage: tmux-web agent-token remove <id>"),process.exit(1)),n(i)||(console.error(`no token with id ${i}`),process.exit(1)),console.log(`\u2713 token ${i} removed`);return}default:console.error("usage: tmux-web agent-token <add|list|remove>"),process.exit(1)}}
@@ -1,2 +1,2 @@
1
- import{execFile as l}from"node:child_process";import{tmuxEnv as f}from"./tmux-env.js";const r=1e4,h=2e3;function u(c,s,e){return c-s<=e?"working":"idle"}function p(c){return new Promise((s,e)=>{l("tmux",c,{env:f(),maxBuffer:8*1024*1024},(i,n)=>{i?e(i):s(n)})})}class _{idleMs;scanMs;execTmux;hashes=new Map;lastActivity=new Map;constructor(s={}){const e=parseInt(process.env.TMUX_WEB_ACTIVITY_IDLE_MS||String(r),10);this.idleMs=s.idleMs??(e||r);const i=parseInt(process.env.TMUX_WEB_ACTIVITY_SCAN_MS||String(h),10);this.scanMs=s.scanMs??(i||h),this.execTmux=s.execTmux??p}async scan(s=Date.now()){const e=await this.listPanes(),i=new Set;for(const t of e){i.add(t.session);let a;try{a=await this.execTmux(["capture-pane","-p","-e","-t",t.target])}catch{continue}const o=this.hashes.get(t.target);o!==void 0&&a!==o&&this.lastActivity.set(t.session,s),this.hashes.set(t.target,a)}for(const t of this.hashes.keys())e.some(a=>a.target===t)||this.hashes.delete(t);for(const t of this.lastActivity.keys())i.has(t)||this.lastActivity.delete(t);const n=new Map;for(const t of i)this.lastActivity.has(t)||this.lastActivity.set(t,s-this.idleMs-1),n.set(t,u(s,this.lastActivity.get(t),this.idleMs));return n}async listPanes(){const s=await this.execTmux(["list-panes","-a","-F","#{session_name} #{window_index}.#{pane_index}"]),e=[];for(const i of s.split(`
2
- `)){const[n,t]=i.split(" ");n&&t&&e.push({session:n,target:`${n}:${t}`})}return e}}export{_ as ActivityProbe,r as DEFAULT_ACTIVITY_IDLE_MS,h as DEFAULT_ACTIVITY_SCAN_MS,u as computeAgentState};
1
+ import{execFile as u}from"node:child_process";import{tmuxEnv as _}from"./tmux-env.js";const o=1e4,h=2e3,p=5e3;function f(c,s,i){return c-s<=i?"working":"idle"}function T(c){return new Promise((s,i)=>{u("tmux",c,{env:_(),maxBuffer:8*1024*1024},(n,e)=>{n?i(n):s(e)})})}class d{idleMs;scanMs;attachGraceMs;execTmux;hashes=new Map;lastActivity=new Map;suppressedUntil=new Map;constructor(s={}){const i=parseInt(process.env.TMUX_WEB_ACTIVITY_IDLE_MS||String(o),10);this.idleMs=s.idleMs??(i||o);const n=parseInt(process.env.TMUX_WEB_ACTIVITY_SCAN_MS||String(h),10);this.scanMs=s.scanMs??(n||h);const e=parseInt(process.env.TMUX_WEB_ACTIVITY_ATTACH_GRACE_MS||String(p),10);this.attachGraceMs=Number.isFinite(e)&&e>=0?e:p,this.execTmux=s.execTmux??T}suppress(s,i=this.attachGraceMs,n=Date.now()){const e=this.suppressedUntil.get(s)??0;this.suppressedUntil.set(s,Math.max(e,n+i))}async scan(s=Date.now()){const i=await this.listPanes(),n=new Set;for(const t of i){n.add(t.session);let a;try{a=await this.execTmux(["capture-pane","-p","-e","-t",t.target])}catch{continue}const r=this.hashes.get(t.target);if(r!==void 0&&a!==r){const l=this.suppressedUntil.get(t.session)??0;s>=l&&this.lastActivity.set(t.session,s)}this.hashes.set(t.target,a)}for(const[t,a]of this.suppressedUntil)s>=a&&this.suppressedUntil.delete(t);for(const t of this.hashes.keys())i.some(a=>a.target===t)||this.hashes.delete(t);for(const t of this.lastActivity.keys())n.has(t)||this.lastActivity.delete(t);const e=new Map;for(const t of n)this.lastActivity.has(t)||this.lastActivity.set(t,s-this.idleMs-1),e.set(t,f(s,this.lastActivity.get(t),this.idleMs));return e}async listPanes(){const s=await this.execTmux(["list-panes","-a","-F","#{session_name} #{window_index}.#{pane_index}"]),i=[];for(const n of s.split(`
2
+ `)){const[e,t]=n.split(" ");e&&t&&i.push({session:e,target:`${e}:${t}`})}return i}}export{d as ActivityProbe,p as DEFAULT_ACTIVITY_ATTACH_GRACE_MS,o as DEFAULT_ACTIVITY_IDLE_MS,h as DEFAULT_ACTIVITY_SCAN_MS,f as computeAgentState};
@@ -1 +1 @@
1
- import{WebSocket as h}from"ws";import{attachTerminal as X}from"./attach-terminal.js";import{encodeBinaryFrame as Y,decodeBinaryFrame as z,BIN_KIND_HTTP_BODY as R}from"./agent-channel.js";import{ActivityProbe as Q}from"./activity-probe.js";import{sessionWorkingPath as V}from"./sessions-sidebar.js";import{newTmuxSession as Z}from"./tmux-windows.js";const ee=parseInt(process.env.TMUX_WEB_AGENT_HEARTBEAT_MS||"15000",10),te=parseInt(process.env.TMUX_WEB_AGENT_SESSIONS_POLL_MS||"10000",10),ne=parseInt(process.env.TMUX_WEB_AGENT_PONG_TIMEOUT_MS||"45000",10),H=5,se=6e4,oe=new Set(["content-type","content-disposition","location","cache-control","etag"]);function fe(o){const E=o.log??(t=>console.log(`[agent] ${t}`)),p=t=>console.error(`[agent] ${t}`);let s=null,I=0,y=0,B=null,P=!1;const d=new Map,_=new Map,S=new Map,M=new Set;function U(t){return M.has(t)?Promise.resolve():new Promise(e=>{S.set(t,e),setTimeout(()=>{S.delete(t)&&e()},3e4)})}let m=null,g=null,b=null,w=null,v=new Map,C="",N=null;const T={state:"idle",agentId:null,hub:o.hub,name:o.name};function f(t,e){e!==void 0&&(T.lastError=e),T.state=t,T.agentId=N,o.onStatus?.({...T})}function c(t){s&&s.readyState===h.OPEN&&s.send(JSON.stringify(t))}function k(){let t;try{t=o.listSessions()}catch{return}const e=t.map(r=>({...r,path:V(r.name)})),n=JSON.stringify(e);n!==C&&(C=n,c({type:"sessions",sessions:e}))}function L(){m||(m=setInterval(k,te)),g||(g=setInterval(()=>{if(s&&s.readyState===h.OPEN&&y>0&&Date.now()-y>ne){E(`hub heartbeat timeout (${Date.now()-y}ms without pong), reconnecting`);try{s.close(1001,"hub heartbeat timeout")}catch{}return}c({type:"ping",ts:Date.now()})},ee)),b||(w=new Q,b=setInterval(()=>void D(),w.scanMs))}function q(){m&&(clearInterval(m),m=null),g&&(clearInterval(g),g=null),b&&(clearInterval(b),b=null),w=null}async function D(){if(!(!s||s.readyState!==h.OPEN||!w))try{const t=await w.scan(),e=[];for(const[n,r]of t)v.get(n)!==r&&e.push({session:n,state:r});v=t,e.length&&c({type:"activity",activities:e})}catch{}}async function x(t){let e;if(t.hasBody){await U(t.id),M.delete(t.id);const a=_.get(t.id);_.delete(t.id),e=a&&a.length?Buffer.concat(a):Buffer.alloc(0)}const n=new URL(t.path,"http://agent.invalid");let r;try{const a={method:t.method,headers:t.headers};e&&e.length&&(a.body=e),r=await Promise.race([o.app.request(new Request(n,a)),new Promise((re,K)=>setTimeout(()=>K(new Error("app.request timed out")),3e4))])}catch(a){p(`tunnel request failed: ${t.method} ${t.path} ${a.message}`),c({type:"http_resp",id:t.id,status:500,headers:{"content-type":"application/json"},hasBody:!1});return}const u={};r.headers.forEach((a,l)=>{oe.has(l.toLowerCase())&&(u[l]=a)});const i=Buffer.from(await r.arrayBuffer());if(c({type:"http_resp",id:t.id,status:r.status,headers:u,hasBody:i.length>0}),i.length>0){for(let l=0;l<i.length;l+=65536){if(!s||s.readyState!==h.OPEN)return;s.send(Y(R,t.id,i.subarray(l,l+65536)))}c({type:"http_body_end",id:t.id})}}function G(t,e){let n=!1,r="";try{Z(t,e),n=!0}catch(u){r=u instanceof Error?u.message:"rebuild failed"}s&&s.readyState===h.OPEN&&s.send(JSON.stringify({type:"rebuild_session_result",session:t,ok:n,message:r})),k()}function J(t){const e=t.session;let n=!1;try{n=o.listSessions().some(i=>i.name===e)}catch{n=!1}if(!n){c({type:"attach_err",connId:t.connId,session:e,message:"no such session"});return}let r=null;const u=X(e,{send:i=>c({type:"ws_to_hub",connId:t.connId,msg:i}),onMessage:i=>{r=i},onClose:()=>{}},{terminalBufferConfig:o.terminalBufferConfig,getSessionPaneTarget:o.getSessionPaneTarget,capturePaneTail:o.capturePaneTail,capturePaneHistoryChunk:o.capturePaneHistoryChunk,toCrlf:o.toCrlf,handleClientMessage:o.handleClientMessage,acquireControlClient:o.acquireControlClient,initialCols:t.cols,initialRows:t.rows,tmuxSocketPath:o.tmuxSocketPath,onPtyExit:(i,a)=>{c({type:"ws_close",connId:t.connId,code:1e3,reason:`tmux exited (${a})`})},onSpawnError:(i,a)=>{p(`attach failed for session "${t.session}": ${a}`),c({type:"attach_err",connId:t.connId,session:e,message:a})},onAttached:i=>{c({type:"attach_ok",connId:t.connId,session:e})}});d.set(t.connId,{onMessage:i=>r?.(i),dispose:()=>{d.delete(t.connId),u.dispose()}})}function W(t){let e;try{e=JSON.parse(t)}catch{return}switch(e.type){case"hello_ok":N=e.agentId,I=0,y=Date.now(),E(`connected to hub as "${N}"`),f("connected"),k(),L();break;case"hello_err":if(p(`hub rejected us: ${e.reason}`),f("error",e.reason),s)try{s.close(4003,"hello rejected")}catch{}break;case"sessions_req":k();break;case"http_req":x(e);break;case"http_body_end":{M.add(e.id);const n=S.get(e.id);n&&(S.delete(e.id),n());break}case"attach":J(e);break;case"rebuild_session":G(e.session,e.dir);break;case"ws_to_agent":{const n=d.get(e.connId);if(n)try{n.onMessage(JSON.stringify(e.msg))}catch{}break}case"detach":{const n=d.get(e.connId);n&&n.dispose();break}case"pong":y=Date.now();break;case"ping":c({type:"pong",ts:e.ts});break;default:break}}function j(t){const e=z(t);if(e&&e.kind===R){const n=_.get(e.id)??[];n.push(e.payload),_.set(e.id,n)}}function A(){q();for(const t of d.values())try{t.dispose()}catch{}d.clear(),_.clear(),S.clear(),M.clear(),C=""}function O(){if(s){try{s.removeAllListeners()}catch{}s=null}if(A(),P)return;f("connecting"),I+=1;const e=(I<=H?1e3:Math.min(1e3*Math.pow(2,I-H),se))+Math.random()*500;E(`reconnecting in ${Math.round(e)}ms\u2026`),B=setTimeout($,e)}function $(){if(P||s)return;const t=o.hub.replace(/\/+$/,"").replace(/^http:/,"ws:").replace(/^https:/,"wss:")+"/agent/ws";let e;try{e=new h(t)}catch(n){p(`bad hub URL: ${n.message} (${t})`),f("error",`bad hub URL: ${n.message}`);return}s=e,f("connecting"),e.on("open",()=>{E(`connecting to ${t}\u2026`),c({type:"hello",token:o.token,name:o.name,version:o.version})}),e.on("message",(n,r)=>{r?j(Array.isArray(n)?Buffer.concat(n):Buffer.isBuffer(n)?n:Buffer.from(n)):W(typeof n=="string"?n:Buffer.from(n).toString("utf-8"))}),e.on("close",()=>{s===e&&O()}),e.on("error",n=>{p(`connection error: ${n.message}`),s===e&&O()})}function F(){if(!P){if(P=!0,B&&clearTimeout(B),A(),s){try{s.removeAllListeners(),s.close(1e3,"shutdown")}catch{}s=null}N=null,f("idle")}}return $(),{stop:F,status:()=>({...T})}}export{fe as startAgentClient};
1
+ import{WebSocket as p}from"ws";import{attachTerminal as X}from"./attach-terminal.js";import{encodeBinaryFrame as Y,decodeBinaryFrame as z,BIN_KIND_HTTP_BODY as R}from"./agent-channel.js";import{ActivityProbe as Q}from"./activity-probe.js";import{sessionWorkingPath as V}from"./sessions-sidebar.js";import{newTmuxSession as Z}from"./tmux-windows.js";const ee=parseInt(process.env.TMUX_WEB_AGENT_HEARTBEAT_MS||"15000",10),te=parseInt(process.env.TMUX_WEB_AGENT_SESSIONS_POLL_MS||"10000",10),ne=parseInt(process.env.TMUX_WEB_AGENT_PONG_TIMEOUT_MS||"45000",10),H=5,se=6e4,oe=new Set(["content-type","content-disposition","location","cache-control","etag"]);function fe(o){const E=o.log??(t=>console.log(`[agent] ${t}`)),y=t=>console.error(`[agent] ${t}`);let s=null,I=0,_=0,B=null,P=!1;const d=new Map,S=new Map,m=new Map,M=new Set;function U(t){return M.has(t)?Promise.resolve():new Promise(e=>{m.set(t,e),setTimeout(()=>{m.delete(t)&&e()},3e4)})}let g=null,b=null,w=null,f=null,v=new Map,C="",N=null;const T={state:"idle",agentId:null,hub:o.hub,name:o.name};function h(t,e){e!==void 0&&(T.lastError=e),T.state=t,T.agentId=N,o.onStatus?.({...T})}function c(t){s&&s.readyState===p.OPEN&&s.send(JSON.stringify(t))}function k(){let t;try{t=o.listSessions()}catch{return}const e=t.map(r=>({...r,path:V(r.name)})),n=JSON.stringify(e);n!==C&&(C=n,c({type:"sessions",sessions:e}))}function L(){g||(g=setInterval(k,te)),b||(b=setInterval(()=>{if(s&&s.readyState===p.OPEN&&_>0&&Date.now()-_>ne){E(`hub heartbeat timeout (${Date.now()-_}ms without pong), reconnecting`);try{s.close(1001,"hub heartbeat timeout")}catch{}return}c({type:"ping",ts:Date.now()})},ee)),w||(f=new Q,w=setInterval(()=>void D(),f.scanMs))}function q(){g&&(clearInterval(g),g=null),b&&(clearInterval(b),b=null),w&&(clearInterval(w),w=null),f=null}async function D(){if(!(!s||s.readyState!==p.OPEN||!f))try{const t=await f.scan(),e=[];for(const[n,r]of t)v.get(n)!==r&&e.push({session:n,state:r});v=t,e.length&&c({type:"activity",activities:e})}catch{}}async function x(t){let e;if(t.hasBody){await U(t.id),M.delete(t.id);const a=S.get(t.id);S.delete(t.id),e=a&&a.length?Buffer.concat(a):Buffer.alloc(0)}const n=new URL(t.path,"http://agent.invalid");let r;try{const a={method:t.method,headers:t.headers};e&&e.length&&(a.body=e),r=await Promise.race([o.app.request(new Request(n,a)),new Promise((re,K)=>setTimeout(()=>K(new Error("app.request timed out")),3e4))])}catch(a){y(`tunnel request failed: ${t.method} ${t.path} ${a.message}`),c({type:"http_resp",id:t.id,status:500,headers:{"content-type":"application/json"},hasBody:!1});return}const u={};r.headers.forEach((a,l)=>{oe.has(l.toLowerCase())&&(u[l]=a)});const i=Buffer.from(await r.arrayBuffer());if(c({type:"http_resp",id:t.id,status:r.status,headers:u,hasBody:i.length>0}),i.length>0){for(let l=0;l<i.length;l+=65536){if(!s||s.readyState!==p.OPEN)return;s.send(Y(R,t.id,i.subarray(l,l+65536)))}c({type:"http_body_end",id:t.id})}}function G(t,e){let n=!1,r="";try{Z(t,e),n=!0}catch(u){r=u instanceof Error?u.message:"rebuild failed"}s&&s.readyState===p.OPEN&&s.send(JSON.stringify({type:"rebuild_session_result",session:t,ok:n,message:r})),k()}function J(t){const e=t.session;let n=!1;try{n=o.listSessions().some(i=>i.name===e)}catch{n=!1}if(!n){c({type:"attach_err",connId:t.connId,session:e,message:"no such session"});return}let r=null;f?.suppress(e);const u=X(e,{send:i=>c({type:"ws_to_hub",connId:t.connId,msg:i}),onMessage:i=>{r=i},onClose:()=>{}},{terminalBufferConfig:o.terminalBufferConfig,getSessionPaneTarget:o.getSessionPaneTarget,capturePaneTail:o.capturePaneTail,capturePaneHistoryChunk:o.capturePaneHistoryChunk,toCrlf:o.toCrlf,handleClientMessage:o.handleClientMessage,acquireControlClient:o.acquireControlClient,initialCols:t.cols,initialRows:t.rows,tmuxSocketPath:o.tmuxSocketPath,onPtyExit:(i,a)=>{c({type:"ws_close",connId:t.connId,code:1e3,reason:`tmux exited (${a})`})},onSpawnError:(i,a)=>{y(`attach failed for session "${t.session}": ${a}`),c({type:"attach_err",connId:t.connId,session:e,message:a})},onAttached:i=>{c({type:"attach_ok",connId:t.connId,session:e})}});d.set(t.connId,{onMessage:i=>r?.(i),dispose:()=>{d.delete(t.connId),u.dispose()}})}function W(t){let e;try{e=JSON.parse(t)}catch{return}switch(e.type){case"hello_ok":N=e.agentId,I=0,_=Date.now(),E(`connected to hub as "${N}"`),h("connected"),k(),L();break;case"hello_err":if(y(`hub rejected us: ${e.reason}`),h("error",e.reason),s)try{s.close(4003,"hello rejected")}catch{}break;case"sessions_req":k();break;case"http_req":x(e);break;case"http_body_end":{M.add(e.id);const n=m.get(e.id);n&&(m.delete(e.id),n());break}case"attach":J(e);break;case"rebuild_session":G(e.session,e.dir);break;case"ws_to_agent":{const n=d.get(e.connId);if(n)try{n.onMessage(JSON.stringify(e.msg))}catch{}break}case"detach":{const n=d.get(e.connId);n&&n.dispose();break}case"pong":_=Date.now();break;case"ping":c({type:"pong",ts:e.ts});break;default:break}}function j(t){const e=z(t);if(e&&e.kind===R){const n=S.get(e.id)??[];n.push(e.payload),S.set(e.id,n)}}function A(){q();for(const t of d.values())try{t.dispose()}catch{}d.clear(),S.clear(),m.clear(),M.clear(),C=""}function O(){if(s){try{s.removeAllListeners()}catch{}s=null}if(A(),P)return;h("connecting"),I+=1;const e=(I<=H?1e3:Math.min(1e3*Math.pow(2,I-H),se))+Math.random()*500;E(`reconnecting in ${Math.round(e)}ms\u2026`),B=setTimeout($,e)}function $(){if(P||s)return;const t=o.hub.replace(/\/+$/,"").replace(/^http:/,"ws:").replace(/^https:/,"wss:")+"/agent/ws";let e;try{e=new p(t)}catch(n){y(`bad hub URL: ${n.message} (${t})`),h("error",`bad hub URL: ${n.message}`);return}s=e,h("connecting"),e.on("open",()=>{E(`connecting to ${t}\u2026`),c({type:"hello",token:o.token,name:o.name,version:o.version})}),e.on("message",(n,r)=>{r?j(Array.isArray(n)?Buffer.concat(n):Buffer.isBuffer(n)?n:Buffer.from(n)):W(typeof n=="string"?n:Buffer.from(n).toString("utf-8"))}),e.on("close",()=>{s===e&&O()}),e.on("error",n=>{y(`connection error: ${n.message}`),s===e&&O()})}function F(){if(!P){if(P=!0,B&&clearTimeout(B),A(),s){try{s.removeAllListeners(),s.close(1e3,"shutdown")}catch{}s=null}N=null,h("idle")}}return $(),{stop:F,status:()=>({...T})}}export{fe as startAgentClient};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmux-weblink",
3
- "version": "2.2.21",
3
+ "version": "2.2.22",
4
4
  "description": "Access your tmux sessions from the browser. A continuation of tmux-web and persalink.",
5
5
  "type": "module",
6
6
  "bin": {