agent-dag 1.35.19 → 1.35.20
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.
|
@@ -69,7 +69,7 @@ Claude Code turns only. The sound is a Stop hook Claude Code runs itself when a
|
|
|
69
69
|
|
|
70
70
|
${n.clash} sound hook${n.clash>1?"s":""} of your own in settings.json also run${n.clash>1?"":"s"} here.`:"",l=n.parked>0?`
|
|
71
71
|
|
|
72
|
-
${n.parked} of your own sound hook${n.parked>1?"s were":" was"} set aside so this switch actually controls the sound. Nothing was deleted — shift-click to put ${n.parked>1?"them":"it"} back.`:"";return r+i+o+l}function LT(e){const n=Math.max(0,Math.floor(e));return`${n} event${n===1?"":"s"}`}function b5(e){return e.connected?e.paused?{tone:"paused",label:"paused",title:e.held>0?`Connected — ${LT(e.held)} held until you resume (Space)`:"Connected — updates held until you resume (Space)"}:{tone:"live",label:"live",title:"Receiving events"}:{tone:"dead",label:"offline",title:e.paused?"SSE disconnected — and the canvas is paused, so resuming will not bring events back until it reconnects":"SSE disconnected"}}function S5(e){return e.paused?e.held<=0?{label:"Resume",title:"Nothing has arrived since you paused. Resume to follow the canvas again (Space)"}:{label:`Resume · ${e.held} held`,title:`${LT(e.held)} arrived while paused and will be applied in order when you resume (Space)`}:{label:"Pause",title:"Pause live updates — events keep arriving and are applied when you resume (Space)"}}function $T(e,n){return e!==""&&n>0}function k5(e,n,r){return e===""?{active:!1,matched:n,total:r,count:null,empty:!1,message:null,dim:!1}:{active:!0,matched:n,total:r,count:`${n} of ${r}`,empty:n===0,message:n===0?`No agents match “${e}”`:null,dim:$T(e,n)}}const E5="Search agents, tools, model…";function qT(e,n){var i,o,l,u;if(!n)return!0;const r=n.toLowerCase();if(e.label.toLowerCase().includes(r)||(i=e.cwd)!=null&&i.toLowerCase().includes(r)||(o=e.cwdBasename)!=null&&o.toLowerCase().includes(r)||e.sessionId.toLowerCase().includes(r)||(l=e.firstPrompt)!=null&&l.toLowerCase().includes(r)||e.model&&(e.model.toLowerCase().includes(r)||js(e.model).toLowerCase().includes(r))||(u=e.provider)!=null&&u.toLowerCase().startsWith(r))return!0;for(const c of e.tools)if(c.name.toLowerCase().includes(r))return!0;return!1}function Pv(e){const n=Math.max(0,Math.floor(e/1e3));return n<60?"just now":n<3600?`${Math.floor(n/60)}m ago`:n<86400?`${Math.floor(n/3600)}h ago`:`${Math.floor(n/86400)}d ago`}function N5(e,n){return e.getFullYear()===n.getFullYear()&&e.getMonth()===n.getMonth()&&e.getDate()===n.getDate()}function C5(e,n,r){const i=new Date(e),o=Pv(n-e);return{label:N5(i,new Date(n))?o:`${o} · ${i.toLocaleDateString(r,{month:"short",day:"numeric"})}`,title:i.toLocaleString(r,{dateStyle:"medium",timeStyle:"medium"})}}function ua(e){return typeof window>"u"?"":getComputedStyle(document.documentElement).getPropertyValue(e).trim()||""}const T5={agent:QP,sessionGroup:dL},DT="react-flow__node";function OE(e){var n,r;return!!e&&((r=(n=e.classList)==null?void 0:n.contains)==null?void 0:r.call(n,DT))===!0}function R5(e){try{const n=document.querySelector(`.${DT}[data-id="${CSS.escape(e)}"]`);n==null||n.focus({preventScroll:!0})}catch{}}const j5=["a[href]","button:not(:disabled)","input:not(:disabled)","select:not(:disabled)","textarea:not(:disabled)","summary","[tabindex]"].join(","),A5=420,au=18,I5=200,M5=5*6e4,O5=6,P5=2*6e4,ky="agent-dag.layout",Ey="agent-dag.viewport",FT="agent-dag.summariesDismissed",zT="agent-dag.sessionListOpen",BT="agent-dag.detailOpen",UT="agent-dag.usagePanelOpen",PE="agent-dag.accountsPanelOpen",LE="agent-dag.versionNoticeDismissed",$E="agent-dag.oldNameNoticeDismissed",L5=15*6e4,qE={git_checkout:"this deck runs from a git checkout — pull instead:",npx:"npx runs from a cache that cannot be upgraded in place — run:",not_writable:"the install directory is not writable by this user — run:",opted_out:"installs are off (AGENTS_DECK_NO_INSTALL=1) — run:"},DE="agent-dag.autoRestart",FE="agent-dag.bundleReloadedFor";function $5(){if(typeof window>"u")return!1;try{return window.localStorage.getItem(zT)==="1"}catch{return!1}}function q5(e){if(!(typeof window>"u"))try{window.localStorage.setItem(zT,e?"1":"0")}catch{}}function D5(){if(typeof window>"u")return!1;try{return window.localStorage.getItem(BT)==="1"}catch{return!1}}function F5(e){if(!(typeof window>"u"))try{window.localStorage.setItem(BT,e?"1":"0")}catch{}}function z5(){const e=MC(UT);return e===null?!0:e==="1"}function B5(e){if(!(typeof window>"u"))try{window.localStorage.setItem(UT,e?"1":"0")}catch{}}function U5(){if(typeof window>"u")return new Set;try{const e=window.localStorage.getItem(FT);if(!e)return new Set;const n=JSON.parse(e);return new Set(Array.isArray(n)?n.filter(r=>typeof r=="string"):[])}catch{return new Set}}function zE(e){if(!(typeof window>"u"))try{const n=Array.from(e),r=n.length>200?n.slice(-200):n;window.localStorage.setItem(FT,JSON.stringify(r))}catch{}}function H5(){const e={positions:[],pins:[]};if(typeof window>"u")return e;try{const n=window.localStorage.getItem(ky);if(!n)return e;const r=JSON.parse(n);if(!("v"in r)){const o=Object.entries(r).filter(([,l])=>l&&typeof l.x=="number"&&typeof l.y=="number");return{positions:o,pins:o.map(([l])=>l)}}return{positions:Object.entries(r.positions??{}).filter(([,o])=>o&&typeof o.x=="number"&&typeof o.y=="number"),pins:Array.isArray(r.pins)?r.pins:[]}}catch{return e}}function dv(e,n){if(!(typeof window>"u"))try{const r={};for(const[i,o]of e)r[i]=o;for(const[i,o]of n)r[i]=o;window.localStorage.setItem(ky,JSON.stringify({v:2,positions:r,pins:Array.from(n.keys())}))}catch{}}function W5(){if(typeof window>"u")return null;try{const e=window.localStorage.getItem(Ey);if(!e)return null;const n=JSON.parse(e);return typeof(n==null?void 0:n.x)!="number"||typeof(n==null?void 0:n.y)!="number"||typeof(n==null?void 0:n.zoom)!="number"?null:n}catch{return null}}function V5(e){if(!(typeof window>"u"))try{window.localStorage.setItem(Ey,JSON.stringify(e))}catch{}}function BE(){if(!(typeof window>"u")){try{window.localStorage.removeItem(ky)}catch{}try{window.localStorage.removeItem(Ey)}catch{}}}function G5(e,n){const r=e.agents.get(n);if(!r)return;const i=[];for(const d of e.agents.values())d.sessionId===n&&i.push(d);const o={schemaVersion:1,exportedAt:new Date().toISOString(),sessionId:n,label:r.label,cwd:r.cwd,startedAt:r.startedAt,endedAt:r.endedAt,model:r.model,agents:i.map(d=>({id:d.id,kind:d.kind,label:d.label,parentId:d.parentId,state:d.state,startedAt:d.startedAt,endedAt:d.endedAt,model:d.model,cwd:d.cwd,usage:d.usage,prompts:d.prompts,tools:d.tools.map(m=>({id:m.id,name:m.name,inputPreview:m.inputPreview,startedAt:m.startedAt,endedAt:m.endedAt,ok:m.ok,errorPreview:m.errorPreview,usage:m.usage}))}))},l=JSON.stringify(o,null,2),u=new Blob([l],{type:"application/json"}),c=URL.createObjectURL(u),h=document.createElement("a"),p=(r.label||"session").replace(/[^a-z0-9._-]/gi,"_");h.href=c,h.download=`${en}-${p}-${n.slice(0,8)}.json`,document.body.appendChild(h),h.click(),document.body.removeChild(h),setTimeout(()=>URL.revokeObjectURL(c),1e3)}const Lv={file:"📁",shell:"⚡",web:"🌐",agent:"🤖",task:"📋",plan:"🧭",mcp:"🔌",other:"✨"},$v={file:"file",shell:"shell",web:"web",agent:"agent",task:"task",plan:"plan",mcp:"mcp",other:"other"},HT=kC;function Y5(e,n){if(!n)return null;const r=new Set([n]);let i=n;for(;i;){const l=e.agents.get(i);if(!(l!=null&&l.parentId)||r.has(l.parentId))break;r.add(l.parentId),i=l.parentId}let o=!0;for(;o;){o=!1;for(const l of e.agents.values())l.parentId&&r.has(l.parentId)&&!r.has(l.id)&&(r.add(l.id),o=!0)}return r}function K5(e,n,r,i,o,l,u,c,h,p,d,m,y,w,N,_,b,S,k){const E=[],T=[],I=new Set;if(l)for(const B of e.agents.values())S.has(B.id)&&qT(B,l)&&I.add(B.id);const O=$T(l,I.size);for(const B of e.agents.values()){if(!S.has(B.id))continue;const C=O&&!I.has(B.id),q=B.exitAt!=null,L=b!=null&&!b.has(B.id),re=[C?"rf-dim":"",q?"rf-exiting":"",L?"rf-spotlit-out":""].filter(Boolean).join(" ")||void 0,J=u.get(B.id);if(E.push({id:B.id,type:"agent",position:{x:0,y:0},data:{...B,now:n,onOpenContext:k},className:re,...J?{width:J.width,height:J.height}:null}),B.parentId&&S.has(B.parentId)){const W=ny(B.sessionId),Z=l&&!I.has(B.id)&&!I.has(B.parentId),M=q,G=_.size>0&&(_.has(B.id)||_.has(B.parentId)),K=b!=null&&!b.has(B.id)&&!b.has(B.parentId),$=B.state==="active"?2:1.5,Q=G?$+1.5:$,ne=Z||M?.2:K?.12:1,ae=["sess-edge",B.state==="active"?"sess-live":"sess-idle",M?"rf-edge-exiting":"",G?"rf-edge-selected":""].filter(Boolean).join(" ");T.push({id:`e:${B.parentId}->${B.id}`,source:B.parentId,target:B.id,animated:(B.state==="active"||G)&&!Z&&!M,type:"smoothstep",style:{"--session-hue":W,strokeWidth:Q,opacity:ne,transition:"var(--edge-transition)"},className:ae})}}const A=new Map;for(const B of e.agents.values())B.tools.length>0&&A.set(B.id,Math.min(4,B.tools.length));const U=`${w}#lanes:${J4(A)}`,z=E.filter(B=>c$(B.id,o,m,y));if(z.length>0||U!==N.current){if(z.length>0){const B=t5(E,T,{direction:"LR",pinned:o,measured:u,availableWidth:r,availableHeight:i,lanes:A});for(const C of B)IC(C.id,m,y)&&f$(C.id,C.position,m,y);r5(E,m,o,u,new Set(z.map(C=>C.id)),A)}n5(E,m,o,u,A),N.current=U}const P=s5(E,m,o,u,c,!p||d,A);P.length>0&&h(P),ru(m,e.agents),ru(y,e.agents),ru(o,e.agents),ru(u,u$(e.agents.values()));const D=[];let V=!1;for(const B of E){let C=o.get(B.id)??m.get(B.id);C||(C=d$(B.id,m,y),V=!0),D.push({...B,position:C})}return V&&(N.current=""),{nodes:D,edges:T}}function X5(){return g.jsx(ty,{children:g.jsx(Q5,{})})}function Q5(){var Va,Ga,Ya,Ka;const e=ja(),n=j.useRef(Tv()),[,r]=j.useState(0),i=j.useCallback(()=>r(H=>H+1),[]),[o,l]=j.useState(()=>new Set),[u,c]=j.useState(null),[h,p]=j.useState(null),d=j.useCallback((H,Y)=>{l(oe=>{if(!Y)return new Set([H]);const de=new Set(oe);return de.has(H)?de.delete(H):de.add(H),de}),c(oe=>Y&&oe===H?oe:H)},[]),m=j.useCallback(()=>{l(new Set),c(null)},[]),[y,w]=j.useState(null),[N,_]=j.useState(null),b=j.useCallback(H=>_(H),[]),[S,k]=j.useState(!1),E=j.useRef(U5()),[T,I]=j.useState($5);j.useEffect(()=>{q5(T)},[T]);const[O,A]=j.useState(D5);j.useEffect(()=>{F5(O)},[O]);const[U,z]=j.useState(z5);j.useEffect(()=>{B5(U)},[U]);const[P,D]=j.useState(()=>{try{const H=window.localStorage.getItem(PE);return H===null?!0:H==="1"}catch{return!0}});j.useEffect(()=>{try{window.localStorage.setItem(PE,P?"1":"0")}catch{}},[P]);const[V,B]=j.useState(null),[C,q]=j.useState(!1),[L,re]=j.useState(0),[J,W]=j.useState(0);j.useEffect(()=>{fetch("/api/sound-hook").then(H=>H.ok?H.json():null).then(H=>{H!=null&&H.ok&&(B(H.enabled===!0),re((H.foreign??[]).filter(Y=>Y.worksHere).length),W(typeof H.parked=="number"?H.parked:0))}).catch(()=>{})},[]);const Z=j.useCallback(async()=>{q(!0);try{const Y=await(await fetch("/api/sound-hook",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:!V})})).json().catch(()=>null);Y!=null&&Y.ok&&B(Y.enabled===!0),fetch("/api/sound-hook").then(oe=>oe.ok?oe.json():null).then(oe=>{oe!=null&&oe.ok&&(re((oe.foreign??[]).filter(de=>de.worksHere).length),W(typeof oe.parked=="number"?oe.parked:0))}).catch(()=>{})}catch{}finally{q(!1)}},[V]),[M,G]=j.useState(!1),[K,$]=j.useState(null),[Q,ne]=j.useState(()=>{if(typeof window>"u")return"";try{return window.localStorage.getItem(LE)??""}catch{return""}}),[ae,fe]=j.useState(!1),te=j.useRef(0),[ce,Ce]=j.useState(!1),ve=j.useCallback((H=!1)=>{H&&(te.current=Date.now(),Ce(!0)),fetch(H?"/api/version?refresh=1":"/api/version").then(Y=>Y.ok?Y.json():null).then(Y=>{Y&&$(Y)}).catch(()=>{}).finally(()=>{H&&Ce(!1)})},[]),je=j.useCallback(()=>{ve(Date.now()-te.current>=L5)},[ve]);j.useEffect(()=>{ve();const H=window.setInterval(je,5*6e4),Y=()=>{document.visibilityState==="visible"&&je()};return document.addEventListener("visibilitychange",Y),()=>{window.clearInterval(H),document.removeEventListener("visibilitychange",Y)}},[ve,je]),j.useEffect(()=>{M&&ve()},[M,ve]);const ye=(K==null?void 0:K.notice)??null,[Ee,Ye]=j.useState(null),[we,_e]=j.useState(Ov);j.useEffect(()=>{let H=!1;return fetch("/api/health").then(Y=>Y.ok?Y.json():null).then(Y=>{H||(_e(m5(Y)),!(!Y||typeof Y.workspace!="string")&&Ye(Y.workspace))}).catch(()=>{}),()=>{H=!0}},[M]);const Be=j.useRef(we);Be.current=we;const it=ye?`${ye.kind}:${ye.to}`:"",ft=ye!=null&&Q!==it,Ze=j.useCallback(()=>{if(!ye)return;const H=Q===it?"":it;ne(H);try{window.localStorage.setItem(LE,H)}catch{}},[ye,it,Q]),[De,Je]=j.useState(()=>{if(typeof window>"u")return"";try{return window.localStorage.getItem($E)??""}catch{return""}}),Xe=K!=null&&K.invokedAs&&K.invokedAs!==en?K.invokedAs:null,Mt=Xe!=null&&De!==Xe,lt=j.useCallback(()=>{if(Xe){Je(Xe);try{window.localStorage.setItem($E,Xe)}catch{}}},[Xe]),Qe=((Va=K==null?void 0:K.upgrade)==null?void 0:Va.state)??"idle",Ot=Z3(K==null?void 0:K.upgrade),zt=j.useCallback(async()=>{try{await fetch("/api/upgrade",{method:"POST"})}catch{}ve()},[ve]);j.useEffect(()=>{if(Qe!=="running")return;const H=window.setInterval(ve,3e3);return()=>window.clearInterval(H)},[Qe,ve]);const et=j.useCallback(async()=>{var oe;const H=K==null?void 0:K.command;if(!H)return;let Y=!1;try{Y=await Promise.race([((oe=navigator.clipboard)==null?void 0:oe.writeText(H).then(()=>!0))??Promise.resolve(!1),new Promise(de=>window.setTimeout(()=>de(!1),500))])}catch{Y=!1}if(!Y)try{const de=document.createElement("textarea");de.value=H,de.setAttribute("readonly",""),de.style.cssText="position:fixed;top:0;left:0;opacity:0",document.body.appendChild(de),de.select(),Y=document.execCommand("copy"),de.remove()}catch{Y=!1}Y&&(fe(!0),window.setTimeout(()=>fe(!1),1600))},[K==null?void 0:K.command]),bt=j.useCallback(()=>{I(H=>(H||D(!1),!H))},[]),ht=j.useCallback(()=>{D(H=>(H||I(!1),!H))},[]),[vt,hn]=j.useState(!1),[Gt,Pt]=j.useState(0),bn=j.useRef(null),[gr,Sn]=j.useState(!1),Bt=j.useRef(null),[Fn,pn]=j.useState(0),vr=j.useRef(g$()),[Hr,xi]=j.useState(!1),Wr=j.useCallback(()=>{const H=vr.current,Y=H.setPaused(!H.paused);for(const oe of Y)n.current=Fx(n.current,oe);xi(H.paused)},[]),[ut,_i]=j.useState(Date.now()),[mn,Is]=j.useState(()=>{if(typeof window>"u")return!0;try{return window.localStorage.getItem(DE)!=="0"}catch{return!0}}),yr=j.useCallback(()=>{Is(H=>{const Y=!H;try{window.localStorage.setItem(DE,Y?"1":"0")}catch{}return Y})},[]),[zn,kn]=j.useState(!1),[Vr,Gr]=j.useState("restart"),[Yr,bi]=j.useState(null),sr=j.useRef(!1),Ms=j.useRef(null),En=j.useRef(0),wr=j.useCallback(async H=>{if(sr.current)return;const Y=(H==null?void 0:H.upgrade)===!0;sr.current=!0,Ms.current=Ot;const oe=++En.current;Gr(Y?"npx":"restart"),kn(!0);try{window.sessionStorage.setItem("agent-dag.restartPending",(ye==null?void 0:ye.to)??"")}catch{}try{await fetch("/api/restart",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({upgrade:Y})})}catch{}window.setTimeout(()=>{if(En.current===oe&&sr.current){sr.current=!1,kn(!1);try{window.sessionStorage.removeItem("agent-dag.restartPending")}catch{}}},Y?18e4:3e4)},[ye==null?void 0:ye.to,Ot]),Os=j.useRef(null);j.useEffect(()=>{let H=!1;for(const oe of n.current.agents.values())if(oe.state==="active"){H=!0;break}const Y=e$({enabled:mn,kind:ye==null?void 0:ye.kind,canRestart:(K==null?void 0:K.canRestart)===!0,busy:H,idleSince:Os.current,now:ut});Os.current=Y.idleSince,Y.restart&&wr()},[mn,ye==null?void 0:ye.kind,K==null?void 0:K.canRestart,ut,wr]),j.useEffect(()=>{const H=K==null?void 0:K.running;let Y=null,oe=null;try{Y=window.sessionStorage.getItem("agent-dag.restartPending"),oe=window.sessionStorage.getItem(FE)}catch{return}const de=Q3({bundle:"1.35.19",running:H,pending:Y,lastTried:oe});if(de==="reload"){try{window.sessionStorage.setItem(FE,H??"")}catch{return}window.location.reload();return}if(de!=="confirm")return;try{window.sessionStorage.removeItem("agent-dag.restartPending")}catch{}sr.current=!1,kn(!1),bi(H??null);const xe=window.setTimeout(()=>bi(null),6e3);return()=>window.clearTimeout(xe)},[K==null?void 0:K.running]),j.useEffect(()=>{if(zn&&J3({asked:Ms.current,reported:Ot})){try{window.sessionStorage.removeItem("agent-dag.restartPending")}catch{}sr.current=!1,kn(!1)}},[zn,Ot]);const Ps=j.useRef(H5()).current;j.useRef(!1);const Tt=j.useRef(new Map(Ps.positions.filter(([H])=>Ps.pins.includes(H)))),xr=j.useRef(null),_r=j.useState(()=>W5())[0],[Yt,Kr]=j.useState(""),[Si,nt]=j.useState(()=>new Set),[br,Xr]=j.useState(_$),[Ls,Qr]=j.useState(!1),Zr=j.useRef(null);j.useEffect(()=>{document.documentElement.dataset.theme=br;try{window.localStorage.setItem(OC,br)}catch{}},[br]),j.useEffect(()=>{if(!_r)return;const H=window.setTimeout(()=>{try{e.setViewport(_r,{duration:0})}catch{}},60);return()=>window.clearTimeout(H)},[e,_r]);const $s=j.useRef(!0);j.useEffect(()=>{const H=new EventSource("/events"),Y=m$(i,{now:()=>Date.now(),setTimeout:(oe,de)=>window.setTimeout(oe,de),clearTimeout:oe=>window.clearTimeout(oe)});return H.addEventListener("open",()=>{G(!0),Qr(!0)}),H.addEventListener("error",()=>G(!1)),H.addEventListener("replay-end",()=>{$s.current=!1,Y.flush()}),H.addEventListener("hook",oe=>{try{const de=JSON.parse(oe.data);if(!vr.current.accept(de))return;n.current=Fx(n.current,de),de.replay===!0||$s.current||Date.now()-de.receivedAt>3e4?Y.replay():Y.live()}catch{}}),()=>{H.close(),Y.cancel()}},[i]),j.useEffect(()=>{const H=setInterval(()=>{const Y=Date.now();_i(Y);let oe=mP(n.current,Y,Dx);gP(n.current,Y,Dx)&&(oe=!0),hP(n.current,Y,I5,M5)&&(oe=!0),pP(n.current,Y,O5,P5)&&(oe=!0),oe&&i()},250);return()=>clearInterval(H)},[i]);const ir=j.useRef(null),St=j.useRef(0),qs=420,pt=j.useCallback((H=500)=>{try{const Se=document.querySelector(".canvas-wrap"),Pe=Array.from(document.querySelectorAll(".react-flow__node")).filter(wt=>wt.offsetWidth>0);if(!Se||Pe.length===0)return;const Fe=Se.getBoundingClientRect(),Ge=e.getViewport();if(!Number.isFinite(Ge.zoom)||Ge.zoom<=0)return;const We=wt=>(wt-Fe.left-Ge.x)/Ge.zoom,ze=wt=>(wt-Fe.top-Ge.y)/Ge.zoom,Tn=Pe.map(wt=>wt.getBoundingClientRect()),Ys=Math.min(...Tn.map(wt=>We(wt.left))),Ao=Math.max(...Tn.map(wt=>We(wt.right))),Ks=Math.min(...Tn.map(wt=>ze(wt.top))),Io=Math.max(...Tn.map(wt=>ze(wt.bottom))),Xa=Ao-Ys,us=Io-Ks;if(!(Xa>0&&us>0))return;const Qt=Math.max(.2,Math.min(1,(Fe.width-160)/(Xa+qs)*.86,(Fe.height-160)/us*.86));e.setViewport({x:80-Ys*Qt,y:Math.max(80,(Fe.height-us*Qt)/2)-Ks*Qt,zoom:Qt},{duration:H}),St.current=Date.now(),window.setTimeout(()=>{try{const wt={x:80-Ys*Qt,y:Math.max(80,(Fe.height-us*Qt)/2)-Ks*Qt,zoom:Qt},Mo=e.getViewport();(Math.abs(Mo.zoom-Qt)>.01||Math.abs(Mo.x-wt.x)>2)&&e.setViewport(wt,{duration:0})}catch{}},H+60)}catch{}},[e]),ko=j.useRef(""),Eo=j.useRef(null),qa=j.useRef(0),Sr=j.useCallback(()=>{qa.current=Date.now()},[]),Jr="agent-dag.autoFitDisabled",gn=j.useRef((()=>{if(typeof window>"u")return!1;try{return window.localStorage.getItem(Jr)==="1"}catch{return!1}})()),[Ds,Da]=j.useState(gn.current),Fa=j.useCallback(()=>{if(!gn.current){gn.current=!0,Da(!0);try{window.localStorage.setItem(Jr,"1")}catch{}}},[]),No=j.useCallback(()=>{gn.current=!1,Da(!1);try{window.localStorage.removeItem(Jr)}catch{}pt(400)},[e,pt]);j.useEffect(()=>{const H=setInterval(()=>{if(gn.current||Date.now()-qa.current<800)return;const Y=n.current,oe=Date.now(),de=[];for(const We of Y.agents.values())Mv(We,oe)&&de.push({id:We.id});if(de.length===0)return;const xe=e.getViewport(),Se=window.innerWidth-360,Pe=window.innerHeight-52;let Fe=!1,Ge=!1;for(const{id:We}of de){const ze=or.current.get(We),Tn=Tt.current.get(We)??Kt.current.get(We);if(!ze||!Tn)continue;Ge=!0;const Ys=Tn.x*xe.zoom+xe.x,Ao=Tn.y*xe.zoom+xe.y,Ks=(Tn.x+ze.width)*xe.zoom+xe.x,Io=(Tn.y+ze.height)*xe.zoom+xe.y;if(Ks>0&&Ys<Se&&Io>0&&Ao<Pe){Fe=!0;break}}Ge&&!Fe&&pt(600)},1500);return()=>clearInterval(H)},[e]);const ki=j.useRef(!1),or=j.useRef(new Map),es=j.useRef(0),ec=j.useCallback(H=>{const Y=or.current;let oe=!1;for(const de of H.nodeInternals.values()){const xe=de.width,Se=de.height;if(xe==null||Se==null)continue;const Pe=Y.get(de.id);Pe?(Math.abs(Pe.height-Se)>4||Math.abs(Pe.width-xe)>4)&&(Y.set(de.id,{width:xe,height:Se}),oe=!0):(Y.set(de.id,{width:xe,height:Se}),oe=!0)}return oe&&(es.current+=1),es.current},[]),Ei=Ke(ec),[He,za]=j.useState(0);j.useEffect(()=>{let H=0;const Y=()=>{const de=or.current;let xe=!1;for(const Se of document.querySelectorAll(".react-flow__node[data-id]")){const Pe=Se.getAttribute("data-id");if(!Pe)continue;const Fe=Se.offsetWidth,Ge=Se.offsetHeight;if(!Fe||!Ge)continue;const We=de.get(Pe);(!We||Math.abs(We.width-Fe)>4||Math.abs(We.height-Ge)>4)&&(de.set(Pe,{width:Fe,height:Ge}),xe=!0)}xe&&za(Se=>Se+1)};if(ki.current)return;let oe=0;return document.visibilityState==="hidden"?oe=window.setTimeout(Y,32):H=requestAnimationFrame(Y),()=>{cancelAnimationFrame(H),window.clearTimeout(oe)}});const Kt=j.useRef(new Map(Ps.positions)),Co=j.useRef(new Set),Ni=j.useRef(""),kr=j.useMemo(()=>{const H=[];for(const Y of n.current.agents.values())Mv(Y,ut)&&H.push(Y.id+(Y.parentId?`>${Y.parentId}`:""));return H.sort(),`${H.join("|")}#sv${Ei}.${He}`},[n.current,n.current.lastSeq,ut,Ei,He]),ts=j.useRef(null);j.useEffect(()=>(ts.current!=null&&window.clearTimeout(ts.current),ts.current=window.setTimeout(()=>{dv(Kt.current,Tt.current)},1500),()=>{ts.current!=null&&window.clearTimeout(ts.current)}),[kr]),j.useEffect(()=>{if(ko.current===kr)return;const H=ko.current;if(ko.current=kr,!H||gn.current)return;Date.now()-St.current>1200&&pt(400),ir.current&&window.clearTimeout(ir.current),ir.current=window.setTimeout(()=>{gn.current||pt(500)},280)},[kr,e,pt]);const Fs=j.useMemo(()=>{if(o.size===0)return null;const H=new Set;for(const Y of o){const oe=Y5(n.current,Y);if(oe)for(const de of oe)H.add(de)}return H.size>0?H:null},[n.current,n.current.lastSeq,o]),Nn=j.useMemo(()=>o5(n.current,ut),[n.current,n.current.lastSeq,ut]),Bn=j.useRef(null),[vn,Un]=j.useState({w:0,h:0});j.useEffect(()=>{const H=Bn.current;if(!H||typeof ResizeObserver>"u")return;const Y=new ResizeObserver(oe=>{var xe;const de=(xe=oe[0])==null?void 0:xe.contentRect;de&&Un(Se=>Math.abs(Se.w-de.width)>40||Math.abs(Se.h-de.height)>40?{w:de.width,h:de.height}:Se)});return Y.observe(H),Un({w:H.clientWidth,h:H.clientHeight}),()=>Y.disconnect()},[]);const zs=j.useCallback(H=>{var oe,de;const Y=H.target;!(Y!=null&&Y.closest)||Y.closest(j5)!==H.currentTarget||(H.preventDefault(),(de=(oe=document.activeElement)==null?void 0:oe.blur)==null||de.call(oe))},[]),Bs=j.useRef(new Map),[Hn,tc]=j.useState(!1);j.useEffect(()=>{const H=window.setTimeout(()=>tc(!0),2500);return()=>window.clearTimeout(H)},[]);const[Ba,Er]=j.useState(!1),ar=j.useRef(null),[Ci,Us]=j.useState(!1),Ua=j.useCallback(()=>{ar.current&&(window.clearTimeout(ar.current),ar.current=null),Er(!1)},[]),nc=j.useCallback(H=>{H.length!==0&&queueMicrotask(()=>{Er(!0),ar.current&&window.clearTimeout(ar.current),ar.current=window.setTimeout(()=>Er(!1),A5+80)})},[]);j.useEffect(()=>()=>{ar.current&&window.clearTimeout(ar.current)},[]);const ns=vn.w>0?vn.w*.92:0,rs=vn.h>0?vn.h:0,{nodes:Xt,edges:Ti}=j.useMemo(()=>K5(n.current,ut,ns,rs,Tt.current,Yt,or.current,Bs.current,nc,Hn,Ci,Kt.current,Co.current,kr,Ni,o,Fs,Nn,b),[n.current,n.current.lastSeq,ut,ns,rs,Hn,Ci,Yt,kr,o,Fs,Nn,b,Gt]),Ha=j.useMemo(()=>{const H=new Map;for(const oe of Xt){const de=oe.data;if(!(de!=null&&de.sessionId)||de.exitAt!=null)continue;const xe=oe.width,Se=oe.height;if(xe==null||Se==null)continue;const Pe=oe.position.x,Fe=oe.position.y,Ge=Pe+xe,We=Fe+Se,ze=H.get(de.sessionId);ze?(ze.minX=Math.min(ze.minX,Pe),ze.minY=Math.min(ze.minY,Fe),ze.maxX=Math.max(ze.maxX,Ge),ze.maxY=Math.max(ze.maxY,We)):H.set(de.sessionId,{minX:Pe,minY:Fe,maxX:Ge,maxY:We})}const Y=[];for(const[oe,de]of H){const xe=de.maxX-de.minX+au*2,Se=de.maxY-de.minY+au*2;Y.push({id:`group:${oe}`,type:PT,position:{x:de.minX-au,y:de.minY-au},data:{sessionId:oe,w:xe,h:Se},width:xe,height:Se,style:{width:xe,height:Se},zIndex:-1,draggable:!0,selectable:!1,focusable:!1,deletable:!1,connectable:!1})}return Y},[Xt,ut]),rc=j.useMemo(()=>{const H=[...Ha,...Xt],Y=Bt.current;return!Y||Y.size===0?H:H.map(oe=>{const de=Y.get(oe.id);return de?{...oe,position:de}:oe})},[Ha,Xt,Fn]),Nr=j.useMemo(()=>{if(!Yt)return null;const H=new Set;for(const Y of n.current.agents.values())Nn.has(Y.id)&&qT(Y,Yt)&&H.add(Y.id);return H},[n.current,n.current.lastSeq,Yt,Nn]),Cn=j.useMemo(()=>k5(Yt,(Nr==null?void 0:Nr.size)??0,Nn.size),[Yt,Nr,Nn]),ss=j.useMemo(()=>{const H=new Set;for(const Y of n.current.agents.values())for(const oe of Y.tools)H.add(HT(oe.name));return Object.keys(Lv).filter(Y=>H.has(Y))},[n.current,n.current.lastSeq]);j.useEffect(()=>{if(ss.length<=1){Sn(!1);return}let H=0;const Y=()=>{const oe=bn.current;if(oe&&!document.hidden){const de=oe.getBoundingClientRect(),xe=Array.from(document.querySelectorAll(".react-flow__node, .tool-burst")).map(Pe=>Pe.getBoundingClientRect()),Se=l5(de,xe,8);Sn(Pe=>Pe===Se?Pe:Se)}H=window.setTimeout(Y,300)};return Y(),()=>window.clearTimeout(H)},[ss.length]);const is=j.useMemo(()=>{const H=new Map;for(const oe of n.current.agents.values())for(const de of oe.tools){if(!de.name.startsWith("mcp__"))continue;const xe=de.name.slice(5),Se=xe.indexOf("__"),Pe=Se>0?xe.slice(0,Se):xe;H.set(Pe,(H.get(Pe)??0)+1)}const Y=[];for(const[oe,de]of H){let xe=5381;for(let Se=0;Se<oe.length;Se++)xe=(xe<<5)+xe^oe.charCodeAt(Se);Y.push({server:oe,count:de,hue:Math.abs(xe)%360})}return Y.sort((oe,de)=>de.count-oe.count),Y},[n.current,n.current.lastSeq]),lr=j.useCallback(H=>{nt(Y=>{const oe=new Set(Y);return oe.has(H)?oe.delete(H):oe.add(H),oe})},[]),Rt=u?n.current.agents.get(u):null,os=h?Array.from(n.current.agents.values()).flatMap(H=>H.tools).find(H=>H.id===h)??null:null,To=j.useCallback(async()=>{try{await fetch("/api/clear",{method:"POST"})}catch{}n.current=Tv(),Tt.current.clear(),or.current.clear(),Kt.current.clear(),Ni.current="",BE(),m(),i()},[i,m]),Hs=j.useRef(S);Hs.current=S;const Ws=j.useRef(!1);Ws.current=os!=null||vt||N!=null||y!=null;const Vs=j.useCallback(H=>{const Y=n$(H,{confirmOpen:Hs.current,modalOpen:Ws.current});Y==="confirm"?k(!0):Y==="clear"&&(k(!1),To())},[To]),Lt=j.useCallback(()=>{Tt.current.clear(),Kt.current.clear(),Ni.current="",BE(),i(),window.setTimeout(()=>pt(500),80)},[i,e,pt]),Cr=j.useCallback(()=>pt(500),[pt]),as=j.useRef(Xt);as.current=Xt;const ls=j.useRef(u);ls.current=u;const Ro=j.useCallback(H=>{const Y=as.current,oe=s$(Y.map(Se=>({id:Se.id,x:Se.position.x,y:Se.position.y})),ls.current,H),de=oe?Y.find(Se=>Se.id===oe):void 0;if(!de)return;const xe=OE(document.activeElement);d(de.id,!1),window.setTimeout(()=>{try{e.fitView({padding:.35,duration:350,nodes:[de]})}catch{}St.current=Date.now(),xe&&R5(de.id)},30)},[d,e]),Ri=j.useCallback(H=>{d(H,!1),window.setTimeout(()=>{try{const Y=as.current.find(oe=>oe.id===H);Y&&e.fitView({padding:.3,duration:500,nodes:[Y]}),St.current=Date.now()}catch{}},60)},[d,e]);j.useEffect(()=>{const H=Y=>{var Fe,Ge,We;const oe=Y.target,de={tagName:oe==null?void 0:oe.tagName,isContentEditable:oe==null?void 0:oe.isContentEditable,role:(Fe=oe==null?void 0:oe.getAttribute)==null?void 0:Fe.call(oe,"role"),type:oe==null?void 0:oe.type};if(Y.key==="Escape"){const ze=PP({overlayOpen:Eu.depth()>0,typing:ry(de)});ze==="dismiss"?Eu.dismissTop():ze==="blur"?oe==null||oe.blur():(l$(de)&&(oe==null||oe.blur()),m());return}if(_C(Y))return;const xe=OE(oe)?(Ge=oe==null?void 0:oe.getAttribute)==null?void 0:Ge.call(oe,"data-id"):null,Se=xe&&as.current.some(ze=>ze.id===xe)?xe:null,Pe=o$(Y,Se);if(Pe.kind==="activate"){Y.preventDefault(),d(Pe.nodeId,Pe.additive);return}if(Pe.kind!=="node"&&!(Pe.nodeId==null&&UP(de))){if(Y.key==="/"){Y.preventDefault(),(We=Zr.current)==null||We.focus();return}Y.key===" "&&(Y.preventDefault(),Wr()),(Y.key==="c"||Y.key==="C")&&Vs("shortcut"),(Y.key==="r"||Y.key==="R")&&Lt(),(Y.key==="f"||Y.key==="F")&&Cr(),(Y.key==="l"||Y.key==="L")&&bt(),(Y.key==="h"||Y.key==="H")&&hn(ze=>!ze),(Y.key==="u"||Y.key==="U")&&z(ze=>!ze),(Y.key==="a"||Y.key==="A")&&Be.current.claude&&ht(),(Y.key==="j"||Y.key==="J")&&Ro(1),(Y.key==="k"||Y.key==="K")&&Ro(-1),(Y.key==="t"||Y.key==="T")&&Xr(ze=>ze==="dark"?"light":"dark")}};return window.addEventListener("keydown",H),()=>window.removeEventListener("keydown",H)},[Vs,Lt,Cr,m,d,Ro,Wr]);const Tr=n.current.agents.size,Gs=new Set(Array.from(n.current.agents.values()).map(H=>H.sessionId)).size,tn=j.useMemo(()=>JL(n.current.agents.values()),[n.current,n.current.lastSeq]),jo=j.useMemo(()=>e3(n.current.agents.values()),[n.current,n.current.lastSeq]),Wa=j.useRef(null);j.useEffect(()=>{const H=b$({waiting:tn.length,running:jo}),Y=Wa.current;if(Wa.current=H,(Y==null?void 0:Y.title)!==H.title&&(document.title=H.title),(Y==null?void 0:Y.icon)!==H.icon){const oe=document.querySelector('link[rel="icon"]');oe&&(oe.href=S$[H.icon])}},[tn.length,jo]);const[sc,ic]=j.useState(""),ji=E$(tn);j.useEffect(()=>{ic(H=>N$(H,ji))},[ji]);const nn=j.useMemo(()=>{let H=0,Y=0,oe=0,de=0,xe=0,Se=0,Pe=0,Fe=0,Ge=0;for(const We of n.current.agents.values()){H+=We.usage.inputTokens,Y+=We.usage.outputTokens,oe+=We.usage.cacheReadTokens,de+=We.usage.cacheCreateTokens;const ze=dn(We.usage,We.model);xe+=ze.total,Se+=ze.input,Pe+=ze.output,Fe+=ze.cacheRead,Ge+=ze.cacheWrite}return{inT:H,outT:Y,cacheR:oe,cacheC:de,sum:H+Y,cost:{total:xe,input:Se,output:Pe,cacheRead:Fe,cacheWrite:Ge}}},[n.current,n.current.lastSeq]);return g.jsxs("div",{className:"app",children:[g.jsx("a",{className:"skip-link",href:"#canvas",children:"Skip to the canvas"}),g.jsxs("header",{className:"topbar",children:[g.jsxs("div",{className:"brand",children:[g.jsx("span",{className:"logo"}),g.jsx("h1",{children:en}),ye?g.jsxs("button",{type:"button",className:"v stale",onClick:Ze,"aria-label":h5({...ye,open:ft}),title:ye.kind==="restart"?`Running v${ye.from}; v${ye.to} is installed on disk. Restart to pick it up.`:`Running v${ye.from}; v${ye.to} is on npm.`,children:["v",ye.from,g.jsx("span",{className:"v-dot","aria-hidden":!0})]}):(()=>{const H={running:(K==null?void 0:K.running)??"1.35.19",latest:K==null?void 0:K.latest,latestPending:K==null?void 0:K.latestPending,checkedAgo:K!=null&&K.checkedAt?Pv(ut-K.checkedAt):null,checkDisabled:K==null?void 0:K.checkDisabled,checking:ce};return g.jsxs("button",{type:"button",className:ce?"v checking":"v",onClick:()=>ve(!0),"aria-busy":ce||void 0,"aria-label":f5(H),title:d5(H),children:["v",H.running]})})()]}),Rt&&(()=>{const H=dn(Rt.usage,Rt.model),Y=Math.max(0,((Rt.endedAt??ut)-Rt.startedAt)/1e3),oe=Rt.state==="active"?ku(H.total,Y):null,de=o.size-1;return g.jsxs("button",{type:"button",className:"selected-ribbon",title:`Fit view to ${Rt.label}`,onClick:()=>{try{const xe=Xt.find(Se=>Se.id===Rt.id);xe&&e.fitView({padding:.35,duration:500,nodes:[xe]}),St.current=Date.now()}catch{}},children:[g.jsx("span",{className:`state-pill state-${Rt.state}`,children:Rt.state==="active"?"live":Rt.state}),g.jsx("span",{className:"selected-label",children:Rt.label}),H.total>0&&g.jsxs("span",{className:"selected-cost",children:[qe(H.total),oe?g.jsxs("span",{className:"selected-rate",children:[" · ",oe]}):null]}),de>0&&g.jsxs("span",{className:"selected-extra",children:["+",de]}),g.jsx("span",{"aria-hidden":!0,className:"selected-close",onClick:xe=>{xe.stopPropagation(),m()},children:"×"})]})})(),g.jsxs("div",{className:"actions",children:[g.jsxs("div",{className:"search",children:[g.jsx("span",{className:"search-icon","aria-hidden":!0,children:"⌕"}),g.jsx("input",{ref:Zr,type:"text",placeholder:E5,value:Yt,onChange:H=>Kr(H.target.value),spellCheck:!1,"aria-label":"Filter the graph"}),Yt?g.jsx("button",{className:"search-clear","aria-label":"Clear search",onClick:()=>Kr(""),children:"×"}):g.jsx("kbd",{className:"search-kbd","aria-hidden":!0,children:"/"}),Cn.count&&g.jsx("span",{className:`search-count${Cn.empty?" none":""}`,"aria-live":"polite",title:`${Cn.count} agents on the canvas match “${Yt}”`,children:Cn.count})]}),g.jsxs("span",{className:"status",children:[(()=>{const H=b5({connected:M,paused:Hr,held:vr.current.size});return g.jsx("span",{className:`pill ${H.tone}`,title:H.title,children:H.label})})(),g.jsxs("span",{className:"stat",title:y5(we),children:[g.jsx("span",{className:"count",children:Gs}),g.jsx("span",{className:"lbl",children:"sessions"})]}),g.jsxs("span",{className:"stat",title:"Total agents (root + subagents)",children:[g.jsx("span",{className:"count",children:Tr}),g.jsx("span",{className:"lbl",children:"agents"})]}),g.jsxs("span",{className:"stat",title:w5(we),children:[g.jsx("span",{className:"count",children:n.current.totalEvents}),g.jsx("span",{className:"lbl",children:"events"})]}),nn.sum>0&&g.jsxs("span",{className:"stat",title:`in:${nn.inT.toLocaleString()} out:${nn.outT.toLocaleString()} cache-r:${nn.cacheR.toLocaleString()} cache-c:${nn.cacheC.toLocaleString()}`,children:[g.jsx("span",{className:"count",children:Dt(nn.sum)}),g.jsx("span",{className:"lbl",children:"tokens"})]}),is.length>0&&g.jsxs("span",{className:"stat mcp-legend",title:`MCP servers seen this session:
|
|
72
|
+
${n.parked} of your own sound hook${n.parked>1?"s were":" was"} set aside so this switch actually controls the sound. Nothing was deleted — shift-click to put ${n.parked>1?"them":"it"} back.`:"";return r+i+o+l}function LT(e){const n=Math.max(0,Math.floor(e));return`${n} event${n===1?"":"s"}`}function b5(e){return e.connected?e.paused?{tone:"paused",label:"paused",title:e.held>0?`Connected — ${LT(e.held)} held until you resume (Space)`:"Connected — updates held until you resume (Space)"}:{tone:"live",label:"live",title:"Receiving events"}:{tone:"dead",label:"offline",title:e.paused?"SSE disconnected — and the canvas is paused, so resuming will not bring events back until it reconnects":"SSE disconnected"}}function S5(e){return e.paused?e.held<=0?{label:"Resume",title:"Nothing has arrived since you paused. Resume to follow the canvas again (Space)"}:{label:`Resume · ${e.held} held`,title:`${LT(e.held)} arrived while paused and will be applied in order when you resume (Space)`}:{label:"Pause",title:"Pause live updates — events keep arriving and are applied when you resume (Space)"}}function $T(e,n){return e!==""&&n>0}function k5(e,n,r){return e===""?{active:!1,matched:n,total:r,count:null,empty:!1,message:null,dim:!1}:{active:!0,matched:n,total:r,count:`${n} of ${r}`,empty:n===0,message:n===0?`No agents match “${e}”`:null,dim:$T(e,n)}}const E5="Search agents, tools, model…";function qT(e,n){var i,o,l,u;if(!n)return!0;const r=n.toLowerCase();if(e.label.toLowerCase().includes(r)||(i=e.cwd)!=null&&i.toLowerCase().includes(r)||(o=e.cwdBasename)!=null&&o.toLowerCase().includes(r)||e.sessionId.toLowerCase().includes(r)||(l=e.firstPrompt)!=null&&l.toLowerCase().includes(r)||e.model&&(e.model.toLowerCase().includes(r)||js(e.model).toLowerCase().includes(r))||(u=e.provider)!=null&&u.toLowerCase().startsWith(r))return!0;for(const c of e.tools)if(c.name.toLowerCase().includes(r))return!0;return!1}function Pv(e){const n=Math.max(0,Math.floor(e/1e3));return n<60?"just now":n<3600?`${Math.floor(n/60)}m ago`:n<86400?`${Math.floor(n/3600)}h ago`:`${Math.floor(n/86400)}d ago`}function N5(e,n){return e.getFullYear()===n.getFullYear()&&e.getMonth()===n.getMonth()&&e.getDate()===n.getDate()}function C5(e,n,r){const i=new Date(e),o=Pv(n-e);return{label:N5(i,new Date(n))?o:`${o} · ${i.toLocaleDateString(r,{month:"short",day:"numeric"})}`,title:i.toLocaleString(r,{dateStyle:"medium",timeStyle:"medium"})}}function ua(e){return typeof window>"u"?"":getComputedStyle(document.documentElement).getPropertyValue(e).trim()||""}const T5={agent:QP,sessionGroup:dL},DT="react-flow__node";function OE(e){var n,r;return!!e&&((r=(n=e.classList)==null?void 0:n.contains)==null?void 0:r.call(n,DT))===!0}function R5(e){try{const n=document.querySelector(`.${DT}[data-id="${CSS.escape(e)}"]`);n==null||n.focus({preventScroll:!0})}catch{}}const j5=["a[href]","button:not(:disabled)","input:not(:disabled)","select:not(:disabled)","textarea:not(:disabled)","summary","[tabindex]"].join(","),A5=420,au=18,I5=200,M5=5*6e4,O5=6,P5=2*6e4,ky="agent-dag.layout",Ey="agent-dag.viewport",FT="agent-dag.summariesDismissed",zT="agent-dag.sessionListOpen",BT="agent-dag.detailOpen",UT="agent-dag.usagePanelOpen",PE="agent-dag.accountsPanelOpen",LE="agent-dag.versionNoticeDismissed",$E="agent-dag.oldNameNoticeDismissed",L5=15*6e4,qE={git_checkout:"this deck runs from a git checkout — pull instead:",npx:"npx runs from a cache that cannot be upgraded in place — run:",not_writable:"the install directory is not writable by this user — run:",opted_out:"installs are off (AGENTS_DECK_NO_INSTALL=1) — run:"},DE="agent-dag.autoRestart",FE="agent-dag.bundleReloadedFor";function $5(){if(typeof window>"u")return!1;try{return window.localStorage.getItem(zT)==="1"}catch{return!1}}function q5(e){if(!(typeof window>"u"))try{window.localStorage.setItem(zT,e?"1":"0")}catch{}}function D5(){if(typeof window>"u")return!1;try{return window.localStorage.getItem(BT)==="1"}catch{return!1}}function F5(e){if(!(typeof window>"u"))try{window.localStorage.setItem(BT,e?"1":"0")}catch{}}function z5(){const e=MC(UT);return e===null?!0:e==="1"}function B5(e){if(!(typeof window>"u"))try{window.localStorage.setItem(UT,e?"1":"0")}catch{}}function U5(){if(typeof window>"u")return new Set;try{const e=window.localStorage.getItem(FT);if(!e)return new Set;const n=JSON.parse(e);return new Set(Array.isArray(n)?n.filter(r=>typeof r=="string"):[])}catch{return new Set}}function zE(e){if(!(typeof window>"u"))try{const n=Array.from(e),r=n.length>200?n.slice(-200):n;window.localStorage.setItem(FT,JSON.stringify(r))}catch{}}function H5(){const e={positions:[],pins:[]};if(typeof window>"u")return e;try{const n=window.localStorage.getItem(ky);if(!n)return e;const r=JSON.parse(n);if(!("v"in r)){const o=Object.entries(r).filter(([,l])=>l&&typeof l.x=="number"&&typeof l.y=="number");return{positions:o,pins:o.map(([l])=>l)}}return{positions:Object.entries(r.positions??{}).filter(([,o])=>o&&typeof o.x=="number"&&typeof o.y=="number"),pins:Array.isArray(r.pins)?r.pins:[]}}catch{return e}}function dv(e,n){if(!(typeof window>"u"))try{const r={};for(const[i,o]of e)r[i]=o;for(const[i,o]of n)r[i]=o;window.localStorage.setItem(ky,JSON.stringify({v:2,positions:r,pins:Array.from(n.keys())}))}catch{}}function W5(){if(typeof window>"u")return null;try{const e=window.localStorage.getItem(Ey);if(!e)return null;const n=JSON.parse(e);return typeof(n==null?void 0:n.x)!="number"||typeof(n==null?void 0:n.y)!="number"||typeof(n==null?void 0:n.zoom)!="number"?null:n}catch{return null}}function V5(e){if(!(typeof window>"u"))try{window.localStorage.setItem(Ey,JSON.stringify(e))}catch{}}function BE(){if(!(typeof window>"u")){try{window.localStorage.removeItem(ky)}catch{}try{window.localStorage.removeItem(Ey)}catch{}}}function G5(e,n){const r=e.agents.get(n);if(!r)return;const i=[];for(const d of e.agents.values())d.sessionId===n&&i.push(d);const o={schemaVersion:1,exportedAt:new Date().toISOString(),sessionId:n,label:r.label,cwd:r.cwd,startedAt:r.startedAt,endedAt:r.endedAt,model:r.model,agents:i.map(d=>({id:d.id,kind:d.kind,label:d.label,parentId:d.parentId,state:d.state,startedAt:d.startedAt,endedAt:d.endedAt,model:d.model,cwd:d.cwd,usage:d.usage,prompts:d.prompts,tools:d.tools.map(m=>({id:m.id,name:m.name,inputPreview:m.inputPreview,startedAt:m.startedAt,endedAt:m.endedAt,ok:m.ok,errorPreview:m.errorPreview,usage:m.usage}))}))},l=JSON.stringify(o,null,2),u=new Blob([l],{type:"application/json"}),c=URL.createObjectURL(u),h=document.createElement("a"),p=(r.label||"session").replace(/[^a-z0-9._-]/gi,"_");h.href=c,h.download=`${en}-${p}-${n.slice(0,8)}.json`,document.body.appendChild(h),h.click(),document.body.removeChild(h),setTimeout(()=>URL.revokeObjectURL(c),1e3)}const Lv={file:"📁",shell:"⚡",web:"🌐",agent:"🤖",task:"📋",plan:"🧭",mcp:"🔌",other:"✨"},$v={file:"file",shell:"shell",web:"web",agent:"agent",task:"task",plan:"plan",mcp:"mcp",other:"other"},HT=kC;function Y5(e,n){if(!n)return null;const r=new Set([n]);let i=n;for(;i;){const l=e.agents.get(i);if(!(l!=null&&l.parentId)||r.has(l.parentId))break;r.add(l.parentId),i=l.parentId}let o=!0;for(;o;){o=!1;for(const l of e.agents.values())l.parentId&&r.has(l.parentId)&&!r.has(l.id)&&(r.add(l.id),o=!0)}return r}function K5(e,n,r,i,o,l,u,c,h,p,d,m,y,w,N,_,b,S,k){const E=[],T=[],I=new Set;if(l)for(const B of e.agents.values())S.has(B.id)&&qT(B,l)&&I.add(B.id);const O=$T(l,I.size);for(const B of e.agents.values()){if(!S.has(B.id))continue;const C=O&&!I.has(B.id),q=B.exitAt!=null,L=b!=null&&!b.has(B.id),re=[C?"rf-dim":"",q?"rf-exiting":"",L?"rf-spotlit-out":""].filter(Boolean).join(" ")||void 0,J=u.get(B.id);if(E.push({id:B.id,type:"agent",position:{x:0,y:0},data:{...B,now:n,onOpenContext:k},className:re,...J?{width:J.width,height:J.height}:null}),B.parentId&&S.has(B.parentId)){const W=ny(B.sessionId),Z=l&&!I.has(B.id)&&!I.has(B.parentId),M=q,G=_.size>0&&(_.has(B.id)||_.has(B.parentId)),K=b!=null&&!b.has(B.id)&&!b.has(B.parentId),$=B.state==="active"?2:1.5,Q=G?$+1.5:$,ne=Z||M?.2:K?.12:1,ae=["sess-edge",B.state==="active"?"sess-live":"sess-idle",M?"rf-edge-exiting":"",G?"rf-edge-selected":""].filter(Boolean).join(" ");T.push({id:`e:${B.parentId}->${B.id}`,source:B.parentId,target:B.id,animated:(B.state==="active"||G)&&!Z&&!M,type:"smoothstep",style:{"--session-hue":W,strokeWidth:Q,opacity:ne,transition:"var(--edge-transition)"},className:ae})}}const A=new Map;for(const B of e.agents.values())B.tools.length>0&&A.set(B.id,Math.min(4,B.tools.length));const U=`${w}#lanes:${J4(A)}`,z=E.filter(B=>c$(B.id,o,m,y));if(z.length>0||U!==N.current){if(z.length>0){const B=t5(E,T,{direction:"LR",pinned:o,measured:u,availableWidth:r,availableHeight:i,lanes:A});for(const C of B)IC(C.id,m,y)&&f$(C.id,C.position,m,y);r5(E,m,o,u,new Set(z.map(C=>C.id)),A)}n5(E,m,o,u,A),N.current=U}const P=s5(E,m,o,u,c,!p||d,A);P.length>0&&h(P),ru(m,e.agents),ru(y,e.agents),ru(o,e.agents),ru(u,u$(e.agents.values()));const D=[];let V=!1;for(const B of E){let C=o.get(B.id)??m.get(B.id);C||(C=d$(B.id,m,y),V=!0),D.push({...B,position:C})}return V&&(N.current=""),{nodes:D,edges:T}}function X5(){return g.jsx(ty,{children:g.jsx(Q5,{})})}function Q5(){var Va,Ga,Ya,Ka;const e=ja(),n=j.useRef(Tv()),[,r]=j.useState(0),i=j.useCallback(()=>r(H=>H+1),[]),[o,l]=j.useState(()=>new Set),[u,c]=j.useState(null),[h,p]=j.useState(null),d=j.useCallback((H,Y)=>{l(oe=>{if(!Y)return new Set([H]);const de=new Set(oe);return de.has(H)?de.delete(H):de.add(H),de}),c(oe=>Y&&oe===H?oe:H)},[]),m=j.useCallback(()=>{l(new Set),c(null)},[]),[y,w]=j.useState(null),[N,_]=j.useState(null),b=j.useCallback(H=>_(H),[]),[S,k]=j.useState(!1),E=j.useRef(U5()),[T,I]=j.useState($5);j.useEffect(()=>{q5(T)},[T]);const[O,A]=j.useState(D5);j.useEffect(()=>{F5(O)},[O]);const[U,z]=j.useState(z5);j.useEffect(()=>{B5(U)},[U]);const[P,D]=j.useState(()=>{try{const H=window.localStorage.getItem(PE);return H===null?!0:H==="1"}catch{return!0}});j.useEffect(()=>{try{window.localStorage.setItem(PE,P?"1":"0")}catch{}},[P]);const[V,B]=j.useState(null),[C,q]=j.useState(!1),[L,re]=j.useState(0),[J,W]=j.useState(0);j.useEffect(()=>{fetch("/api/sound-hook").then(H=>H.ok?H.json():null).then(H=>{H!=null&&H.ok&&(B(H.enabled===!0),re((H.foreign??[]).filter(Y=>Y.worksHere).length),W(typeof H.parked=="number"?H.parked:0))}).catch(()=>{})},[]);const Z=j.useCallback(async()=>{q(!0);try{const Y=await(await fetch("/api/sound-hook",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:!V})})).json().catch(()=>null);Y!=null&&Y.ok&&B(Y.enabled===!0),fetch("/api/sound-hook").then(oe=>oe.ok?oe.json():null).then(oe=>{oe!=null&&oe.ok&&(re((oe.foreign??[]).filter(de=>de.worksHere).length),W(typeof oe.parked=="number"?oe.parked:0))}).catch(()=>{})}catch{}finally{q(!1)}},[V]),[M,G]=j.useState(!1),[K,$]=j.useState(null),[Q,ne]=j.useState(()=>{if(typeof window>"u")return"";try{return window.localStorage.getItem(LE)??""}catch{return""}}),[ae,fe]=j.useState(!1),te=j.useRef(0),[ce,Ce]=j.useState(!1),ve=j.useCallback((H=!1)=>{H&&(te.current=Date.now(),Ce(!0)),fetch(H?"/api/version?refresh=1":"/api/version").then(Y=>Y.ok?Y.json():null).then(Y=>{Y&&$(Y)}).catch(()=>{}).finally(()=>{H&&Ce(!1)})},[]),je=j.useCallback(()=>{ve(Date.now()-te.current>=L5)},[ve]);j.useEffect(()=>{ve();const H=window.setInterval(je,5*6e4),Y=()=>{document.visibilityState==="visible"&&je()};return document.addEventListener("visibilitychange",Y),()=>{window.clearInterval(H),document.removeEventListener("visibilitychange",Y)}},[ve,je]),j.useEffect(()=>{M&&ve()},[M,ve]);const ye=(K==null?void 0:K.notice)??null,[Ee,Ye]=j.useState(null),[we,_e]=j.useState(Ov);j.useEffect(()=>{let H=!1;return fetch("/api/health").then(Y=>Y.ok?Y.json():null).then(Y=>{H||(_e(m5(Y)),!(!Y||typeof Y.workspace!="string")&&Ye(Y.workspace))}).catch(()=>{}),()=>{H=!0}},[M]);const Be=j.useRef(we);Be.current=we;const it=ye?`${ye.kind}:${ye.to}`:"",ft=ye!=null&&Q!==it,Ze=j.useCallback(()=>{if(!ye)return;const H=Q===it?"":it;ne(H);try{window.localStorage.setItem(LE,H)}catch{}},[ye,it,Q]),[De,Je]=j.useState(()=>{if(typeof window>"u")return"";try{return window.localStorage.getItem($E)??""}catch{return""}}),Xe=K!=null&&K.invokedAs&&K.invokedAs!==en?K.invokedAs:null,Mt=Xe!=null&&De!==Xe,lt=j.useCallback(()=>{if(Xe){Je(Xe);try{window.localStorage.setItem($E,Xe)}catch{}}},[Xe]),Qe=((Va=K==null?void 0:K.upgrade)==null?void 0:Va.state)??"idle",Ot=Z3(K==null?void 0:K.upgrade),zt=j.useCallback(async()=>{try{await fetch("/api/upgrade",{method:"POST"})}catch{}ve()},[ve]);j.useEffect(()=>{if(Qe!=="running")return;const H=window.setInterval(ve,3e3);return()=>window.clearInterval(H)},[Qe,ve]);const et=j.useCallback(async()=>{var oe;const H=K==null?void 0:K.command;if(!H)return;let Y=!1;try{Y=await Promise.race([((oe=navigator.clipboard)==null?void 0:oe.writeText(H).then(()=>!0))??Promise.resolve(!1),new Promise(de=>window.setTimeout(()=>de(!1),500))])}catch{Y=!1}if(!Y)try{const de=document.createElement("textarea");de.value=H,de.setAttribute("readonly",""),de.style.cssText="position:fixed;top:0;left:0;opacity:0",document.body.appendChild(de),de.select(),Y=document.execCommand("copy"),de.remove()}catch{Y=!1}Y&&(fe(!0),window.setTimeout(()=>fe(!1),1600))},[K==null?void 0:K.command]),bt=j.useCallback(()=>{I(H=>(H||D(!1),!H))},[]),ht=j.useCallback(()=>{D(H=>(H||I(!1),!H))},[]),[vt,hn]=j.useState(!1),[Gt,Pt]=j.useState(0),bn=j.useRef(null),[gr,Sn]=j.useState(!1),Bt=j.useRef(null),[Fn,pn]=j.useState(0),vr=j.useRef(g$()),[Hr,xi]=j.useState(!1),Wr=j.useCallback(()=>{const H=vr.current,Y=H.setPaused(!H.paused);for(const oe of Y)n.current=Fx(n.current,oe);xi(H.paused)},[]),[ut,_i]=j.useState(Date.now()),[mn,Is]=j.useState(()=>{if(typeof window>"u")return!0;try{return window.localStorage.getItem(DE)!=="0"}catch{return!0}}),yr=j.useCallback(()=>{Is(H=>{const Y=!H;try{window.localStorage.setItem(DE,Y?"1":"0")}catch{}return Y})},[]),[zn,kn]=j.useState(!1),[Vr,Gr]=j.useState("restart"),[Yr,bi]=j.useState(null),sr=j.useRef(!1),Ms=j.useRef(null),En=j.useRef(0),wr=j.useCallback(async H=>{if(sr.current)return;const Y=(H==null?void 0:H.upgrade)===!0;sr.current=!0,Ms.current=Ot;const oe=++En.current;Gr(Y?"npx":"restart"),kn(!0);try{window.sessionStorage.setItem("agent-dag.restartPending",(ye==null?void 0:ye.to)??"")}catch{}try{await fetch("/api/restart",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({upgrade:Y})})}catch{}window.setTimeout(()=>{if(En.current===oe&&sr.current){sr.current=!1,kn(!1);try{window.sessionStorage.removeItem("agent-dag.restartPending")}catch{}}},Y?18e4:3e4)},[ye==null?void 0:ye.to,Ot]),Os=j.useRef(null);j.useEffect(()=>{let H=!1;for(const oe of n.current.agents.values())if(oe.state==="active"){H=!0;break}const Y=e$({enabled:mn,kind:ye==null?void 0:ye.kind,canRestart:(K==null?void 0:K.canRestart)===!0,busy:H,idleSince:Os.current,now:ut});Os.current=Y.idleSince,Y.restart&&wr()},[mn,ye==null?void 0:ye.kind,K==null?void 0:K.canRestart,ut,wr]),j.useEffect(()=>{const H=K==null?void 0:K.running;let Y=null,oe=null;try{Y=window.sessionStorage.getItem("agent-dag.restartPending"),oe=window.sessionStorage.getItem(FE)}catch{return}const de=Q3({bundle:"1.35.20",running:H,pending:Y,lastTried:oe});if(de==="reload"){try{window.sessionStorage.setItem(FE,H??"")}catch{return}window.location.reload();return}if(de!=="confirm")return;try{window.sessionStorage.removeItem("agent-dag.restartPending")}catch{}sr.current=!1,kn(!1),bi(H??null);const xe=window.setTimeout(()=>bi(null),6e3);return()=>window.clearTimeout(xe)},[K==null?void 0:K.running]),j.useEffect(()=>{if(zn&&J3({asked:Ms.current,reported:Ot})){try{window.sessionStorage.removeItem("agent-dag.restartPending")}catch{}sr.current=!1,kn(!1)}},[zn,Ot]);const Ps=j.useRef(H5()).current;j.useRef(!1);const Tt=j.useRef(new Map(Ps.positions.filter(([H])=>Ps.pins.includes(H)))),xr=j.useRef(null),_r=j.useState(()=>W5())[0],[Yt,Kr]=j.useState(""),[Si,nt]=j.useState(()=>new Set),[br,Xr]=j.useState(_$),[Ls,Qr]=j.useState(!1),Zr=j.useRef(null);j.useEffect(()=>{document.documentElement.dataset.theme=br;try{window.localStorage.setItem(OC,br)}catch{}},[br]),j.useEffect(()=>{if(!_r)return;const H=window.setTimeout(()=>{try{e.setViewport(_r,{duration:0})}catch{}},60);return()=>window.clearTimeout(H)},[e,_r]);const $s=j.useRef(!0);j.useEffect(()=>{const H=new EventSource("/events"),Y=m$(i,{now:()=>Date.now(),setTimeout:(oe,de)=>window.setTimeout(oe,de),clearTimeout:oe=>window.clearTimeout(oe)});return H.addEventListener("open",()=>{G(!0),Qr(!0)}),H.addEventListener("error",()=>G(!1)),H.addEventListener("replay-end",()=>{$s.current=!1,Y.flush()}),H.addEventListener("hook",oe=>{try{const de=JSON.parse(oe.data);if(!vr.current.accept(de))return;n.current=Fx(n.current,de),de.replay===!0||$s.current||Date.now()-de.receivedAt>3e4?Y.replay():Y.live()}catch{}}),()=>{H.close(),Y.cancel()}},[i]),j.useEffect(()=>{const H=setInterval(()=>{const Y=Date.now();_i(Y);let oe=mP(n.current,Y,Dx);gP(n.current,Y,Dx)&&(oe=!0),hP(n.current,Y,I5,M5)&&(oe=!0),pP(n.current,Y,O5,P5)&&(oe=!0),oe&&i()},250);return()=>clearInterval(H)},[i]);const ir=j.useRef(null),St=j.useRef(0),qs=420,pt=j.useCallback((H=500)=>{try{const Se=document.querySelector(".canvas-wrap"),Pe=Array.from(document.querySelectorAll(".react-flow__node")).filter(wt=>wt.offsetWidth>0);if(!Se||Pe.length===0)return;const Fe=Se.getBoundingClientRect(),Ge=e.getViewport();if(!Number.isFinite(Ge.zoom)||Ge.zoom<=0)return;const We=wt=>(wt-Fe.left-Ge.x)/Ge.zoom,ze=wt=>(wt-Fe.top-Ge.y)/Ge.zoom,Tn=Pe.map(wt=>wt.getBoundingClientRect()),Ys=Math.min(...Tn.map(wt=>We(wt.left))),Ao=Math.max(...Tn.map(wt=>We(wt.right))),Ks=Math.min(...Tn.map(wt=>ze(wt.top))),Io=Math.max(...Tn.map(wt=>ze(wt.bottom))),Xa=Ao-Ys,us=Io-Ks;if(!(Xa>0&&us>0))return;const Qt=Math.max(.2,Math.min(1,(Fe.width-160)/(Xa+qs)*.86,(Fe.height-160)/us*.86));e.setViewport({x:80-Ys*Qt,y:Math.max(80,(Fe.height-us*Qt)/2)-Ks*Qt,zoom:Qt},{duration:H}),St.current=Date.now(),window.setTimeout(()=>{try{const wt={x:80-Ys*Qt,y:Math.max(80,(Fe.height-us*Qt)/2)-Ks*Qt,zoom:Qt},Mo=e.getViewport();(Math.abs(Mo.zoom-Qt)>.01||Math.abs(Mo.x-wt.x)>2)&&e.setViewport(wt,{duration:0})}catch{}},H+60)}catch{}},[e]),ko=j.useRef(""),Eo=j.useRef(null),qa=j.useRef(0),Sr=j.useCallback(()=>{qa.current=Date.now()},[]),Jr="agent-dag.autoFitDisabled",gn=j.useRef((()=>{if(typeof window>"u")return!1;try{return window.localStorage.getItem(Jr)==="1"}catch{return!1}})()),[Ds,Da]=j.useState(gn.current),Fa=j.useCallback(()=>{if(!gn.current){gn.current=!0,Da(!0);try{window.localStorage.setItem(Jr,"1")}catch{}}},[]),No=j.useCallback(()=>{gn.current=!1,Da(!1);try{window.localStorage.removeItem(Jr)}catch{}pt(400)},[e,pt]);j.useEffect(()=>{const H=setInterval(()=>{if(gn.current||Date.now()-qa.current<800)return;const Y=n.current,oe=Date.now(),de=[];for(const We of Y.agents.values())Mv(We,oe)&&de.push({id:We.id});if(de.length===0)return;const xe=e.getViewport(),Se=window.innerWidth-360,Pe=window.innerHeight-52;let Fe=!1,Ge=!1;for(const{id:We}of de){const ze=or.current.get(We),Tn=Tt.current.get(We)??Kt.current.get(We);if(!ze||!Tn)continue;Ge=!0;const Ys=Tn.x*xe.zoom+xe.x,Ao=Tn.y*xe.zoom+xe.y,Ks=(Tn.x+ze.width)*xe.zoom+xe.x,Io=(Tn.y+ze.height)*xe.zoom+xe.y;if(Ks>0&&Ys<Se&&Io>0&&Ao<Pe){Fe=!0;break}}Ge&&!Fe&&pt(600)},1500);return()=>clearInterval(H)},[e]);const ki=j.useRef(!1),or=j.useRef(new Map),es=j.useRef(0),ec=j.useCallback(H=>{const Y=or.current;let oe=!1;for(const de of H.nodeInternals.values()){const xe=de.width,Se=de.height;if(xe==null||Se==null)continue;const Pe=Y.get(de.id);Pe?(Math.abs(Pe.height-Se)>4||Math.abs(Pe.width-xe)>4)&&(Y.set(de.id,{width:xe,height:Se}),oe=!0):(Y.set(de.id,{width:xe,height:Se}),oe=!0)}return oe&&(es.current+=1),es.current},[]),Ei=Ke(ec),[He,za]=j.useState(0);j.useEffect(()=>{let H=0;const Y=()=>{const de=or.current;let xe=!1;for(const Se of document.querySelectorAll(".react-flow__node[data-id]")){const Pe=Se.getAttribute("data-id");if(!Pe)continue;const Fe=Se.offsetWidth,Ge=Se.offsetHeight;if(!Fe||!Ge)continue;const We=de.get(Pe);(!We||Math.abs(We.width-Fe)>4||Math.abs(We.height-Ge)>4)&&(de.set(Pe,{width:Fe,height:Ge}),xe=!0)}xe&&za(Se=>Se+1)};if(ki.current)return;let oe=0;return document.visibilityState==="hidden"?oe=window.setTimeout(Y,32):H=requestAnimationFrame(Y),()=>{cancelAnimationFrame(H),window.clearTimeout(oe)}});const Kt=j.useRef(new Map(Ps.positions)),Co=j.useRef(new Set),Ni=j.useRef(""),kr=j.useMemo(()=>{const H=[];for(const Y of n.current.agents.values())Mv(Y,ut)&&H.push(Y.id+(Y.parentId?`>${Y.parentId}`:""));return H.sort(),`${H.join("|")}#sv${Ei}.${He}`},[n.current,n.current.lastSeq,ut,Ei,He]),ts=j.useRef(null);j.useEffect(()=>(ts.current!=null&&window.clearTimeout(ts.current),ts.current=window.setTimeout(()=>{dv(Kt.current,Tt.current)},1500),()=>{ts.current!=null&&window.clearTimeout(ts.current)}),[kr]),j.useEffect(()=>{if(ko.current===kr)return;const H=ko.current;if(ko.current=kr,!H||gn.current)return;Date.now()-St.current>1200&&pt(400),ir.current&&window.clearTimeout(ir.current),ir.current=window.setTimeout(()=>{gn.current||pt(500)},280)},[kr,e,pt]);const Fs=j.useMemo(()=>{if(o.size===0)return null;const H=new Set;for(const Y of o){const oe=Y5(n.current,Y);if(oe)for(const de of oe)H.add(de)}return H.size>0?H:null},[n.current,n.current.lastSeq,o]),Nn=j.useMemo(()=>o5(n.current,ut),[n.current,n.current.lastSeq,ut]),Bn=j.useRef(null),[vn,Un]=j.useState({w:0,h:0});j.useEffect(()=>{const H=Bn.current;if(!H||typeof ResizeObserver>"u")return;const Y=new ResizeObserver(oe=>{var xe;const de=(xe=oe[0])==null?void 0:xe.contentRect;de&&Un(Se=>Math.abs(Se.w-de.width)>40||Math.abs(Se.h-de.height)>40?{w:de.width,h:de.height}:Se)});return Y.observe(H),Un({w:H.clientWidth,h:H.clientHeight}),()=>Y.disconnect()},[]);const zs=j.useCallback(H=>{var oe,de;const Y=H.target;!(Y!=null&&Y.closest)||Y.closest(j5)!==H.currentTarget||(H.preventDefault(),(de=(oe=document.activeElement)==null?void 0:oe.blur)==null||de.call(oe))},[]),Bs=j.useRef(new Map),[Hn,tc]=j.useState(!1);j.useEffect(()=>{const H=window.setTimeout(()=>tc(!0),2500);return()=>window.clearTimeout(H)},[]);const[Ba,Er]=j.useState(!1),ar=j.useRef(null),[Ci,Us]=j.useState(!1),Ua=j.useCallback(()=>{ar.current&&(window.clearTimeout(ar.current),ar.current=null),Er(!1)},[]),nc=j.useCallback(H=>{H.length!==0&&queueMicrotask(()=>{Er(!0),ar.current&&window.clearTimeout(ar.current),ar.current=window.setTimeout(()=>Er(!1),A5+80)})},[]);j.useEffect(()=>()=>{ar.current&&window.clearTimeout(ar.current)},[]);const ns=vn.w>0?vn.w*.92:0,rs=vn.h>0?vn.h:0,{nodes:Xt,edges:Ti}=j.useMemo(()=>K5(n.current,ut,ns,rs,Tt.current,Yt,or.current,Bs.current,nc,Hn,Ci,Kt.current,Co.current,kr,Ni,o,Fs,Nn,b),[n.current,n.current.lastSeq,ut,ns,rs,Hn,Ci,Yt,kr,o,Fs,Nn,b,Gt]),Ha=j.useMemo(()=>{const H=new Map;for(const oe of Xt){const de=oe.data;if(!(de!=null&&de.sessionId)||de.exitAt!=null)continue;const xe=oe.width,Se=oe.height;if(xe==null||Se==null)continue;const Pe=oe.position.x,Fe=oe.position.y,Ge=Pe+xe,We=Fe+Se,ze=H.get(de.sessionId);ze?(ze.minX=Math.min(ze.minX,Pe),ze.minY=Math.min(ze.minY,Fe),ze.maxX=Math.max(ze.maxX,Ge),ze.maxY=Math.max(ze.maxY,We)):H.set(de.sessionId,{minX:Pe,minY:Fe,maxX:Ge,maxY:We})}const Y=[];for(const[oe,de]of H){const xe=de.maxX-de.minX+au*2,Se=de.maxY-de.minY+au*2;Y.push({id:`group:${oe}`,type:PT,position:{x:de.minX-au,y:de.minY-au},data:{sessionId:oe,w:xe,h:Se},width:xe,height:Se,style:{width:xe,height:Se},zIndex:-1,draggable:!0,selectable:!1,focusable:!1,deletable:!1,connectable:!1})}return Y},[Xt,ut]),rc=j.useMemo(()=>{const H=[...Ha,...Xt],Y=Bt.current;return!Y||Y.size===0?H:H.map(oe=>{const de=Y.get(oe.id);return de?{...oe,position:de}:oe})},[Ha,Xt,Fn]),Nr=j.useMemo(()=>{if(!Yt)return null;const H=new Set;for(const Y of n.current.agents.values())Nn.has(Y.id)&&qT(Y,Yt)&&H.add(Y.id);return H},[n.current,n.current.lastSeq,Yt,Nn]),Cn=j.useMemo(()=>k5(Yt,(Nr==null?void 0:Nr.size)??0,Nn.size),[Yt,Nr,Nn]),ss=j.useMemo(()=>{const H=new Set;for(const Y of n.current.agents.values())for(const oe of Y.tools)H.add(HT(oe.name));return Object.keys(Lv).filter(Y=>H.has(Y))},[n.current,n.current.lastSeq]);j.useEffect(()=>{if(ss.length<=1){Sn(!1);return}let H=0;const Y=()=>{const oe=bn.current;if(oe&&!document.hidden){const de=oe.getBoundingClientRect(),xe=Array.from(document.querySelectorAll(".react-flow__node, .tool-burst")).map(Pe=>Pe.getBoundingClientRect()),Se=l5(de,xe,8);Sn(Pe=>Pe===Se?Pe:Se)}H=window.setTimeout(Y,300)};return Y(),()=>window.clearTimeout(H)},[ss.length]);const is=j.useMemo(()=>{const H=new Map;for(const oe of n.current.agents.values())for(const de of oe.tools){if(!de.name.startsWith("mcp__"))continue;const xe=de.name.slice(5),Se=xe.indexOf("__"),Pe=Se>0?xe.slice(0,Se):xe;H.set(Pe,(H.get(Pe)??0)+1)}const Y=[];for(const[oe,de]of H){let xe=5381;for(let Se=0;Se<oe.length;Se++)xe=(xe<<5)+xe^oe.charCodeAt(Se);Y.push({server:oe,count:de,hue:Math.abs(xe)%360})}return Y.sort((oe,de)=>de.count-oe.count),Y},[n.current,n.current.lastSeq]),lr=j.useCallback(H=>{nt(Y=>{const oe=new Set(Y);return oe.has(H)?oe.delete(H):oe.add(H),oe})},[]),Rt=u?n.current.agents.get(u):null,os=h?Array.from(n.current.agents.values()).flatMap(H=>H.tools).find(H=>H.id===h)??null:null,To=j.useCallback(async()=>{try{await fetch("/api/clear",{method:"POST"})}catch{}n.current=Tv(),Tt.current.clear(),or.current.clear(),Kt.current.clear(),Ni.current="",BE(),m(),i()},[i,m]),Hs=j.useRef(S);Hs.current=S;const Ws=j.useRef(!1);Ws.current=os!=null||vt||N!=null||y!=null;const Vs=j.useCallback(H=>{const Y=n$(H,{confirmOpen:Hs.current,modalOpen:Ws.current});Y==="confirm"?k(!0):Y==="clear"&&(k(!1),To())},[To]),Lt=j.useCallback(()=>{Tt.current.clear(),Kt.current.clear(),Ni.current="",BE(),i(),window.setTimeout(()=>pt(500),80)},[i,e,pt]),Cr=j.useCallback(()=>pt(500),[pt]),as=j.useRef(Xt);as.current=Xt;const ls=j.useRef(u);ls.current=u;const Ro=j.useCallback(H=>{const Y=as.current,oe=s$(Y.map(Se=>({id:Se.id,x:Se.position.x,y:Se.position.y})),ls.current,H),de=oe?Y.find(Se=>Se.id===oe):void 0;if(!de)return;const xe=OE(document.activeElement);d(de.id,!1),window.setTimeout(()=>{try{e.fitView({padding:.35,duration:350,nodes:[de]})}catch{}St.current=Date.now(),xe&&R5(de.id)},30)},[d,e]),Ri=j.useCallback(H=>{d(H,!1),window.setTimeout(()=>{try{const Y=as.current.find(oe=>oe.id===H);Y&&e.fitView({padding:.3,duration:500,nodes:[Y]}),St.current=Date.now()}catch{}},60)},[d,e]);j.useEffect(()=>{const H=Y=>{var Fe,Ge,We;const oe=Y.target,de={tagName:oe==null?void 0:oe.tagName,isContentEditable:oe==null?void 0:oe.isContentEditable,role:(Fe=oe==null?void 0:oe.getAttribute)==null?void 0:Fe.call(oe,"role"),type:oe==null?void 0:oe.type};if(Y.key==="Escape"){const ze=PP({overlayOpen:Eu.depth()>0,typing:ry(de)});ze==="dismiss"?Eu.dismissTop():ze==="blur"?oe==null||oe.blur():(l$(de)&&(oe==null||oe.blur()),m());return}if(_C(Y))return;const xe=OE(oe)?(Ge=oe==null?void 0:oe.getAttribute)==null?void 0:Ge.call(oe,"data-id"):null,Se=xe&&as.current.some(ze=>ze.id===xe)?xe:null,Pe=o$(Y,Se);if(Pe.kind==="activate"){Y.preventDefault(),d(Pe.nodeId,Pe.additive);return}if(Pe.kind!=="node"&&!(Pe.nodeId==null&&UP(de))){if(Y.key==="/"){Y.preventDefault(),(We=Zr.current)==null||We.focus();return}Y.key===" "&&(Y.preventDefault(),Wr()),(Y.key==="c"||Y.key==="C")&&Vs("shortcut"),(Y.key==="r"||Y.key==="R")&&Lt(),(Y.key==="f"||Y.key==="F")&&Cr(),(Y.key==="l"||Y.key==="L")&&bt(),(Y.key==="h"||Y.key==="H")&&hn(ze=>!ze),(Y.key==="u"||Y.key==="U")&&z(ze=>!ze),(Y.key==="a"||Y.key==="A")&&Be.current.claude&&ht(),(Y.key==="j"||Y.key==="J")&&Ro(1),(Y.key==="k"||Y.key==="K")&&Ro(-1),(Y.key==="t"||Y.key==="T")&&Xr(ze=>ze==="dark"?"light":"dark")}};return window.addEventListener("keydown",H),()=>window.removeEventListener("keydown",H)},[Vs,Lt,Cr,m,d,Ro,Wr]);const Tr=n.current.agents.size,Gs=new Set(Array.from(n.current.agents.values()).map(H=>H.sessionId)).size,tn=j.useMemo(()=>JL(n.current.agents.values()),[n.current,n.current.lastSeq]),jo=j.useMemo(()=>e3(n.current.agents.values()),[n.current,n.current.lastSeq]),Wa=j.useRef(null);j.useEffect(()=>{const H=b$({waiting:tn.length,running:jo}),Y=Wa.current;if(Wa.current=H,(Y==null?void 0:Y.title)!==H.title&&(document.title=H.title),(Y==null?void 0:Y.icon)!==H.icon){const oe=document.querySelector('link[rel="icon"]');oe&&(oe.href=S$[H.icon])}},[tn.length,jo]);const[sc,ic]=j.useState(""),ji=E$(tn);j.useEffect(()=>{ic(H=>N$(H,ji))},[ji]);const nn=j.useMemo(()=>{let H=0,Y=0,oe=0,de=0,xe=0,Se=0,Pe=0,Fe=0,Ge=0;for(const We of n.current.agents.values()){H+=We.usage.inputTokens,Y+=We.usage.outputTokens,oe+=We.usage.cacheReadTokens,de+=We.usage.cacheCreateTokens;const ze=dn(We.usage,We.model);xe+=ze.total,Se+=ze.input,Pe+=ze.output,Fe+=ze.cacheRead,Ge+=ze.cacheWrite}return{inT:H,outT:Y,cacheR:oe,cacheC:de,sum:H+Y,cost:{total:xe,input:Se,output:Pe,cacheRead:Fe,cacheWrite:Ge}}},[n.current,n.current.lastSeq]);return g.jsxs("div",{className:"app",children:[g.jsx("a",{className:"skip-link",href:"#canvas",children:"Skip to the canvas"}),g.jsxs("header",{className:"topbar",children:[g.jsxs("div",{className:"brand",children:[g.jsx("span",{className:"logo"}),g.jsx("h1",{children:en}),ye?g.jsxs("button",{type:"button",className:"v stale",onClick:Ze,"aria-label":h5({...ye,open:ft}),title:ye.kind==="restart"?`Running v${ye.from}; v${ye.to} is installed on disk. Restart to pick it up.`:`Running v${ye.from}; v${ye.to} is on npm.`,children:["v",ye.from,g.jsx("span",{className:"v-dot","aria-hidden":!0})]}):(()=>{const H={running:(K==null?void 0:K.running)??"1.35.20",latest:K==null?void 0:K.latest,latestPending:K==null?void 0:K.latestPending,checkedAgo:K!=null&&K.checkedAt?Pv(ut-K.checkedAt):null,checkDisabled:K==null?void 0:K.checkDisabled,checking:ce};return g.jsxs("button",{type:"button",className:ce?"v checking":"v",onClick:()=>ve(!0),"aria-busy":ce||void 0,"aria-label":f5(H),title:d5(H),children:["v",H.running]})})()]}),Rt&&(()=>{const H=dn(Rt.usage,Rt.model),Y=Math.max(0,((Rt.endedAt??ut)-Rt.startedAt)/1e3),oe=Rt.state==="active"?ku(H.total,Y):null,de=o.size-1;return g.jsxs("button",{type:"button",className:"selected-ribbon",title:`Fit view to ${Rt.label}`,onClick:()=>{try{const xe=Xt.find(Se=>Se.id===Rt.id);xe&&e.fitView({padding:.35,duration:500,nodes:[xe]}),St.current=Date.now()}catch{}},children:[g.jsx("span",{className:`state-pill state-${Rt.state}`,children:Rt.state==="active"?"live":Rt.state}),g.jsx("span",{className:"selected-label",children:Rt.label}),H.total>0&&g.jsxs("span",{className:"selected-cost",children:[qe(H.total),oe?g.jsxs("span",{className:"selected-rate",children:[" · ",oe]}):null]}),de>0&&g.jsxs("span",{className:"selected-extra",children:["+",de]}),g.jsx("span",{"aria-hidden":!0,className:"selected-close",onClick:xe=>{xe.stopPropagation(),m()},children:"×"})]})})(),g.jsxs("div",{className:"actions",children:[g.jsxs("div",{className:"search",children:[g.jsx("span",{className:"search-icon","aria-hidden":!0,children:"⌕"}),g.jsx("input",{ref:Zr,type:"text",placeholder:E5,value:Yt,onChange:H=>Kr(H.target.value),spellCheck:!1,"aria-label":"Filter the graph"}),Yt?g.jsx("button",{className:"search-clear","aria-label":"Clear search",onClick:()=>Kr(""),children:"×"}):g.jsx("kbd",{className:"search-kbd","aria-hidden":!0,children:"/"}),Cn.count&&g.jsx("span",{className:`search-count${Cn.empty?" none":""}`,"aria-live":"polite",title:`${Cn.count} agents on the canvas match “${Yt}”`,children:Cn.count})]}),g.jsxs("span",{className:"status",children:[(()=>{const H=b5({connected:M,paused:Hr,held:vr.current.size});return g.jsx("span",{className:`pill ${H.tone}`,title:H.title,children:H.label})})(),g.jsxs("span",{className:"stat",title:y5(we),children:[g.jsx("span",{className:"count",children:Gs}),g.jsx("span",{className:"lbl",children:"sessions"})]}),g.jsxs("span",{className:"stat",title:"Total agents (root + subagents)",children:[g.jsx("span",{className:"count",children:Tr}),g.jsx("span",{className:"lbl",children:"agents"})]}),g.jsxs("span",{className:"stat",title:w5(we),children:[g.jsx("span",{className:"count",children:n.current.totalEvents}),g.jsx("span",{className:"lbl",children:"events"})]}),nn.sum>0&&g.jsxs("span",{className:"stat",title:`in:${nn.inT.toLocaleString()} out:${nn.outT.toLocaleString()} cache-r:${nn.cacheR.toLocaleString()} cache-c:${nn.cacheC.toLocaleString()}`,children:[g.jsx("span",{className:"count",children:Dt(nn.sum)}),g.jsx("span",{className:"lbl",children:"tokens"})]}),is.length>0&&g.jsxs("span",{className:"stat mcp-legend",title:`MCP servers seen this session:
|
|
73
73
|
${is.map(H=>` ${H.server}: ${H.count} call${H.count===1?"":"s"}`).join(`
|
|
74
74
|
`)}`,children:[is.slice(0,6).map(H=>g.jsx("span",{className:"mcp-dot",style:{"--mcp-hue":H.hue}},H.server)),is.length>6&&g.jsxs("span",{className:"mcp-more",children:["+",is.length-6]}),g.jsx("span",{className:"lbl",children:"mcp"})]}),nn.cost.total>0&&(()=>{let H=0,Y=0;for(const xe of n.current.agents.values()){if(xe.state!=="active")continue;const Se=dn(xe.usage,xe.model);H+=Se.total,Y=Math.max(Y,((xe.endedAt??ut)-xe.startedAt)/1e3)}const oe=Y>0?ku(H,Y):null,de=`input ${qe(nn.cost.input)} + output ${qe(nn.cost.output)} + cache r ${qe(nn.cost.cacheRead)} + cache w ${qe(nn.cost.cacheWrite)}${oe?`
|
|
75
75
|
active burn: ${oe}`:""}`;return g.jsxs("span",{className:"stat",title:de,children:[g.jsx("span",{className:"count",children:qe(nn.cost.total)}),g.jsxs("span",{className:"lbl",children:["cost",oe?` · ${oe}`:""]})]})})()]}),g.jsx("div",{className:"vis-hidden",role:"status","aria-atomic":"true",children:sc}),tn.length>0&&g.jsxs("button",{type:"button",className:"waiting-stat",onClick:()=>Ri(tn[0].id),title:`Blocked waiting for you — click to go to the one that has been stuck longest:
|
package/dist/web/index.html
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
document.documentElement.setAttribute("data-theme", stored === "light" ? "light" : "dark");
|
|
41
41
|
})();
|
|
42
42
|
</script>
|
|
43
|
-
<script type="module" crossorigin src="/assets/index-
|
|
43
|
+
<script type="module" crossorigin src="/assets/index-DITF5bH9.js"></script>
|
|
44
44
|
<link rel="stylesheet" crossorigin href="/assets/index-hLBidJXz.css">
|
|
45
45
|
</head>
|
|
46
46
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-dag",
|
|
3
|
-
"version": "1.35.
|
|
3
|
+
"version": "1.35.20",
|
|
4
4
|
"description": "Live deck of Claude Code and Codex agents — watch tool calls, token spend and every Claude Code subagent on one calm canvas. Also available as npx ccdeck and npx agent-dag.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/server/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// agent-dag server: HTTP ingest + SSE broadcast + static file serving.
|
|
2
2
|
// Single-file pure Node HTTP server, zero deps.
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
|
-
import { readFile, stat, mkdir,
|
|
4
|
+
import { readFile, stat, mkdir, open, truncate, readdir, unlink } from "node:fs/promises";
|
|
5
5
|
import { createReadStream, existsSync, readFileSync, realpathSync } from "node:fs";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { extname, join, resolve, dirname as pdirname } from "node:path";
|
|
@@ -13,7 +13,7 @@ import { claudeConfigDir } from "./claude-dir.mjs";
|
|
|
13
13
|
import { CODEX_HOME, CODEX_SESSIONS_DIR, STOP, walkRolloutDays } from "./codex-dir.mjs";
|
|
14
14
|
import { PRODUCT } from "./brand.mjs";
|
|
15
15
|
import { invokedName, renameNotice } from "./invoked-as.mjs";
|
|
16
|
-
import { codexCwdInWorkspace, writesCodexLog } from "./log-writer.mjs";
|
|
16
|
+
import { appendLogLine, codexCwdInWorkspace, writesCodexLog } from "./log-writer.mjs";
|
|
17
17
|
|
|
18
18
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
19
|
const PKG_ROOT = resolve(__dirname, "..", "..");
|
|
@@ -1804,8 +1804,16 @@ function pushEvent(raw, source, opts = {}) {
|
|
|
1804
1804
|
}
|
|
1805
1805
|
|
|
1806
1806
|
if (persisting) {
|
|
1807
|
-
// Fire-and-forget append. JSONL = newline-delimited JSON
|
|
1808
|
-
|
|
1807
|
+
// Fire-and-forget append. JSONL = newline-delimited JSON, so the whole line
|
|
1808
|
+
// has to reach the file as one write — this used to be `appendFile`, which
|
|
1809
|
+
// splits anything over 512 KiB into separate appends and let another
|
|
1810
|
+
// event land in the middle of a large tool response. See appendLogLine.
|
|
1811
|
+
//
|
|
1812
|
+
// Note this runs AFTER redactDeckToken above, as every path to the log
|
|
1813
|
+
// does: the string being written is the one serialization of the event the
|
|
1814
|
+
// SSE frame also used, and the token was taken out of the payload before
|
|
1815
|
+
// either existed.
|
|
1816
|
+
appendLogLine(persistPath, json + "\n");
|
|
1809
1817
|
// Cheap throttled check (every 30s) — only rotates if file > 50MB.
|
|
1810
1818
|
maybeRotatePersistFile();
|
|
1811
1819
|
}
|
|
@@ -1837,9 +1845,35 @@ function pushEvent(raw, source, opts = {}) {
|
|
|
1837
1845
|
return evt;
|
|
1838
1846
|
}
|
|
1839
1847
|
|
|
1848
|
+
/**
|
|
1849
|
+
* Read the log back into the ring buffer at boot.
|
|
1850
|
+
*
|
|
1851
|
+
* A line that will not parse is skipped rather than thrown on, and that is
|
|
1852
|
+
* deliberate — it is what lets a log damaged by the pre-#446 writer still
|
|
1853
|
+
* replay. Every line before the damage and every line after it is whole, so
|
|
1854
|
+
* stopping at the first bad one would throw away the rest of the session for a
|
|
1855
|
+
* fault that costs one event. Newline framing is what makes the recovery
|
|
1856
|
+
* possible: a torn write leaves the reader resynchronised at the very next
|
|
1857
|
+
* `\n`, with no length prefix to have been lost along with the bytes.
|
|
1858
|
+
*
|
|
1859
|
+
* What changed is that the skip is no longer silent. Before, a deck whose log
|
|
1860
|
+
* had been shredded replayed "mostly" and said nothing anywhere: the largest
|
|
1861
|
+
* tool responses and whatever ordinary event was spliced into them were gone,
|
|
1862
|
+
* with no counter and no line on the terminal. The count below is the only
|
|
1863
|
+
* signal that a log carries damage from a writer that has since been fixed, and
|
|
1864
|
+
* the byte total is what tells the user whether it was one truncated tail from
|
|
1865
|
+
* a kill -9 or a megabyte of shredded tool output.
|
|
1866
|
+
*
|
|
1867
|
+
* Deliberately one line, and deliberately without the path in it: this prints
|
|
1868
|
+
* onto a terminal the deck is about to paint over (see oneLine in term.mjs for
|
|
1869
|
+
* what a multi-line message does there), and the path was already printed at
|
|
1870
|
+
* boot by the caller.
|
|
1871
|
+
*/
|
|
1840
1872
|
async function replayLog(filePath) {
|
|
1841
1873
|
if (!existsSync(filePath)) return 0;
|
|
1842
1874
|
let count = 0;
|
|
1875
|
+
let skipped = 0;
|
|
1876
|
+
let skippedBytes = 0;
|
|
1843
1877
|
const rl = createInterface({ input: createReadStream(filePath, { encoding: "utf8" }) });
|
|
1844
1878
|
for await (const line of rl) {
|
|
1845
1879
|
if (!line) continue;
|
|
@@ -1849,7 +1883,14 @@ async function replayLog(filePath) {
|
|
|
1849
1883
|
pushEvent(evt.payload, evt.source ?? "replay", { receivedAt: evt.receivedAt, replay: true });
|
|
1850
1884
|
count++;
|
|
1851
1885
|
}
|
|
1852
|
-
} catch {
|
|
1886
|
+
} catch {
|
|
1887
|
+
skipped++;
|
|
1888
|
+
skippedBytes += Buffer.byteLength(line, "utf8");
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
if (skipped > 0) {
|
|
1892
|
+
const kb = (skippedBytes / 1024).toFixed(0);
|
|
1893
|
+
console.warn(`${PRODUCT}: skipped ${skipped} unreadable line(s) (${kb}KB) while replaying the event log`);
|
|
1853
1894
|
}
|
|
1854
1895
|
return count;
|
|
1855
1896
|
}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
// by the host CLI, with no path back to the module it came from — the same
|
|
21
21
|
// reason it re-derives the Claude config dir inline. The two copies are pinned
|
|
22
22
|
// equal by a test, as challengeProof's pair already is.
|
|
23
|
+
import { open } from "node:fs/promises";
|
|
23
24
|
import { win32, posix } from "node:path";
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -148,3 +149,135 @@ export function writesCodexLog({ decks, pid, cwd, platform = process.platform })
|
|
|
148
149
|
}
|
|
149
150
|
return electWriters(group, platform).has(self);
|
|
150
151
|
}
|
|
152
|
+
|
|
153
|
+
// ─── Appending one whole line ─────────────────────────────────────────────
|
|
154
|
+
//
|
|
155
|
+
// The election above decides WHICH deck writes a line. This decides HOW, and
|
|
156
|
+
// the two are answers to the same question: the log is one file that several
|
|
157
|
+
// processes append to, so a line that arrives in pieces is a line another
|
|
158
|
+
// writer can land inside.
|
|
159
|
+
//
|
|
160
|
+
// What was wrong. events.jsonl was appended with `fsPromises.appendFile`, which
|
|
161
|
+
// is not one write(2). Node's writeFileHandle loops over the payload in chunks
|
|
162
|
+
// of kWriteFileMaxChunkSize — 512 KiB — awaiting each one, so any line longer
|
|
163
|
+
// than 524288 bytes became two or more separate appends with the event loop
|
|
164
|
+
// free in between. Measured here, on Node 26 / macOS 15 / APFS, by watching the
|
|
165
|
+
// file grow while one 5 MiB appendFile was in flight: it arrived in exactly ten
|
|
166
|
+
// steps of 524288 bytes. A single handle.write of the same payload arrived in
|
|
167
|
+
// one step of 5242880.
|
|
168
|
+
//
|
|
169
|
+
// That is reachable in ordinary use. `POST /api/event` accepts a body up to
|
|
170
|
+
// 5,000,000 characters, and a PostToolUse carrying a large Read or Bash
|
|
171
|
+
// response is routinely a good fraction of that. The event that gets spliced
|
|
172
|
+
// into the middle is nearly always this deck's own: pushEvent kicks off
|
|
173
|
+
// maybeResolveUsage / maybeResolveModel / maybeResolveContext on the same call,
|
|
174
|
+
// and each of those pushes its own event a moment later. Reproduced at 8/8 runs
|
|
175
|
+
// with one 1 MiB line and eight small ones issued in one tick, and 4/4 runs
|
|
176
|
+
// across two processes appending 3 MiB lines to one file.
|
|
177
|
+
//
|
|
178
|
+
// The damage is silent and doubled: the oversized event is torn into two
|
|
179
|
+
// unparseable halves AND the small event that landed between them is swallowed
|
|
180
|
+
// inside one of those halves, so a boot replay loses both.
|
|
181
|
+
//
|
|
182
|
+
// What this does instead: exactly one write(2) per line, on a descriptor opened
|
|
183
|
+
// O_APPEND. That is the primitive the atomicity rests on, so it is worth
|
|
184
|
+
// stating exactly what each platform promises.
|
|
185
|
+
//
|
|
186
|
+
// Linux — a write to a regular file holds the inode's i_rwsem for the whole
|
|
187
|
+
// call, so one write(2) is atomic against every other writer regardless of
|
|
188
|
+
// size, up to MAX_RW_COUNT (~2 GiB) after which the call returns short.
|
|
189
|
+
// O_APPEND additionally makes the seek-to-end and the write one step, which
|
|
190
|
+
// is what stops two processes overwriting each other's tail.
|
|
191
|
+
//
|
|
192
|
+
// macOS — measured, not assumed, because the guarantee is not written down as
|
|
193
|
+
// plainly: single writes of 1 MiB and 5 MiB in-process, and 3 MiB from two
|
|
194
|
+
// processes at once, produced no torn line in any run, while appendFile of
|
|
195
|
+
// the same payloads tore in every run.
|
|
196
|
+
//
|
|
197
|
+
// Windows — libuv issues the append as a WriteFile at the documented
|
|
198
|
+
// end-of-file offset on a handle opened FILE_APPEND_DATA, which NTFS serialises
|
|
199
|
+
// per call. This is the platform where the Codex rollout watcher is the only
|
|
200
|
+
// capture path there is, so it is also the platform with the most decks
|
|
201
|
+
// sharing one log.
|
|
202
|
+
//
|
|
203
|
+
// Where the guarantee does NOT hold: any filesystem that can return a short
|
|
204
|
+
// write — NFS and some network/FUSE mounts under memory pressure, and any
|
|
205
|
+
// platform once the payload passes its per-call ceiling. The loop below then
|
|
206
|
+
// issues a second write for the remainder, and another writer can land between
|
|
207
|
+
// them. Nothing available in userland fixes that, so the answer is the reader:
|
|
208
|
+
// replayLog skips a line it cannot parse, counts it, and says so, rather than
|
|
209
|
+
// stopping at it. A torn line costs one event, not the rest of the file.
|
|
210
|
+
//
|
|
211
|
+
// Why not cap or drop oversized events instead. The 5 MB ceiling is already the
|
|
212
|
+
// cap, and it is enforced where it belongs — at ingest, with a 413 the poster
|
|
213
|
+
// can see. Refusing to persist an event the deck is happily drawing would make
|
|
214
|
+
// the canvas and its own replay disagree, which is the bug this fixes wearing a
|
|
215
|
+
// different hat.
|
|
216
|
+
//
|
|
217
|
+
// Why the file is opened per line rather than kept open. maybeRotatePersistFile
|
|
218
|
+
// renames events.jsonl to events.jsonl.1 at 50 MB and `/api/clear` truncates it
|
|
219
|
+
// to zero; a descriptor held across either would go on filling the file nobody
|
|
220
|
+
// reads any more. Opening per line is also exactly the syscall count appendFile
|
|
221
|
+
// already paid — open, write, close — minus the extra writes.
|
|
222
|
+
const appendTails = new Map();
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Append one already-serialized line to the shared log, whole.
|
|
226
|
+
*
|
|
227
|
+
* Appends are serialized per file behind a promise chain. That is not what
|
|
228
|
+
* makes them atomic — the single write(2) below is — but it keeps the order the
|
|
229
|
+
* lines land in equal to the order pushEvent produced them, which is the order
|
|
230
|
+
* a replay reads them back in, and it keeps this process to one open descriptor
|
|
231
|
+
* on the log no matter how many events arrive in one tick.
|
|
232
|
+
*
|
|
233
|
+
* Never rejects. This is called fire-and-forget from the hottest path in the
|
|
234
|
+
* process, and a rejection nobody awaits is a dead deck; a line that could not
|
|
235
|
+
* be written is a line lost, which the caller could not have done anything
|
|
236
|
+
* about anyway. The chain deliberately continues past a failure — one ENOSPC
|
|
237
|
+
* must not stop every later event from being recorded once space is back.
|
|
238
|
+
*
|
|
239
|
+
* @param {string} filePath absolute path to the log
|
|
240
|
+
* @param {string} line the line to append, INCLUDING its trailing newline
|
|
241
|
+
*/
|
|
242
|
+
export function appendLogLine(filePath, line) {
|
|
243
|
+
const tail = (appendTails.get(filePath) ?? Promise.resolve())
|
|
244
|
+
.then(() => writeWholeLine(filePath, line))
|
|
245
|
+
.catch(() => {});
|
|
246
|
+
appendTails.set(filePath, tail);
|
|
247
|
+
// Drop the chain once it drains, so a process that writes to several logs
|
|
248
|
+
// over its life does not hold a promise per path it has finished with. Only
|
|
249
|
+
// the tail we just installed is cleared: if another append chained on in the
|
|
250
|
+
// meantime the map already points at that one, and deleting it would let the
|
|
251
|
+
// next line race the one still in flight.
|
|
252
|
+
tail.then(() => { if (appendTails.get(filePath) === tail) appendTails.delete(filePath); });
|
|
253
|
+
return tail;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* One line, one write(2). The loop exists only for the short-write case
|
|
258
|
+
* described above; on every filesystem that does not do that it runs once.
|
|
259
|
+
*
|
|
260
|
+
* `position` is left null on purpose. That is what makes libuv issue write(2)
|
|
261
|
+
* rather than pwrite(2), and pwrite is the version that does not honour
|
|
262
|
+
* O_APPEND on every platform — it would write at an offset computed before the
|
|
263
|
+
* other writer moved the end of the file.
|
|
264
|
+
*/
|
|
265
|
+
async function writeWholeLine(filePath, line) {
|
|
266
|
+
// Encoded up front so the short-write loop can count bytes rather than UTF-16
|
|
267
|
+
// units. A multi-byte character split across two chunks would otherwise be
|
|
268
|
+
// resumed mid-sequence and the line would be mojibake even without a race.
|
|
269
|
+
const buf = Buffer.from(line, "utf8");
|
|
270
|
+
const handle = await open(filePath, "a");
|
|
271
|
+
try {
|
|
272
|
+
let written = 0;
|
|
273
|
+
while (written < buf.byteLength) {
|
|
274
|
+
const { bytesWritten } = await handle.write(buf, written, buf.byteLength - written, null);
|
|
275
|
+
// A write that reports no progress would spin this loop forever inside a
|
|
276
|
+
// promise nobody is watching. Give up on the line instead.
|
|
277
|
+
if (bytesWritten <= 0) throw new Error(`append made no progress at ${written}/${buf.byteLength} bytes`);
|
|
278
|
+
written += bytesWritten;
|
|
279
|
+
}
|
|
280
|
+
} finally {
|
|
281
|
+
await handle.close();
|
|
282
|
+
}
|
|
283
|
+
}
|