arcane-agents 0.1.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,6 +7,12 @@ Arcane Agents is a local-first visual control room for terminal-backed AI agents
7
7
  Each agent appears as a character on a 2D map, and selecting one opens its live terminal in the right panel.
8
8
  Common setups use Claude Code or OpenCode runtimes, but any terminal-accessible runtime can work.
9
9
 
10
+ <p align="center">
11
+ <video src="https://github.com/user-attachments/assets/b85ee107-17f4-4a78-b546-71951adeabd3" controls width="720"></video>
12
+ </p>
13
+
14
+ > [Watch the demo on YouTube →](https://youtu.be/vOUcloQTCoQ)
15
+
10
16
  ## What It Does
11
17
 
12
18
  - Manages agents as tmux windows.
@@ -77,4 +77,4 @@ WARNING: This link could potentially be dangerous`)){let s=window.open();if(s){t
77
77
  `,t)}paste(e){this._core.paste(e)}refresh(e,t){this._verifyIntegers(e,t),this._core.refresh(e,t)}reset(){this._core.reset()}clearTextureAtlas(){this._core.clearTextureAtlas()}loadAddon(e){this._addonManager.loadAddon(this,e)}static get strings(){return{get promptLabel(){return ih.get()},set promptLabel(e){ih.set(e)},get tooMuchOutput(){return sh.get()},set tooMuchOutput(e){sh.set(e)}}}_verifyIntegers(...e){for(ei of e)if(ei===1/0||isNaN(ei)||ei%1!==0)throw new Error("This API only accepts integers")}_verifyPositiveIntegers(...e){for(ei of e)if(ei&&(ei===1/0||isNaN(ei)||ei%1!==0||ei<0))throw new Error("This API only accepts positive integers")}};const Nw=`
78
78
  `,Iw={background:"#1a1b26",foreground:"#c0caf5",cursor:"#c0caf5",cursorAccent:"#1a1b26",selectionBackground:"rgba(122, 162, 247, 0.28)",selectionInactiveBackground:"rgba(122, 162, 247, 0.2)",black:"#15161e",red:"#f7768e",green:"#9ece6a",yellow:"#e0af68",blue:"#7aa2f7",magenta:"#bb9af7",cyan:"#7dcfff",white:"#a9b1d6",brightBlack:"#414868",brightRed:"#f7768e",brightGreen:"#9ece6a",brightYellow:"#e0af68",brightBlue:"#7aa2f7",brightMagenta:"#bb9af7",brightCyan:"#7dcfff",brightWhite:"#c0caf5"};function Aw({workerId:e,workerName:t,focusRequestKey:s}){const n=x.useRef(null),l=x.useRef(null),c=x.useRef(null),h=x.useRef(null),f=x.useRef(null),p=x.useRef(void 0),d=x.useCallback(()=>{l.current?.focus()},[]),g=x.useCallback(()=>{const S=h.current,v=l.current;!S||!v||S.readyState===WebSocket.OPEN&&S.send(JSON.stringify({type:"resize",cols:v.cols,rows:v.rows}))},[]),y=x.useCallback(()=>{const S=n.current,v=c.current,w=l.current;if(!S||!v||!w)return!1;const E=S.clientWidth,L=S.clientHeight;if(E<8||L<8||S.getClientRects().length===0)return!1;try{v.fit()}catch{return!1}return w.cols>0&&w.rows>0},[]),C=x.useCallback((S=16)=>{f.current&&cancelAnimationFrame(f.current);const v=w=>{if(y()){g();return}w<=0||(f.current=requestAnimationFrame(()=>{v(w-1)}))};f.current=requestAnimationFrame(()=>{v(S)})},[y,g]);return x.useEffect(()=>{if(!n.current)return;const S=new Bw({cursorBlink:!1,cursorStyle:"block",cursorInactiveStyle:"block",fontSize:13,lineHeight:1,theme:Iw}),v=new b0;S.loadAddon(v),S.attachCustomKeyEventHandler(E=>{if(!Ow(E))return!0;E.preventDefault(),E.stopPropagation();const L=h.current;return L?.readyState===WebSocket.OPEN&&L.send(Nw),!1}),S.open(n.current),S.writeln("Select an agent to connect its terminal."),l.current=S,c.current=v,C();const w=new ResizeObserver(()=>{C()});return w.observe(n.current),()=>{w.disconnect(),f.current&&(cancelAnimationFrame(f.current),f.current=null),h.current?.close(),S.dispose(),l.current=null,c.current=null}},[C]),x.useEffect(()=>{const S=l.current;if(!S)return;if(h.current?.close(),h.current=null,S.clear(),C(),!e){S.writeln("Select an agent to connect its terminal.");return}S.writeln(`Connecting to ${t??e}...`);const v=window.location.protocol==="https:"?"wss":"ws",w=new WebSocket(`${v}://${window.location.host}/api/terminal/${e}`);h.current=w;const E=S.onData(L=>{w.readyState===WebSocket.OPEN&&w.send(L)});return w.addEventListener("open",()=>{g(),C(24)}),w.addEventListener("message",L=>{typeof L.data=="string"?S.write(L.data):L.data instanceof Blob&&L.data.text().then(j=>{S.write(j)})}),w.addEventListener("close",()=>{S.writeln(`\r
79
79
  [terminal disconnected]`)}),w.addEventListener("error",()=>{S.writeln(`\r
80
- [terminal connection error]`)}),()=>{E.dispose(),w.close(),h.current===w&&(h.current=null)}},[C,g,e,t]),x.useEffect(()=>{if(!e||s===void 0||p.current===s)return;p.current=s,d();const S=setTimeout(()=>{d()},0);return()=>{clearTimeout(S)}},[s,d,e]),T.jsx("div",{className:"terminal-panel",ref:n})}function Ow(e){return e.type==="keydown"&&e.key==="Enter"&&e.shiftKey&&!e.ctrlKey&&!e.metaKey&&!e.altKey}function zw({activeWorkers:e,selectedWorkers:t,terminalWorker:s,terminalFocused:n,selectedGroupActiveIndex:l,setSelectedGroupActiveIndex:c,setFocusedSelectedWorkerId:h,rallyCommandInputRef:f,rallyCommandDraft:p,rallyCommandSending:d,rallyCommandResultText:g,onRallyCommandDraftChange:y,onSendRallyCommand:C,rosterEntries:S,completionPendingWorkerIds:v,rosterActiveIndex:w,setRosterActiveIndex:E,onActivateRosterIndex:L,onOpenSelectedInTerminal:j,terminalFocusToken:$}){const W=x.useMemo(()=>new Set(v),[v]),Q=v.length;return T.jsxs("div",{className:`terminal-column${s?" terminal-column-selected":""}${s&&n?" terminal-column-focused":""}`,children:[T.jsxs("div",{className:"terminal-header",children:[T.jsx("div",{className:"terminal-header-title",children:t.length>1&&!s?`${t.length} selected agents`:s?`${s.displayName??s.name} (${s.status})`:`Agents (${e.length})`}),!s&&Q>0?T.jsxs("div",{className:"terminal-ready-chip",title:"Agents finished but not yet reviewed in terminal",children:["✦ ",Q," ready"]}):null,s?T.jsx("button",{className:"terminal-open-external",onClick:()=>{j()},disabled:s.status==="stopped",title:"Open in external terminal",type:"button",children:"↗"}):null]}),t.length>1&&!s?T.jsxs("div",{className:"worker-roster",children:[T.jsx("div",{className:"worker-roster-section-label",children:"Selected Group"}),t.map((K,Z)=>T.jsx("button",{className:`worker-roster-item ${Z===l?"active":""}`,onMouseEnter:()=>c(Z),onClick:()=>{c(Z),h(K.id)},type:"button",children:T.jsxs("div",{className:"worker-roster-main",children:[T.jsx("img",{className:"worker-roster-avatar",src:`/api/assets/characters/${encodeURIComponent(K.avatarType)}/rotations/south.png`,alt:"",loading:"lazy","aria-hidden":"true"}),T.jsxs("div",{className:"worker-roster-text",children:[T.jsxs("div",{className:"worker-roster-name-row",children:[T.jsx("div",{className:"worker-roster-name",children:K.displayName??K.name}),W.has(K.id)?T.jsx("span",{className:"worker-complete-badge",title:"Finished and waiting for review",children:"READY"}):null]}),T.jsxs("div",{className:"worker-roster-meta",children:[K.projectId," · ",K.runtimeId," · ",K.status]}),K.activityText?T.jsx("div",{className:"worker-roster-activity",children:K.activityText}):null]})]})},K.id)),T.jsxs("form",{className:"rally-command-card",onSubmit:K=>{K.preventDefault(),C()},children:[T.jsxs("div",{className:"rally-command-header",children:[T.jsx("div",{className:"rally-command-title",children:"Rally Command"}),T.jsxs("div",{className:"rally-command-count",children:[t.length," agents"]})]}),T.jsx("textarea",{ref:f,className:"input rally-command-input",value:p,onChange:K=>{y(K.target.value)},onKeyDown:K=>{K.key==="Enter"&&!K.shiftKey&&!K.ctrlKey&&!K.metaKey&&!K.altKey&&(K.preventDefault(),C())},placeholder:"Type once, send to all selected agents (use $NAME for per-agent names)...",disabled:d,rows:3}),T.jsxs("div",{className:"rally-command-actions",children:[T.jsx("div",{className:"rally-command-hint",children:"Enter sends, Shift+Enter adds a new line, $NAME inserts each agent's name"}),T.jsx("button",{className:"bar-btn",type:"submit",disabled:d||p.length===0,children:d?"Sending...":`Send to ${t.length}`})]}),g?T.jsx("div",{className:"rally-command-result",children:g}):null]})]}):s?T.jsx(Aw,{workerId:s.id,workerName:s.displayName??s.name,focusRequestKey:$}):T.jsx("div",{className:"worker-roster",children:S.length===0?T.jsx("div",{className:"worker-roster-empty",children:"No active agents yet. Summon one from the bottom bar."}):S.map((K,Z)=>T.jsxs("div",{children:[K.kind==="shortcut"&&(Z===0||S[Z-1]?.kind!=="shortcut")?T.jsx("div",{className:"worker-roster-section-label",children:"Summon"}):null,K.kind==="worker"?T.jsx("button",{className:`worker-roster-item ${Z===w?"active":""}`,onMouseEnter:()=>E(Z),onClick:()=>L(Z),type:"button",children:T.jsxs("div",{className:"worker-roster-main",children:[T.jsx("img",{className:"worker-roster-avatar",src:`/api/assets/characters/${encodeURIComponent(K.worker.avatarType)}/rotations/south.png`,alt:"",loading:"lazy","aria-hidden":"true"}),T.jsxs("div",{className:"worker-roster-text",children:[T.jsxs("div",{className:"worker-roster-name-row",children:[T.jsx("div",{className:"worker-roster-name",children:K.worker.displayName??K.worker.name}),W.has(K.worker.id)?T.jsx("span",{className:"worker-complete-badge",title:"Finished and waiting for review",children:"READY"}):null]}),T.jsxs("div",{className:"worker-roster-meta",children:[K.worker.projectId," · ",K.worker.runtimeId," · ",K.worker.status]}),K.worker.activityText?T.jsx("div",{className:"worker-roster-activity",children:K.worker.activityText}):null]})]})}):T.jsx("button",{className:`worker-roster-item worker-roster-item-summon ${Z===w?"active":""}`,onMouseEnter:()=>E(Z),onClick:()=>L(Z),type:"button",children:T.jsxs("div",{className:"worker-roster-main",children:[K.shortcut.avatar?T.jsx("img",{className:"worker-roster-avatar worker-roster-summon-avatar",src:`/api/assets/characters/${encodeURIComponent(K.shortcut.avatar)}/rotations/south.png`,alt:"",loading:"lazy","aria-hidden":"true"}):T.jsx("div",{className:"worker-roster-summon-glyph","aria-hidden":"true",children:"+"}),T.jsxs("div",{className:"worker-roster-text",children:[T.jsx("div",{className:"worker-roster-name",children:K.shortcut.label}),T.jsxs("div",{className:"worker-roster-meta",children:[K.shortcut.project," · ",K.shortcut.runtime]}),T.jsx("div",{className:"worker-roster-activity",children:tv(K.shortcut.hotkeys)})]})]})})]},K.kind==="worker"?K.worker.id:`shortcut-${K.shortcutIndex}-${K.shortcut.label}`))})]})}function Ww(e,t){if(t.killConfirmWorkerIds.length>0)return Il(e)?(e.preventDefault(),t.confirmKillSelection(),!0):(e.preventDefault(),t.closeKillConfirm(),!0);if(!t.isEditableTarget(e.target)&&!t.isTerminalTarget(e.target)){const s=t.findMatchingShortcutIndexes(t.shortcutHotkeyBindings,e);if(s.length>0){e.preventDefault();for(const n of s)t.runSpawn({shortcutIndex:n})}}if(e.key==="Escape")return t.renameModalOpen?(e.preventDefault(),t.closeRenameModal(),!0):t.shortcutsOverlayOpen?(e.preventDefault(),t.setShortcutsOverlayOpen(!1),!0):t.paletteOpen||t.spawnDialogOpen?(e.preventDefault(),t.setPaletteOpen(!1),t.setSpawnDialogOpen(!1),!0):(t.isTerminalTarget(e.target)||t.selectedWorkerId&&(e.preventDefault(),t.applySelection([])),!0);if(t.isTerminalEscapeShortcut(e)){if(!t.renameModalOpen&&!t.shortcutsOverlayOpen&&!t.paletteOpen&&!t.spawnDialogOpen&&t.selectedWorkers.length>1&&t.focusedSelectedWorkerId){const n=t.escapeTerminalFocus();return e.preventDefault(),n&&e.stopPropagation(),t.setFocusedSelectedWorkerId(void 0),!0}if(t.escapeTerminalFocus())return e.preventDefault(),e.stopPropagation(),!0;if(!t.renameModalOpen&&!t.shortcutsOverlayOpen&&!t.paletteOpen&&!t.spawnDialogOpen&&t.selectedWorkerIds.length>0){if(e.preventDefault(),t.selectedWorkerId){const n=t.rosterEntries.findIndex(l=>l.kind==="worker"&&l.worker.id===t.selectedWorkerId);n>=0&&t.setRosterActiveIndex(n)}t.applySelection([])}return!0}return e.key==="?"&&!e.ctrlKey&&!e.metaKey&&!e.altKey?((!t.isEditableTarget(e.target)||t.shortcutsOverlayOpen)&&(e.preventDefault(),t.setShortcutsOverlayOpen(s=>!s)),!0):(e.code==="BracketLeft"||e.code==="BracketRight"||e.key==="="&&!e.shiftKey)&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&!t.isEditableTarget(e.target)&&!t.isTerminalTarget(e.target)?(e.preventDefault(),e.code==="BracketLeft"?t.nudgeMapColumnRatio(e.shiftKey?-1:-t.mapColumnRatioStep):e.code==="BracketRight"?t.nudgeMapColumnRatio(e.shiftKey?1:t.mapColumnRatioStep):t.resetMapColumnRatio(),!0):!1}function Fw(e,t){if(e.key==="Tab"&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&!t.isEditableTarget(e.target))return t.isTerminalTarget(e.target)?!0:(e.preventDefault(),t.selectedWorkers.length>1?(t.cycleSelectedGroupFocus(e.shiftKey?-1:1),!0):(t.cycleSelection(e.shiftKey?-1:1),!0));if(e.code==="Period"&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&!t.isEditableTarget(e.target))return t.isTerminalTarget(e.target)||(e.preventDefault(),t.cycleIdleSelection(e.shiftKey?-1:1)),!0;if(e.code==="Comma"&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&!t.isEditableTarget(e.target))return t.isTerminalTarget(e.target)||(e.preventDefault(),t.cycleIdleSelection(-1)),!0;const s=t.parseControlGroupDigit(e);if(s===void 0)return!1;if((e.ctrlKey||e.metaKey)&&!e.altKey&&t.selectedWorkerIds.length>0)return e.preventDefault(),t.setControlGroups(n=>{const l=new Set(t.selectedWorkerIds),c=n[s]??[],h=new Set(c);if(c.length===t.selectedWorkerIds.length&&t.selectedWorkerIds.every(d=>h.has(d))){const d={...n};return delete d[s],d}const p={...n};for(const[d,g]of Object.entries(p)){const y=Number(d);!Number.isInteger(y)||y<0||y>9||y===s||!Array.isArray(g)||(p[y]=g.filter(C=>!l.has(C)))}return p[s]=[...t.selectedWorkerIds],p}),!0;if(!e.ctrlKey&&!e.metaKey&&!e.altKey&&!e.shiftKey&&!t.isEditableTarget(e.target)){const n=t.controlGroupByDigitRef.current[s]??[];if(n.length===0)return!0;const l=new Set(t.activeWorkers.map(h=>h.id)),c=n.filter(h=>l.has(h));if(c.length===0)return t.setControlGroups(h=>{if(!(s in h))return h;const f={...h};return delete f[s],f}),!0;e.preventDefault(),t.applySelection(c,{center:c.length===1})}return!0}function Hw(e,t){if(t.isEditableTarget(e.target))return!0;const s=e.key.toLowerCase();if((s==="k"&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&(t.inSelectedGroupView?e.shiftKey:!e.shiftKey)||e.key==="Delete")&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&t.selectedWorkerIds.length>0)return e.preventDefault(),t.onKillSelected(),!0;if(t.selectedWorkers.length>1&&!t.isTerminalTarget(e.target)){if(t.inSelectedGroupView&&s==="c"&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&!e.shiftKey)return t.focusRallyCommandInput()&&e.preventDefault(),!0;if((s==="j"||s==="k")&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&!e.shiftKey){e.preventDefault();const l=s==="j"?1:-1,c=t.clampNumber(t.selectedGroupActiveIndex+l,0,t.selectedWorkers.length-1),h=t.selectedWorkers[c];return t.setSelectedGroupActiveIndex(c),t.focusedSelectedWorkerId&&h&&t.setFocusedSelectedWorkerId(h.id),!0}if(Il(e)){const l=t.selectedWorkers.find(c=>c.id===t.focusedSelectedWorkerId)??t.selectedWorkers[t.selectedGroupActiveIndex]??t.selectedWorkers[0];return l&&(e.preventDefault(),t.setFocusedSelectedWorkerId(l.id),t.requestTerminalFocus()),!0}}if(t.selectedWorkerIds.length===0&&t.rosterEntries.length>0&&!t.isTerminalTarget(e.target)){if(s==="k"&&e.shiftKey&&!e.ctrlKey&&!e.metaKey&&!e.altKey)return e.preventDefault(),t.onKillRosterActive(),!0;if(s==="n"&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&!e.shiftKey&&t.firstSummonEntryIndex!==void 0)return e.preventDefault(),t.setRosterActiveIndex(t.firstSummonEntryIndex),!0;if((s==="j"||s==="k")&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&!e.shiftKey)return e.preventDefault(),t.setRosterActiveIndex(l=>{const c=s==="j"?1:-1;return t.clampNumber(l+c,0,t.rosterEntries.length-1)}),!0;if(Il(e))return e.preventDefault(),t.onActivateRosterIndex(t.rosterActiveIndex),!0}if(s==="r"&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&t.selectedWorkers.length>0){e.preventDefault();const l=t.selectedWorkers.length>1?t.selectedWorkers.find(c=>c.id===t.focusedSelectedWorkerId):void 0;return t.openRenameForWorkers(l?[l]:t.selectedWorkers),!0}return s==="m"&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&t.selectedWorkers.length>0?(e.preventDefault(),t.onToggleMovementModeSelected(),!0):Il(e)&&t.selectedWorkerId?(e.preventDefault(),t.requestTerminalFocus(),!0):e.key!=="/"||e.metaKey||e.ctrlKey||e.altKey?!1:(e.preventDefault(),t.setPaletteOpen(!0),t.setSpawnDialogOpen(!1),t.setShortcutsOverlayOpen(!1),!0)}function Il(e){return e.key==="Enter"&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&!e.shiftKey}function jw(e){const t=x.useRef(e);t.current=e,x.useEffect(()=>{const s=n=>{const l=t.current;$w(n,l)||Ww(n,l)||Fw(n,l)||Hw(n,l)};return window.addEventListener("keydown",s,!0),()=>window.removeEventListener("keydown",s,!0)},[])}function $w(e,t){return!(!t.isTerminalTarget(e.target)||t.killConfirmWorkerIds.length>0||t.renameModalOpen||t.shortcutsOverlayOpen||t.paletteOpen||t.spawnDialogOpen||e.key==="Escape"||t.isTerminalEscapeShortcut(e))}async function wi(e,t){const s=await fetch(e,{headers:{"Content-Type":"application/json",...t?.headers??{}},...t});if(!s.ok){const n=await s.json().catch(()=>({error:s.statusText}));throw new Error(n.error??`Request failed: ${s.status}`)}if(s.status!==204)return await s.json()}function Kw(){return wi("/api/config")}async function Q_(){return(await wi("/api/workers")).workers}function Uw(e){return wi("/api/workers/spawn",{method:"POST",body:JSON.stringify(e)})}function Vw(e){return wi(`/api/workers/${e}/stop`,{method:"POST"})}function Yw(e,t,s){return wi(`/api/workers/${e}/position`,{method:"PATCH",body:JSON.stringify({x:t,y:s})})}function J_(e,t){return wi(`/api/workers/${e}/rename`,{method:"PATCH",body:JSON.stringify({displayName:t})})}function qw(e,t){return wi(`/api/workers/${e}/movement-mode`,{method:"PATCH",body:JSON.stringify({movementMode:t})})}function Xw(e){return wi(`/api/workers/${e}/open-terminal`,{method:"POST"})}function Z_(e,t,s=!0){return wi("/api/workers/broadcast-input",{method:"POST",body:JSON.stringify({workerIds:e,text:t,submit:s})})}const Gw=3e4;function Qw(e){const[t,s]=x.useState(null),[n,l]=x.useState([]),[c,h]=x.useState(!1);return x.useEffect(()=>{Promise.all([Kw(),Q_()]).then(([f,p])=>{s(f),l(p),h(!0)}).catch(f=>{const p=f instanceof Error?f.message:"Failed to load Arcane Agents data";e(p)})},[e]),x.useEffect(()=>{let f=null,p=null,d=null,g=!1,y=!1;const C=async()=>{if(!(g||y)){y=!0;try{const E=await Q_();if(g)return;l(E),h(!0)}catch{}finally{y=!1}}},S=()=>{document.visibilityState==="visible"&&C()},v=()=>{C()};function w(){if(g)return;const E=window.location.protocol==="https:"?"wss":"ws";f=new WebSocket(`${E}://${window.location.host}/api/ws`),f.addEventListener("open",()=>{e(void 0),C()}),f.addEventListener("message",L=>{const j=JSON.parse(String(L.data));if(j.type==="init"){s(j.config),l(j.workers),h(!0);return}if(j.type==="worker-created"||j.type==="worker-updated"){l($=>tn($,j.worker));return}j.type==="worker-removed"&&l($=>$.filter(W=>W.id!==j.workerId))}),f.addEventListener("error",()=>{e("Realtime connection failed. Retrying...")}),f.addEventListener("close",()=>{g||(p=setTimeout(w,2e3))})}return d=setInterval(()=>{C()},Gw),window.addEventListener("focus",v),document.addEventListener("visibilitychange",S),w(),()=>{g=!0,p&&clearTimeout(p),d&&clearInterval(d),window.removeEventListener("focus",v),document.removeEventListener("visibilitychange",S),f?.close()}},[e]),{config:t,workers:n,setWorkers:l,workersHydrated:c}}function Jw(e,t){const[s,n]=x.useState(()=>Xg()),l=x.useRef(s),[c,h]=x.useState(()=>Qg());return x.useEffect(()=>{l.current=s,Gg(s)},[s]),x.useEffect(()=>{Jg(c)},[c]),x.useEffect(()=>{if(!t)return;const f=new Set(e.map(p=>p.id));n(p=>{let d=!1;const g={...p};for(const[y,C]of Object.entries(g)){if(!Array.isArray(C)||C.length===0){delete g[Number(y)],d=!0;continue}const S=C.filter(v=>f.has(v));S.length!==C.length&&(S.length===0?delete g[Number(y)]:g[Number(y)]=S,d=!0)}return d?g:p})},[e,t]),{controlGroups:s,setControlGroups:n,controlGroupByDigitRef:l,mapColumnRatio:c,setMapColumnRatio:f=>{h(p=>{const d=vi(f,no,oo);return d===p?p:d})},nudgeMapColumnRatio:f=>{h(p=>vi(p+f,no,oo))},resetMapColumnRatio:()=>{h(Gn)}}}function Zw(e,t,s){const[n,l]=x.useState([]),[c,h]=x.useState(0),[f,p]=x.useState(void 0),[d,g]=x.useState(void 0),[y,C]=x.useState(0),[S,v]=x.useState(0),[w,E]=x.useState(void 0),L=n.length===1?n[0]:void 0,j=x.useMemo(()=>new Set(n),[n]),$=x.useMemo(()=>e.filter(b=>j.has(b.id)),[e,j]),W=x.useMemo(()=>e.filter(b=>b.status==="idle"),[e]),Q=x.useCallback((b,I)=>{const N=Array.from(new Set(b));l(N),E(void 0);const O=N.length===1?N[0]:void 0;I?.center&&O&&(p(O),h(Y=>Y+1)),I?.focusTerminal&&O&&g(Y=>(Y??0)+1)},[]),K=x.useCallback(()=>{g(b=>(b??0)+1)},[]),Z=x.useCallback(b=>{Q(b?[b]:[])},[Q]),fe=x.useCallback(b=>{Q(b)},[Q]),oe=x.useCallback(b=>{Q([b],{focusTerminal:!0})},[Q]),he=x.useCallback(b=>{if(e.length===0)return;const I=e.findIndex(B=>B.id===L),O=((I>=0?I:b>0?-1:0)+b+e.length)%e.length,Y=e[O];Y&&Q([Y.id],{center:!0})},[e,Q,L]),V=x.useCallback(b=>{if(W.length===0)return;const I=W.findIndex(B=>B.id===L),O=((I>=0?I:b>0?-1:0)+b+W.length)%W.length,Y=W[O];Y&&Q([Y.id],{center:!0})},[Q,W,L]),D=x.useCallback(b=>{if($.length<=1)return;const I=w?$.findIndex(B=>B.id===w):vi(S,0,$.length-1),O=((I>=0?I:b>0?-1:0)+b+$.length)%$.length,Y=$[O];Y&&(v(O),E(Y.id))},[w,S,$]);return x.useEffect(()=>{if(t.length===0){C(0);return}if(L){const b=t.findIndex(I=>I.kind==="worker"&&I.worker.id===L);b>=0&&C(b);return}C(b=>vi(b,0,t.length-1))},[t,L]),x.useEffect(()=>{if($.length<=1){v(0),E(void 0),s?.();return}if(w){const b=$.findIndex(I=>I.id===w);if(b>=0){v(b);return}E(void 0)}v(b=>vi(b,0,$.length-1))},[w,s,$]),x.useEffect(()=>{const b=new Set(e.map(I=>I.id));l(I=>I.filter(N=>b.has(N)))},[e]),{selectedWorkerIds:n,setSelectedWorkerIds:l,selectedWorkerId:L,selectedWorkers:$,mapCenterToken:c,mapCenterWorkerId:f,terminalFocusToken:d,rosterActiveIndex:y,setRosterActiveIndex:C,selectedGroupActiveIndex:S,setSelectedGroupActiveIndex:v,focusedSelectedWorkerId:w,setFocusedSelectedWorkerId:E,applySelection:Q,requestTerminalFocus:K,onSelectWorker:Z,onSelectionChange:fe,onActivateWorker:oe,cycleSelection:he,cycleIdleSelection:V,cycleSelectedGroupFocus:D}}function e1(e){const[t,s]=x.useState(!1);return x.useEffect(()=>{e||s(!1)},[e]),x.useEffect(()=>{const n=()=>{s(Kl(document.activeElement))},l=()=>{setTimeout(n,0)},c=()=>{s(!1)};return window.addEventListener("focusin",n,!0),window.addEventListener("focusout",l,!0),window.addEventListener("blur",c),n(),()=>{window.removeEventListener("focusin",n,!0),window.removeEventListener("focusout",l,!0),window.removeEventListener("blur",c)}},[]),t}function t1({workers:e,reviewedWorkerId:t}){const[s,n]=x.useState([]),l=x.useRef(new Map),c=x.useRef(t);return x.useEffect(()=>{c.current=t},[t]),x.useEffect(()=>{const h=new Set(e.map(g=>g.id)),f=new Set,p=new Set;for(const g of e)if(l.current.get(g.id)==="working"&&g.status==="idle"){if(g.id===c.current)continue;p.add(g.id)}else g.status!=="idle"&&f.add(g.id);const d=new Map;for(const g of e)d.set(g.id,g.status);l.current=d,n(g=>{const C=[...g.filter(S=>h.has(S)&&!f.has(S))];for(const S of p)C.includes(S)||C.push(S);return C.length===g.length&&C.every((S,v)=>S===g[v])?g:C})},[e]),x.useEffect(()=>{t&&n(h=>h.filter(f=>f!==t))},[t]),{pendingCompletionWorkerIds:s}}function r1(e){const[t,s]=x.useState([]),n=x.useCallback(c=>{s(h=>[{worker:c,startedAtMs:Date.now()},...h.filter(f=>f.worker.id!==c.id)])},[]),l=x.useCallback(c=>{s(h=>h.filter(f=>f.worker.id!==c))},[]);return x.useEffect(()=>{const c=setInterval(()=>{const h=Date.now();s(f=>f.filter(p=>h-p.startedAtMs<e))},80);return()=>{clearInterval(c)}},[e]),{fadingWorkers:t,queueWorkerFade:n,removeWorkerFade:l}}const i1=1600,Jc=["move","selected"],s1={move:["move.mp3","move_variant_1.mp3","move_variant_2.mp3","move_variant_3.mp3"],selected:["selected.mp3","selected_variant_1.mp3","selected_variant_2.mp3","selected_variant_3.mp3"]};function n1({config:e,workers:t,workersHydrated:s,selectedWorkerIds:n}){const l=e?.audio.enableSound??!0,c=x.useRef(new Map),h=x.useRef(new Set),f=x.useRef(new Map),p=x.useRef(!1),d=x.useRef(!1),g=x.useRef(new Map),y=x.useRef(new Set),C=x.useRef(new Map),S=x.useRef(new Map),v=x.useRef(new Set),w=x.useRef(null);x.useEffect(()=>{f.current=new Map(t.map(V=>[V.id,V]))},[t]);const E=x.useCallback((V,D)=>`/api/assets/characters/${encodeURIComponent(V)}/voice-lines/${D}.mp3`,[]),L=x.useCallback((V,D)=>`/api/assets/characters/${encodeURIComponent(V)}/voice-lines/${encodeURIComponent(D)}`,[]),j=x.useCallback((V,D)=>{const b=g.current.get(V)?.[D]??[];return b.length>0?b:s1[D].map(I=>L(V,I))},[L]),$=x.useCallback(V=>{if(v.current.has(V))return;v.current.add(V);const D=new Audio(V);D.preload="auto",D.addEventListener("canplay",()=>{S.current.set(V,!0)},{once:!0}),D.addEventListener("error",()=>{S.current.set(V,!1)},{once:!0}),D.load()},[]),W=x.useCallback(V=>{if(!l||S.current.get(V)===!1)return;const D=w.current;D&&(D.pause(),D.currentTime=0);const b=new Audio(V);b.preload="auto",b.addEventListener("canplay",()=>{S.current.set(V,!0)},{once:!0}),b.addEventListener("error",()=>{S.current.set(V,!1)},{once:!0}),w.current=b,b.play().catch(()=>{})},[l]),Q=x.useCallback((V,D)=>{W(E(V.avatarType,D))},[W,E]),K=x.useCallback(V=>{if(!l)return;const D=V.filter(O=>S.current.get(O)===!0),b=V.filter(O=>S.current.get(O)!==!1),I=D.length>0?D:b,N=ep(I);N&&W(N)},[W,l]),Z=x.useCallback(V=>{const D=j(V.avatarType,"selected");K(D)},[K,j]),fe=x.useCallback(V=>{const D=j(V.avatarType,"move");K(D)},[K,j]),oe=x.useCallback(async V=>{try{const D=await fetch(`/api/avatars/${encodeURIComponent(V)}/voice-lines`);if(!D.ok)return;const b=await D.json();if(!Array.isArray(b.files))return;const I=b.files.filter(O=>typeof O=="string"&&O.toLowerCase().endsWith(".mp3")).sort((O,Y)=>O.localeCompare(Y)),N={move:[],selected:[]};for(const O of Jc)N[O]=I.filter(Y=>Y.toLowerCase().startsWith(O)).map(Y=>L(V,Y));g.current.set(V,N);for(const O of Jc)for(const Y of N[O])$(Y)}catch{y.current.delete(V)}},[$,L]);x.useEffect(()=>{const V=Array.from(new Set(t.map(D=>D.avatarType)));for(const D of V)y.current.has(D)||(y.current.add(D),oe(D))},[oe,t]),x.useEffect(()=>{if(!l)return;const V=Array.from(new Set(t.map(b=>b.avatarType))),D=["arrive","attention","complete","death"];for(const b of V){for(const I of D)$(E(b,I));for(const I of Jc)for(const N of j(b,I))$(N)}},[$,E,j,l,t]),x.useEffect(()=>{if(!s)return;const V=new Map(t.map(N=>[N.id,N])),D=c.current,b=C.current,I=performance.now();if(!p.current){p.current=!0,c.current=V;return}for(const N of t){const O=D.get(N.id);if(!O){Q(N,"arrive"),b.set(N.id,I+i1);continue}o1(O.status,N.status)&&Q(N,"attention"),l1(O.status,N.status)&&!c1(N)&&Q(N,"complete")}for(const[N,O]of D.entries())V.has(N)||(Q(O,"death"),b.delete(N));c.current=V},[Q,t,s]),x.useEffect(()=>{if(!s)return;const V=new Set(n),D=h.current;if(!d.current){d.current=!0,h.current=V;return}const b=n.filter(B=>!D.has(B)),I=C.current,N=performance.now(),O=b.filter(B=>{const F=I.get(B);return F===void 0?!0:F<=N?(I.delete(B),!0):!1}),Y=ep(O);if(Y){const B=f.current.get(Y);B&&Z(B)}h.current=V},[Z,n,s]);const he=x.useCallback(V=>{const D=f.current.get(V);D&&fe(D)},[fe]);return x.useEffect(()=>()=>{const V=w.current;V&&(V.pause(),V.currentTime=0,w.current=null)},[]),{playMoveVoiceLine:he}}function o1(e,t){return e!=="attention"&&t==="attention"}function l1(e,t){return e==="working"&&t==="idle"}const a1=1e4;function c1(e){return Date.now()-new Date(e.createdAt).getTime()<a1}function ep(e){if(e.length===0)return;const t=Math.floor(Math.random()*e.length);return e[t]}function h1({workers:e,activeWorkers:t,renameModalOpen:s,setRenameModalOpen:n,renameTargetWorkerIds:l,setRenameTargetWorkerIds:c,killConfirmWorkerIds:h,setKillConfirmWorkerIds:f,setRenameDraft:p}){const d=x.useMemo(()=>{if(l.length===0)return[];const v=new Map(e.map(w=>[w.id,w]));return l.map(w=>v.get(w)).filter(w=>!!w)},[l,e]),g=x.useMemo(()=>{if(h.length===0)return[];const v=new Map(e.map(w=>[w.id,w]));return h.map(w=>v.get(w)).filter(w=>!!w)},[h,e]),y=x.useCallback(()=>{n(!1),c([])},[n,c]),C=x.useCallback(()=>{f([])},[f]),S=x.useCallback(v=>{v.length!==0&&(p(v.length===1?v[0].displayName??v[0].name:""),c(v.map(w=>w.id)),n(!0))},[p,n,c]);return x.useEffect(()=>{if(!s||l.length===0)return;const v=new Set(t.map(w=>w.id));l.some(w=>v.has(w))||y()},[t,y,s,l]),x.useEffect(()=>{if(h.length===0)return;const v=new Set(t.map(w=>w.id));h.some(w=>v.has(w))||C()},[t,C,h]),{renameTargetWorkers:d,killConfirmWorkers:g,closeRenameModal:y,closeKillConfirm:C,openRenameForWorkers:S}}function u1({workers:e,selectedWorkerIds:t,setSelectedWorkerIds:s,rosterEntries:n,rosterActiveIndex:l,setKillConfirmWorkerIds:c,closeKillConfirm:h,killConfirmWorkerIds:f,queueWorkerFade:p,removeWorkerFade:d,setWorkers:g,showError:y}){const C=x.useCallback(async E=>{const L=e.find(j=>j.id===E);if(L){h(),p(L);try{const j=await Vw(E);g($=>$.filter(W=>W.id!==j.workerId)),s($=>$.filter(W=>W!==j.workerId))}catch(j){d(E),y(j)}}},[h,p,d,s,g,y,e]),S=x.useCallback(()=>{t.length!==0&&c(t)},[t,c]),v=x.useCallback(()=>{const E=n[l];!E||E.kind!=="worker"||c([E.worker.id])},[l,n,c]),w=x.useCallback(()=>{if(f.length===0)return;const E=[...f];h();for(const L of E)C(L)},[h,f,C]);return{onKillSelected:S,onKillRosterActive:v,confirmKillSelection:w}}function d1({setWorkers:e,selectedWorkers:t,terminalWorkerId:s,applySelection:n,setSpawnDialogOpen:l,setPaletteOpen:c,renameTargetWorkerIds:h,closeRenameModal:f,showError:p}){const d=x.useCallback(async v=>{try{const w=t.map(j=>j.id),E="shortcutIndex"in v&&w.length>0?{...v,spawnNearWorkerIds:w}:v,L=await Uw(E);e(j=>tn(j,L)),n([L.id],{center:!0}),l(!1),c(!1)}catch(w){p(w)}},[n,t,c,l,e,p]),g=x.useCallback(async v=>{const w=[...h];if(w.length===0){f();return}try{if(w.length===1){const E=await J_(w[0],v);e(L=>tn(L,E))}else{const E=v.trim(),L=await Promise.all(w.map((j,$)=>J_(j,E.length>0?`${E} ${$+1}`:"")));e(j=>{let $=j;for(const W of L)$=tn($,W);return $})}f()}catch(E){p(E)}},[f,h,e,p]),y=x.useCallback(async()=>{if(t.length===0)return;const v=t.every(w=>w.movementMode==="hold")?"wander":"hold";try{const w=await Promise.all(t.map(E=>qw(E.id,v)));e(E=>{let L=E;for(const j of w)L=tn(L,j);return L})}catch(w){p(w)}},[t,e,p]),C=x.useCallback(async()=>{if(s)try{await Xw(s)}catch(v){p(v)}},[p,s]),S=x.useCallback((v,w)=>{Yw(v,w.x,w.y).then(E=>{e(L=>tn(L,E))}).catch(E=>{p(E)})},[e,p]);return{runSpawn:d,submitRename:g,onToggleMovementModeSelected:y,onOpenSelectedInTerminal:C,onPositionCommit:S}}function f1({selectedWorkers:e,rallyCommandDraft:t,setRallyCommandDraft:s,rallyCommandSending:n,setRallyCommandSending:l,rallyCommandResultText:c,setRallyCommandResultText:h,showError:f}){const p=x.useCallback(async()=>{if(n)return;const g=e.map(y=>y.id);if(!(g.length<=1)){if(t.length===0){h("Enter a command to broadcast.");return}l(!0),h(void 0);try{const C=t.includes("$NAME")?iv(await Promise.all(e.map(async S=>{const v=t.replace(/\$NAME/g,S.displayName??S.name);try{return await Z_([S.id],v,!0)}catch(w){return{requestedCount:1,deliveredWorkerIds:[],skippedWorkerIds:[],failed:[{workerId:S.id,error:w instanceof Error?w.message:"Failed to send input"}]}}}))):await Z_(g,t,!0);s(""),h(rv(C))}catch(y){f(y)}finally{l(!1)}}},[t,n,e,s,h,l,f]),d=x.useCallback(g=>{s(g),c&&h(void 0)},[c,s,h]);return{onSendRallyCommand:p,onRallyCommandDraftChange:d}}function _1({workers:e,activeWorkers:t,setWorkers:s,selectedWorkers:n,selectedWorkerIds:l,setSelectedWorkerIds:c,focusedSelectedWorkerId:h,terminalWorkerId:f,rosterEntries:p,rosterActiveIndex:d,setRosterActiveIndex:g,applySelection:y,setSpawnDialogOpen:C,setPaletteOpen:S,renameModalOpen:v,setRenameModalOpen:w,renameTargetWorkerIds:E,setRenameTargetWorkerIds:L,setRenameDraft:j,killConfirmWorkerIds:$,setKillConfirmWorkerIds:W,rallyCommandDraft:Q,setRallyCommandDraft:K,rallyCommandSending:Z,setRallyCommandSending:fe,rallyCommandResultText:oe,setRallyCommandResultText:he,queueWorkerFade:V,removeWorkerFade:D,setErrorText:b}){const I=x.useCallback(Fe=>{b(Fe instanceof Error?Fe.message:"Unknown request failure")},[b]),{renameTargetWorkers:N,killConfirmWorkers:O,closeRenameModal:Y,closeKillConfirm:B,openRenameForWorkers:F}=h1({workers:e,activeWorkers:t,renameModalOpen:v,setRenameModalOpen:w,renameTargetWorkerIds:E,setRenameTargetWorkerIds:L,killConfirmWorkerIds:$,setKillConfirmWorkerIds:W,setRenameDraft:j}),{runSpawn:J,submitRename:R,onToggleMovementModeSelected:U,onOpenSelectedInTerminal:le,onPositionCommit:ie}=d1({setWorkers:s,selectedWorkers:n,terminalWorkerId:f,applySelection:y,setSpawnDialogOpen:C,setPaletteOpen:S,renameTargetWorkerIds:E,closeRenameModal:Y,showError:I}),{onKillSelected:ue,onKillRosterActive:_e,confirmKillSelection:Re}=u1({workers:e,selectedWorkerIds:l,setSelectedWorkerIds:c,rosterEntries:p,rosterActiveIndex:d,setKillConfirmWorkerIds:W,closeKillConfirm:B,killConfirmWorkerIds:$,queueWorkerFade:V,removeWorkerFade:D,setWorkers:s,showError:I}),{onSendRallyCommand:ge,onRallyCommandDraftChange:ke}=f1({selectedWorkers:n,rallyCommandDraft:Q,setRallyCommandDraft:K,rallyCommandSending:Z,setRallyCommandSending:fe,rallyCommandResultText:oe,setRallyCommandResultText:he,showError:I}),Ae=x.useCallback(Fe=>{const be=p[Fe];if(be){if(g(Fe),be.kind==="worker"){y([be.worker.id],{center:!0});return}J({shortcutIndex:be.shortcutIndex})}},[y,p,J,g]),Ye=x.useCallback(()=>{if(n.length===0)return;const Fe=n.length>1?n.find(be=>be.id===h):void 0;F(Fe?[Fe]:n)},[h,F,n]);return{renameTargetWorkers:N,killConfirmWorkers:O,runSpawn:J,closeRenameModal:Y,closeKillConfirm:B,openRenameForWorkers:F,submitRename:R,onKillSelected:ue,onKillRosterActive:_e,confirmKillSelection:Re,onToggleMovementModeSelected:U,onActivateRosterIndex:Ae,onOpenSelectedInTerminal:le,onSendRallyCommand:ge,onRallyCommandDraftChange:ke,onRenameSelected:Ye,onPositionCommit:ie}}function p1(e){const t=[];return e.forEach((s,n)=>{for(const l of s.hotkeys??[]){const c=v1(l);c&&t.push({shortcutIndex:n,hotkey:c})}}),t}function m1(e,t){const s=[],n=new Set;for(const l of e)g1(l.hotkey,t)&&(n.has(l.shortcutIndex)||(n.add(l.shortcutIndex),s.push(l.shortcutIndex)));return s}function g1(e,t){return t.ctrlKey!==e.ctrl||t.metaKey!==e.meta||t.altKey!==e.alt||t.shiftKey!==e.shift?!1:w1(t.key)===e.key?!0:e.code?t.code===e.code:!1}function v1(e){const t=y1(e);if(t.length===0)return;let s=!1,n=!1,l=!1,c=!1,h;for(const p of t){const d=p.trim().toLowerCase();if(d){if(d==="ctrl"||d==="control"){s=!0;continue}if(d==="cmd"||d==="meta"||d==="super"){n=!0;continue}if(d==="alt"||d==="option"){l=!0;continue}if(d==="shift"){c=!0;continue}if(h)return;h=p}}if(!h)return;const f=S1(h);if(f)return{key:f.key,code:f.code,ctrl:s,meta:n,alt:l,shift:c}}function y1(e){const t=e.trim().replace(/\s+/g,"");if(!t)return[];if(t.includes("+"))return t.split("+").filter(l=>l.length>0);const s=t.toLowerCase();return s.includes("ctrl-")||s.includes("control-")||s.includes("cmd-")||s.includes("meta-")||s.includes("super-")||s.includes("alt-")||s.includes("option-")||s.includes("shift-")?t.split("-").filter(l=>l.length>0):[t]}function S1(e){const t=e.trim();if(!t)return;const s=t.toLowerCase();if(s==="space"||s==="spacebar")return{key:" ",code:"Space"};if(s==="esc")return{key:"escape",code:"Escape"};if(s==="return")return{key:"enter",code:"Enter"};if(s==="up")return{key:"arrowup",code:"ArrowUp"};if(s==="down")return{key:"arrowdown",code:"ArrowDown"};if(s==="left")return{key:"arrowleft",code:"ArrowLeft"};if(s==="right")return{key:"arrowright",code:"ArrowRight"};if(/^key[a-z]$/.test(s)){const n=s.slice(3);return{key:n,code:`Key${n.toUpperCase()}`}}if(/^digit[0-9]$/.test(s)){const n=s.slice(5);return{key:n,code:`Digit${n}`}}if(/^numpad[0-9]$/.test(s)){const n=s.slice(6);return{key:n,code:`Numpad${n}`}}if(/^f[0-9]{1,2}$/.test(s))return{key:s,code:s.toUpperCase()};if(t.length===1){if(/^[a-z]$/i.test(t)){const n=t.toLowerCase();return{key:n,code:`Key${n.toUpperCase()}`}}return/^[0-9]$/.test(t)?{key:t}:{key:t}}return{key:s}}function w1(e){const t=e.toLowerCase();return t==="spacebar"?" ":t}function C1(){const[e,t]=x.useState(!1),[s,n]=x.useState(!1),[l,c]=x.useState(!1),[h,f]=x.useState(!1),[p,d]=x.useState([]),[g,y]=x.useState(""),[C,S]=x.useState([]),[v,w]=x.useState(""),[E,L]=x.useState(!1),[j,$]=x.useState(void 0),[W,Q]=x.useState(void 0),K=x.useRef(null),{config:Z,workers:fe,setWorkers:oe,workersHydrated:he}=Qw(Q),{fadingWorkers:V,queueWorkerFade:D,removeWorkerFade:b}=r1(qg),I=x.useMemo(()=>fe.filter(ne=>ne.status!=="stopped"),[fe]),{controlGroups:N,setControlGroups:O,controlGroupByDigitRef:Y,mapColumnRatio:B,setMapColumnRatio:F,nudgeMapColumnRatio:J,resetMapColumnRatio:R}=Jw(I,he),U=x.useRef(null),le=x.useRef(void 0),[ie,ue]=x.useState(!1),_e=x.useMemo(()=>Z?.shortcuts??[],[Z]),Re=x.useMemo(()=>p1(_e),[_e]),ge=x.useMemo(()=>[...I.map(ne=>({kind:"worker",worker:ne})),..._e.map((ne,De)=>({kind:"shortcut",shortcut:ne,shortcutIndex:De}))],[I,_e]),ke=x.useMemo(()=>{const ne=ge.findIndex(De=>De.kind==="shortcut");return ne>=0?ne:void 0},[ge]),Ae=x.useCallback(()=>{w(""),$(void 0)},[]),{selectedWorkerIds:Ye,setSelectedWorkerIds:Fe,selectedWorkerId:be,selectedWorkers:et,mapCenterToken:ni,mapCenterWorkerId:it,terminalFocusToken:dr,rosterActiveIndex:Ft,setRosterActiveIndex:at,selectedGroupActiveIndex:qt,setSelectedGroupActiveIndex:Nt,focusedSelectedWorkerId:fr,setFocusedSelectedWorkerId:Ci,applySelection:ki,requestTerminalFocus:bi,onSelectWorker:Ji,onSelectionChange:xi,onActivateWorker:Tr,cycleSelection:Oe,cycleIdleSelection:oi,cycleSelectedGroupFocus:Fr}=Zw(I,ge,Ae),rr=x.useMemo(()=>I.find(ne=>ne.id===be),[I,be]),Pr=x.useMemo(()=>{if(fr)return et.find(ne=>ne.id===fr)},[fr,et]),Ei=rr??(et.length>1?Pr:void 0),_r=Ei?.id,pr=et.length>1&&!Ei,Hr=e1(_r),{pendingCompletionWorkerIds:Lr}=t1({workers:I,reviewedWorkerId:_r}),{playMoveVoiceLine:jr}=n1({config:Z,workers:I,workersHydrated:he,selectedWorkerIds:Ye}),Zi=x.useCallback(()=>{const ne=document.activeElement;return!(ne instanceof HTMLElement)||!ne.closest(".terminal-panel")?!1:(ne.blur(),document.querySelector(".map-canvas")?.focus(),!0)},[]),$r=x.useCallback(()=>{const ne=K.current;if(!ne)return!1;ne.focus();const De=ne.value.length;return ne.setSelectionRange(De,De),!0},[]),ir=x.useCallback(ne=>{const De=U.current;if(!De)return;const Ge=De.getBoundingClientRect(),$e=Math.max(1,Ge.width-Bc),sr=ne-Ge.left-Bc/2,Me=vi(sr/$e,no,oo);F(Me)},[F]);x.useEffect(()=>{if(ie)return document.body.classList.add("split-pane-dragging"),()=>{document.body.classList.remove("split-pane-dragging")}},[ie]);const{renameTargetWorkers:Kr,killConfirmWorkers:Es,runSpawn:Ur,closeRenameModal:es,closeKillConfirm:ts,openRenameForWorkers:rs,submitRename:Ms,onKillSelected:Vr,onKillRosterActive:is,confirmKillSelection:mr,onToggleMovementModeSelected:Rs,onActivateRosterIndex:q,onOpenSelectedInTerminal:X,onSendRallyCommand:se,onRallyCommandDraftChange:de,onRenameSelected:ye,onPositionCommit:ze}=_1({workers:fe,activeWorkers:I,setWorkers:oe,selectedWorkers:et,selectedWorkerIds:Ye,setSelectedWorkerIds:Fe,focusedSelectedWorkerId:fr,terminalWorkerId:_r,rosterEntries:ge,rosterActiveIndex:Ft,setRosterActiveIndex:at,applySelection:ki,setSpawnDialogOpen:t,setPaletteOpen:n,renameModalOpen:h,setRenameModalOpen:f,renameTargetWorkerIds:C,setRenameTargetWorkerIds:S,setRenameDraft:y,killConfirmWorkerIds:p,setKillConfirmWorkerIds:d,rallyCommandDraft:v,setRallyCommandDraft:w,rallyCommandSending:E,setRallyCommandSending:L,rallyCommandResultText:j,setRallyCommandResultText:$,queueWorkerFade:D,removeWorkerFade:b,setErrorText:Q});return jw({activeWorkers:I,applySelection:ki,clampNumber:vi,closeKillConfirm:ts,closeRenameModal:es,confirmKillSelection:mr,controlGroupByDigitRef:Y,cycleIdleSelection:oi,cycleSelectedGroupFocus:Fr,cycleSelection:Oe,escapeTerminalFocus:Zi,findMatchingShortcutIndexes:m1,firstSummonEntryIndex:ke,focusRallyCommandInput:$r,focusedSelectedWorkerId:fr,inSelectedGroupView:pr,isEditableTarget:jh,isTerminalEscapeShortcut:nv,isTerminalTarget:Zg,killConfirmWorkerIds:p,mapColumnRatioStep:Hh,nudgeMapColumnRatio:J,onActivateRosterIndex:q,onKillRosterActive:is,onKillSelected:Vr,onToggleMovementModeSelected:Rs,openRenameForWorkers:rs,paletteOpen:s,parseControlGroupDigit:sv,renameModalOpen:h,requestTerminalFocus:bi,resetMapColumnRatio:R,rosterActiveIndex:Ft,rosterEntries:ge,runSpawn:Ur,selectedGroupActiveIndex:qt,selectedWorkerId:be,selectedWorkerIds:Ye,selectedWorkers:et,setControlGroups:O,setFocusedSelectedWorkerId:Ci,setPaletteOpen:n,setRosterActiveIndex:at,setSelectedGroupActiveIndex:Nt,setShortcutsOverlayOpen:c,setSpawnDialogOpen:t,shortcutHotkeyBindings:Re,shortcutsOverlayOpen:l,spawnDialogOpen:e}),T.jsxs("div",{ref:U,className:"app-shell",style:{gridTemplateColumns:`minmax(0px, ${B.toFixed(3)}fr) ${Bc}px minmax(0px, ${(1-B).toFixed(3)}fr)`},children:[T.jsxs("div",{className:"map-column",children:[T.jsx(v0,{workers:I,fadingWorkers:V,selectedWorkerId:be,selectedWorkerIds:Ye,focusedSelectedWorkerId:fr,terminalFocusedSelected:!!(be&&Hr),terminalFocusedWorkerId:Hr?_r:void 0,controlGroups:N,completionPendingWorkerIds:Lr,onSelect:Ji,onSelectionChange:xi,onActivateWorker:Tr,onMoveOrderIssued:jr,onPositionCommit:ze,centerOnWorkerId:it,centerRequestKey:ni}),T.jsx(ov,{shortcuts:Z?.shortcuts??[],selectedWorker:rr,selectedWorkers:et,onSpawnShortcut:ne=>{Ur({shortcutIndex:ne})},onOpenSpawnDialog:()=>{t(!0),n(!1)},onOpenPalette:()=>{n(!0),t(!1)},onDeselect:()=>Ji(void 0),onKillSelected:()=>{Vr()},onRenameSelected:()=>{ye()},onToggleMovementMode:()=>{Rs()}})]}),T.jsx("div",{className:`layout-divider${ie?" layout-divider-active":""}`,role:"separator","aria-label":"Resize map and terminal columns","aria-orientation":"vertical",onPointerDown:ne=>{ne.button===0&&(ne.preventDefault(),le.current=ne.pointerId,ue(!0),ne.currentTarget.setPointerCapture(ne.pointerId),ir(ne.clientX))},onPointerMove:ne=>{le.current===ne.pointerId&&(ne.preventDefault(),ir(ne.clientX))},onPointerUp:ne=>{le.current===ne.pointerId&&(ne.preventDefault(),ne.currentTarget.hasPointerCapture(ne.pointerId)&&ne.currentTarget.releasePointerCapture(ne.pointerId),le.current=void 0,ue(!1))},onPointerCancel:ne=>{le.current===ne.pointerId&&(ne.currentTarget.hasPointerCapture(ne.pointerId)&&ne.currentTarget.releasePointerCapture(ne.pointerId),le.current=void 0,ue(!1))}}),T.jsx(zw,{activeWorkers:I,selectedWorkers:et,terminalWorker:Ei,terminalFocused:Hr,selectedGroupActiveIndex:qt,setSelectedGroupActiveIndex:Nt,setFocusedSelectedWorkerId:Ci,rallyCommandInputRef:K,rallyCommandDraft:v,rallyCommandSending:E,rallyCommandResultText:j,onRallyCommandDraftChange:de,onSendRallyCommand:se,rosterEntries:ge,completionPendingWorkerIds:Lr,rosterActiveIndex:Ft,setRosterActiveIndex:at,onActivateRosterIndex:q,onOpenSelectedInTerminal:X,terminalFocusToken:dr}),Z?T.jsx(w0,{open:e,projects:Z.projects,runtimes:Z.runtimes,onClose:()=>t(!1),onSpawn:(ne,De)=>{Ur({projectId:ne,runtimeId:De})}}):null,Z?T.jsx(lv,{open:s,config:Z,onClose:()=>n(!1),onSpawnShortcut:ne=>{Ur({shortcutIndex:ne})},onSpawnProjectRuntime:(ne,De)=>{Ur({projectId:ne,runtimeId:De})}}):null,T.jsx(S0,{open:l,onClose:()=>{c(!1)}}),T.jsx(av,{workerIds:p,workers:Es,onClose:ts,onConfirm:mr}),T.jsx(y0,{open:h,targetWorkerIds:C,targetWorkers:Kr,initialDraft:g,onClose:es,onSubmit:Ms}),W?T.jsx("div",{className:"error-toast",onClick:()=>Q(void 0),children:W}):null]})}const Zp=document.getElementById("root");if(!Zp)throw new Error("Missing #root container");Yg.createRoot(Zp).render(T.jsx(x.StrictMode,{children:T.jsx(C1,{})}));
80
+ [terminal connection error]`)}),()=>{E.dispose(),w.close(),h.current===w&&(h.current=null)}},[C,g,e,t]),x.useEffect(()=>{if(!e||s===void 0||p.current===s)return;p.current=s,d();const S=setTimeout(()=>{d()},0);return()=>{clearTimeout(S)}},[s,d,e]),T.jsx("div",{className:"terminal-panel",ref:n})}function Ow(e){return e.type==="keydown"&&e.key==="Enter"&&e.shiftKey&&!e.ctrlKey&&!e.metaKey&&!e.altKey}function zw({activeWorkers:e,selectedWorkers:t,terminalWorker:s,terminalFocused:n,selectedGroupActiveIndex:l,setSelectedGroupActiveIndex:c,setFocusedSelectedWorkerId:h,rallyCommandInputRef:f,rallyCommandDraft:p,rallyCommandSending:d,rallyCommandResultText:g,onRallyCommandDraftChange:y,onSendRallyCommand:C,rosterEntries:S,completionPendingWorkerIds:v,rosterActiveIndex:w,setRosterActiveIndex:E,onActivateRosterIndex:L,onOpenSelectedInTerminal:j,terminalFocusToken:$}){const W=x.useMemo(()=>new Set(v),[v]),Q=v.length;return T.jsxs("div",{className:`terminal-column${s?" terminal-column-selected":""}${s&&n?" terminal-column-focused":""}`,children:[T.jsxs("div",{className:"terminal-header",children:[T.jsx("div",{className:"terminal-header-title",children:t.length>1&&!s?`${t.length} selected agents`:s?`${s.displayName??s.name} (${s.status})`:`Agents (${e.length})`}),!s&&Q>0?T.jsxs("div",{className:"terminal-ready-chip",title:"Agents finished but not yet reviewed in terminal",children:["✦ ",Q," ready"]}):null,s?T.jsx("button",{className:"terminal-open-external",onClick:()=>{j()},disabled:s.status==="stopped",title:"Open in external terminal",type:"button",children:"↗"}):null]}),t.length>1&&!s?T.jsxs("div",{className:"worker-roster",children:[T.jsx("div",{className:"worker-roster-section-label",children:"Selected Group"}),t.map((K,Z)=>T.jsx("button",{className:`worker-roster-item ${Z===l?"active":""}`,onMouseEnter:()=>c(Z),onClick:()=>{c(Z),h(K.id)},type:"button",children:T.jsxs("div",{className:"worker-roster-main",children:[T.jsx("img",{className:"worker-roster-avatar",src:`/api/assets/characters/${encodeURIComponent(K.avatarType)}/rotations/south.png`,alt:"",loading:"lazy","aria-hidden":"true"}),T.jsxs("div",{className:"worker-roster-text",children:[T.jsxs("div",{className:"worker-roster-name-row",children:[T.jsx("div",{className:"worker-roster-name",children:K.displayName??K.name}),W.has(K.id)?T.jsx("span",{className:"worker-complete-badge",title:"Finished and waiting for review",children:"READY"}):null]}),T.jsxs("div",{className:"worker-roster-meta",children:[K.projectId," · ",K.runtimeId," · ",K.status]}),K.activityText?T.jsx("div",{className:"worker-roster-activity",children:K.activityText}):null]})]})},K.id)),T.jsxs("form",{className:"rally-command-card",onSubmit:K=>{K.preventDefault(),C()},children:[T.jsxs("div",{className:"rally-command-header",children:[T.jsx("div",{className:"rally-command-title",children:"Rally Command"}),T.jsxs("div",{className:"rally-command-count",children:[t.length," agents"]})]}),T.jsx("textarea",{ref:f,className:"input rally-command-input",value:p,onChange:K=>{y(K.target.value)},onKeyDown:K=>{K.key==="Enter"&&!K.shiftKey&&!K.ctrlKey&&!K.metaKey&&!K.altKey&&(K.preventDefault(),C())},placeholder:"Type once, send to all selected agents (use $NAME for per-agent names)...",disabled:d,rows:3}),T.jsxs("div",{className:"rally-command-actions",children:[T.jsx("div",{className:"rally-command-hint",children:"Enter sends, Shift+Enter adds a new line, $NAME inserts each agent's name"}),T.jsx("button",{className:"bar-btn",type:"submit",disabled:d||p.length===0,children:d?"Sending...":`Send to ${t.length}`})]}),g?T.jsx("div",{className:"rally-command-result",children:g}):null]})]}):s?T.jsx(Aw,{workerId:s.id,workerName:s.displayName??s.name,focusRequestKey:$}):T.jsx("div",{className:"worker-roster",children:S.length===0?T.jsx("div",{className:"worker-roster-empty",children:"No active agents yet. Summon one from the bottom bar."}):S.map((K,Z)=>T.jsxs("div",{children:[K.kind==="shortcut"&&(Z===0||S[Z-1]?.kind!=="shortcut")?T.jsx("div",{className:"worker-roster-section-label",children:"Summon"}):null,K.kind==="worker"?T.jsx("button",{className:`worker-roster-item ${Z===w?"active":""}`,onMouseEnter:()=>E(Z),onClick:()=>L(Z),type:"button",children:T.jsxs("div",{className:"worker-roster-main",children:[T.jsx("img",{className:"worker-roster-avatar",src:`/api/assets/characters/${encodeURIComponent(K.worker.avatarType)}/rotations/south.png`,alt:"",loading:"lazy","aria-hidden":"true"}),T.jsxs("div",{className:"worker-roster-text",children:[T.jsxs("div",{className:"worker-roster-name-row",children:[T.jsx("div",{className:"worker-roster-name",children:K.worker.displayName??K.worker.name}),W.has(K.worker.id)?T.jsx("span",{className:"worker-complete-badge",title:"Finished and waiting for review",children:"READY"}):null]}),T.jsxs("div",{className:"worker-roster-meta",children:[K.worker.projectId," · ",K.worker.runtimeId," · ",K.worker.status]}),K.worker.activityText?T.jsx("div",{className:"worker-roster-activity",children:K.worker.activityText}):null]})]})}):T.jsx("button",{className:`worker-roster-item worker-roster-item-summon ${Z===w?"active":""}`,onMouseEnter:()=>E(Z),onClick:()=>L(Z),type:"button",children:T.jsxs("div",{className:"worker-roster-main",children:[K.shortcut.avatar?T.jsx("img",{className:"worker-roster-avatar worker-roster-summon-avatar",src:`/api/assets/characters/${encodeURIComponent(K.shortcut.avatar)}/rotations/south.png`,alt:"",loading:"lazy","aria-hidden":"true"}):T.jsx("div",{className:"worker-roster-summon-glyph","aria-hidden":"true",children:"+"}),T.jsxs("div",{className:"worker-roster-text",children:[T.jsx("div",{className:"worker-roster-name",children:K.shortcut.label}),T.jsxs("div",{className:"worker-roster-meta",children:[K.shortcut.project," · ",K.shortcut.runtime]}),T.jsx("div",{className:"worker-roster-activity",children:tv(K.shortcut.hotkeys)})]})]})})]},K.kind==="worker"?K.worker.id:`shortcut-${K.shortcutIndex}-${K.shortcut.label}`))})]})}function Ww(e,t){if(t.killConfirmWorkerIds.length>0)return Il(e)?(e.preventDefault(),t.confirmKillSelection(),!0):(e.preventDefault(),t.closeKillConfirm(),!0);if(!t.isEditableTarget(e.target)&&!t.isTerminalTarget(e.target)){const s=t.findMatchingShortcutIndexes(t.shortcutHotkeyBindings,e);if(s.length>0){e.preventDefault();for(const n of s)t.runSpawn({shortcutIndex:n})}}if(e.key==="Escape")return t.renameModalOpen?(e.preventDefault(),t.closeRenameModal(),!0):t.shortcutsOverlayOpen?(e.preventDefault(),t.setShortcutsOverlayOpen(!1),!0):t.paletteOpen||t.spawnDialogOpen?(e.preventDefault(),t.setPaletteOpen(!1),t.setSpawnDialogOpen(!1),!0):(t.isTerminalTarget(e.target)||t.selectedWorkerId&&(e.preventDefault(),t.applySelection([])),!0);if(t.isTerminalEscapeShortcut(e)){if(!t.renameModalOpen&&!t.shortcutsOverlayOpen&&!t.paletteOpen&&!t.spawnDialogOpen&&t.selectedWorkers.length>1&&t.focusedSelectedWorkerId){const n=t.escapeTerminalFocus();return e.preventDefault(),n&&e.stopPropagation(),t.setFocusedSelectedWorkerId(void 0),!0}if(t.escapeTerminalFocus())return e.preventDefault(),e.stopPropagation(),!0;if(!t.renameModalOpen&&!t.shortcutsOverlayOpen&&!t.paletteOpen&&!t.spawnDialogOpen&&t.selectedWorkerIds.length>0){if(e.preventDefault(),t.selectedWorkerId){const n=t.rosterEntries.findIndex(l=>l.kind==="worker"&&l.worker.id===t.selectedWorkerId);n>=0&&t.setRosterActiveIndex(n)}t.applySelection([])}return!0}return e.key==="?"&&!e.ctrlKey&&!e.metaKey&&!e.altKey?((!t.isEditableTarget(e.target)||t.shortcutsOverlayOpen)&&(e.preventDefault(),t.setShortcutsOverlayOpen(s=>!s)),!0):(e.code==="BracketLeft"||e.code==="BracketRight"||e.key==="="&&!e.shiftKey)&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&!t.isEditableTarget(e.target)&&!t.isTerminalTarget(e.target)?(e.preventDefault(),e.code==="BracketLeft"?t.nudgeMapColumnRatio(e.shiftKey?-1:-t.mapColumnRatioStep):e.code==="BracketRight"?t.nudgeMapColumnRatio(e.shiftKey?1:t.mapColumnRatioStep):t.resetMapColumnRatio(),!0):!1}function Fw(e,t){if(e.key==="Tab"&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&!t.isEditableTarget(e.target))return t.isTerminalTarget(e.target)?!0:(e.preventDefault(),t.selectedWorkers.length>1?(t.cycleSelectedGroupFocus(e.shiftKey?-1:1),!0):(t.cycleSelection(e.shiftKey?-1:1),!0));if(e.code==="Period"&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&!t.isEditableTarget(e.target))return t.isTerminalTarget(e.target)||(e.preventDefault(),t.cycleIdleSelection(e.shiftKey?-1:1)),!0;if(e.code==="Comma"&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&!t.isEditableTarget(e.target))return t.isTerminalTarget(e.target)||(e.preventDefault(),t.cycleIdleSelection(-1)),!0;const s=t.parseControlGroupDigit(e);if(s===void 0)return!1;if((e.ctrlKey||e.metaKey)&&!e.altKey&&t.selectedWorkerIds.length>0)return e.preventDefault(),t.setControlGroups(n=>{const l=new Set(t.selectedWorkerIds),c=n[s]??[],h=new Set(c);if(c.length===t.selectedWorkerIds.length&&t.selectedWorkerIds.every(d=>h.has(d))){const d={...n};return delete d[s],d}const p={...n};for(const[d,g]of Object.entries(p)){const y=Number(d);!Number.isInteger(y)||y<0||y>9||y===s||!Array.isArray(g)||(p[y]=g.filter(C=>!l.has(C)))}return p[s]=[...t.selectedWorkerIds],p}),!0;if(!e.ctrlKey&&!e.metaKey&&!e.altKey&&!e.shiftKey&&!t.isEditableTarget(e.target)){const n=t.controlGroupByDigitRef.current[s]??[];if(n.length===0)return!0;const l=new Set(t.activeWorkers.map(h=>h.id)),c=n.filter(h=>l.has(h));if(c.length===0)return t.setControlGroups(h=>{if(!(s in h))return h;const f={...h};return delete f[s],f}),!0;e.preventDefault(),t.applySelection(c,{center:c.length===1})}return!0}function Hw(e,t){if(t.isEditableTarget(e.target))return!0;const s=e.key.toLowerCase();if((s==="k"&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&(t.inSelectedGroupView?e.shiftKey:t.selectedWorkerIds.length===1||!e.shiftKey)||e.key==="Delete")&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&t.selectedWorkerIds.length>0)return e.preventDefault(),t.onKillSelected(),!0;if(t.selectedWorkers.length>1&&!t.isTerminalTarget(e.target)){if(t.inSelectedGroupView&&s==="c"&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&!e.shiftKey)return t.focusRallyCommandInput()&&e.preventDefault(),!0;if((s==="j"||s==="k")&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&!e.shiftKey){e.preventDefault();const l=s==="j"?1:-1,c=t.clampNumber(t.selectedGroupActiveIndex+l,0,t.selectedWorkers.length-1),h=t.selectedWorkers[c];return t.setSelectedGroupActiveIndex(c),t.focusedSelectedWorkerId&&h&&t.setFocusedSelectedWorkerId(h.id),!0}if(Il(e)){const l=t.selectedWorkers.find(c=>c.id===t.focusedSelectedWorkerId)??t.selectedWorkers[t.selectedGroupActiveIndex]??t.selectedWorkers[0];return l&&(e.preventDefault(),t.setFocusedSelectedWorkerId(l.id),t.requestTerminalFocus()),!0}}if(t.selectedWorkerIds.length===0&&t.rosterEntries.length>0&&!t.isTerminalTarget(e.target)){if(s==="k"&&e.shiftKey&&!e.ctrlKey&&!e.metaKey&&!e.altKey)return e.preventDefault(),t.onKillRosterActive(),!0;if(s==="n"&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&!e.shiftKey&&t.firstSummonEntryIndex!==void 0)return e.preventDefault(),t.setRosterActiveIndex(t.firstSummonEntryIndex),!0;if((s==="j"||s==="k")&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&!e.shiftKey)return e.preventDefault(),t.setRosterActiveIndex(l=>{const c=s==="j"?1:-1;return t.clampNumber(l+c,0,t.rosterEntries.length-1)}),!0;if(Il(e))return e.preventDefault(),t.onActivateRosterIndex(t.rosterActiveIndex),!0}if(s==="r"&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&t.selectedWorkers.length>0){e.preventDefault();const l=t.selectedWorkers.length>1?t.selectedWorkers.find(c=>c.id===t.focusedSelectedWorkerId):void 0;return t.openRenameForWorkers(l?[l]:t.selectedWorkers),!0}return s==="m"&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&t.selectedWorkers.length>0?(e.preventDefault(),t.onToggleMovementModeSelected(),!0):Il(e)&&t.selectedWorkerId?(e.preventDefault(),t.requestTerminalFocus(),!0):e.key!=="/"||e.metaKey||e.ctrlKey||e.altKey?!1:(e.preventDefault(),t.setPaletteOpen(!0),t.setSpawnDialogOpen(!1),t.setShortcutsOverlayOpen(!1),!0)}function Il(e){return e.key==="Enter"&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&!e.shiftKey}function jw(e){const t=x.useRef(e);t.current=e,x.useEffect(()=>{const s=n=>{const l=t.current;$w(n,l)||Ww(n,l)||Fw(n,l)||Hw(n,l)};return window.addEventListener("keydown",s,!0),()=>window.removeEventListener("keydown",s,!0)},[])}function $w(e,t){return!(!t.isTerminalTarget(e.target)||t.killConfirmWorkerIds.length>0||t.renameModalOpen||t.shortcutsOverlayOpen||t.paletteOpen||t.spawnDialogOpen||e.key==="Escape"||t.isTerminalEscapeShortcut(e))}async function wi(e,t){const s=await fetch(e,{headers:{"Content-Type":"application/json",...t?.headers??{}},...t});if(!s.ok){const n=await s.json().catch(()=>({error:s.statusText}));throw new Error(n.error??`Request failed: ${s.status}`)}if(s.status!==204)return await s.json()}function Kw(){return wi("/api/config")}async function Q_(){return(await wi("/api/workers")).workers}function Uw(e){return wi("/api/workers/spawn",{method:"POST",body:JSON.stringify(e)})}function Vw(e){return wi(`/api/workers/${e}/stop`,{method:"POST"})}function Yw(e,t,s){return wi(`/api/workers/${e}/position`,{method:"PATCH",body:JSON.stringify({x:t,y:s})})}function J_(e,t){return wi(`/api/workers/${e}/rename`,{method:"PATCH",body:JSON.stringify({displayName:t})})}function qw(e,t){return wi(`/api/workers/${e}/movement-mode`,{method:"PATCH",body:JSON.stringify({movementMode:t})})}function Xw(e){return wi(`/api/workers/${e}/open-terminal`,{method:"POST"})}function Z_(e,t,s=!0){return wi("/api/workers/broadcast-input",{method:"POST",body:JSON.stringify({workerIds:e,text:t,submit:s})})}const Gw=3e4;function Qw(e){const[t,s]=x.useState(null),[n,l]=x.useState([]),[c,h]=x.useState(!1);return x.useEffect(()=>{Promise.all([Kw(),Q_()]).then(([f,p])=>{s(f),l(p),h(!0)}).catch(f=>{const p=f instanceof Error?f.message:"Failed to load Arcane Agents data";e(p)})},[e]),x.useEffect(()=>{let f=null,p=null,d=null,g=!1,y=!1;const C=async()=>{if(!(g||y)){y=!0;try{const E=await Q_();if(g)return;l(E),h(!0)}catch{}finally{y=!1}}},S=()=>{document.visibilityState==="visible"&&C()},v=()=>{C()};function w(){if(g)return;const E=window.location.protocol==="https:"?"wss":"ws";f=new WebSocket(`${E}://${window.location.host}/api/ws`),f.addEventListener("open",()=>{e(void 0),C()}),f.addEventListener("message",L=>{const j=JSON.parse(String(L.data));if(j.type==="init"){s(j.config),l(j.workers),h(!0);return}if(j.type==="worker-created"||j.type==="worker-updated"){l($=>tn($,j.worker));return}j.type==="worker-removed"&&l($=>$.filter(W=>W.id!==j.workerId))}),f.addEventListener("error",()=>{e("Realtime connection failed. Retrying...")}),f.addEventListener("close",()=>{g||(p=setTimeout(w,2e3))})}return d=setInterval(()=>{C()},Gw),window.addEventListener("focus",v),document.addEventListener("visibilitychange",S),w(),()=>{g=!0,p&&clearTimeout(p),d&&clearInterval(d),window.removeEventListener("focus",v),document.removeEventListener("visibilitychange",S),f?.close()}},[e]),{config:t,workers:n,setWorkers:l,workersHydrated:c}}function Jw(e,t){const[s,n]=x.useState(()=>Xg()),l=x.useRef(s),[c,h]=x.useState(()=>Qg());return x.useEffect(()=>{l.current=s,Gg(s)},[s]),x.useEffect(()=>{Jg(c)},[c]),x.useEffect(()=>{if(!t)return;const f=new Set(e.map(p=>p.id));n(p=>{let d=!1;const g={...p};for(const[y,C]of Object.entries(g)){if(!Array.isArray(C)||C.length===0){delete g[Number(y)],d=!0;continue}const S=C.filter(v=>f.has(v));S.length!==C.length&&(S.length===0?delete g[Number(y)]:g[Number(y)]=S,d=!0)}return d?g:p})},[e,t]),{controlGroups:s,setControlGroups:n,controlGroupByDigitRef:l,mapColumnRatio:c,setMapColumnRatio:f=>{h(p=>{const d=vi(f,no,oo);return d===p?p:d})},nudgeMapColumnRatio:f=>{h(p=>vi(p+f,no,oo))},resetMapColumnRatio:()=>{h(Gn)}}}function Zw(e,t,s){const[n,l]=x.useState([]),[c,h]=x.useState(0),[f,p]=x.useState(void 0),[d,g]=x.useState(void 0),[y,C]=x.useState(0),[S,v]=x.useState(0),[w,E]=x.useState(void 0),L=n.length===1?n[0]:void 0,j=x.useMemo(()=>new Set(n),[n]),$=x.useMemo(()=>e.filter(b=>j.has(b.id)),[e,j]),W=x.useMemo(()=>e.filter(b=>b.status==="idle"),[e]),Q=x.useCallback((b,I)=>{const N=Array.from(new Set(b));l(N),E(void 0);const O=N.length===1?N[0]:void 0;I?.center&&O&&(p(O),h(Y=>Y+1)),I?.focusTerminal&&O&&g(Y=>(Y??0)+1)},[]),K=x.useCallback(()=>{g(b=>(b??0)+1)},[]),Z=x.useCallback(b=>{Q(b?[b]:[])},[Q]),fe=x.useCallback(b=>{Q(b)},[Q]),oe=x.useCallback(b=>{Q([b],{focusTerminal:!0})},[Q]),he=x.useCallback(b=>{if(e.length===0)return;const I=e.findIndex(B=>B.id===L),O=((I>=0?I:b>0?-1:0)+b+e.length)%e.length,Y=e[O];Y&&Q([Y.id],{center:!0})},[e,Q,L]),V=x.useCallback(b=>{if(W.length===0)return;const I=W.findIndex(B=>B.id===L),O=((I>=0?I:b>0?-1:0)+b+W.length)%W.length,Y=W[O];Y&&Q([Y.id],{center:!0})},[Q,W,L]),D=x.useCallback(b=>{if($.length<=1)return;const I=w?$.findIndex(B=>B.id===w):vi(S,0,$.length-1),O=((I>=0?I:b>0?-1:0)+b+$.length)%$.length,Y=$[O];Y&&(v(O),E(Y.id))},[w,S,$]);return x.useEffect(()=>{if(t.length===0){C(0);return}if(L){const b=t.findIndex(I=>I.kind==="worker"&&I.worker.id===L);b>=0&&C(b);return}C(b=>vi(b,0,t.length-1))},[t,L]),x.useEffect(()=>{if($.length<=1){v(0),E(void 0),s?.();return}if(w){const b=$.findIndex(I=>I.id===w);if(b>=0){v(b);return}E(void 0)}v(b=>vi(b,0,$.length-1))},[w,s,$]),x.useEffect(()=>{const b=new Set(e.map(I=>I.id));l(I=>I.filter(N=>b.has(N)))},[e]),{selectedWorkerIds:n,setSelectedWorkerIds:l,selectedWorkerId:L,selectedWorkers:$,mapCenterToken:c,mapCenterWorkerId:f,terminalFocusToken:d,rosterActiveIndex:y,setRosterActiveIndex:C,selectedGroupActiveIndex:S,setSelectedGroupActiveIndex:v,focusedSelectedWorkerId:w,setFocusedSelectedWorkerId:E,applySelection:Q,requestTerminalFocus:K,onSelectWorker:Z,onSelectionChange:fe,onActivateWorker:oe,cycleSelection:he,cycleIdleSelection:V,cycleSelectedGroupFocus:D}}function e1(e){const[t,s]=x.useState(!1);return x.useEffect(()=>{s(!1),document.activeElement instanceof HTMLElement&&document.activeElement.closest(".terminal-panel")&&document.activeElement.blur()},[e]),x.useEffect(()=>{const n=()=>{s(Kl(document.activeElement))},l=()=>{setTimeout(n,0)},c=()=>{s(!1)};return window.addEventListener("focusin",n,!0),window.addEventListener("focusout",l,!0),window.addEventListener("blur",c),n(),()=>{window.removeEventListener("focusin",n,!0),window.removeEventListener("focusout",l,!0),window.removeEventListener("blur",c)}},[]),t}function t1({workers:e,reviewedWorkerId:t}){const[s,n]=x.useState([]),l=x.useRef(new Map),c=x.useRef(t);return x.useEffect(()=>{c.current=t},[t]),x.useEffect(()=>{const h=new Set(e.map(g=>g.id)),f=new Set,p=new Set;for(const g of e)if(l.current.get(g.id)==="working"&&g.status==="idle"){if(g.id===c.current)continue;p.add(g.id)}else g.status!=="idle"&&f.add(g.id);const d=new Map;for(const g of e)d.set(g.id,g.status);l.current=d,n(g=>{const C=[...g.filter(S=>h.has(S)&&!f.has(S))];for(const S of p)C.includes(S)||C.push(S);return C.length===g.length&&C.every((S,v)=>S===g[v])?g:C})},[e]),x.useEffect(()=>{t&&n(h=>h.filter(f=>f!==t))},[t]),{pendingCompletionWorkerIds:s}}function r1(e){const[t,s]=x.useState([]),n=x.useCallback(c=>{s(h=>[{worker:c,startedAtMs:Date.now()},...h.filter(f=>f.worker.id!==c.id)])},[]),l=x.useCallback(c=>{s(h=>h.filter(f=>f.worker.id!==c))},[]);return x.useEffect(()=>{const c=setInterval(()=>{const h=Date.now();s(f=>f.filter(p=>h-p.startedAtMs<e))},80);return()=>{clearInterval(c)}},[e]),{fadingWorkers:t,queueWorkerFade:n,removeWorkerFade:l}}const i1=1600,Jc=["move","selected"],s1={move:["move.mp3","move_variant_1.mp3","move_variant_2.mp3","move_variant_3.mp3"],selected:["selected.mp3","selected_variant_1.mp3","selected_variant_2.mp3","selected_variant_3.mp3"]};function n1({config:e,workers:t,workersHydrated:s,selectedWorkerIds:n}){const l=e?.audio.enableSound??!0,c=x.useRef(new Map),h=x.useRef(new Set),f=x.useRef(new Map),p=x.useRef(!1),d=x.useRef(!1),g=x.useRef(new Map),y=x.useRef(new Set),C=x.useRef(new Map),S=x.useRef(new Map),v=x.useRef(new Set),w=x.useRef(null);x.useEffect(()=>{f.current=new Map(t.map(V=>[V.id,V]))},[t]);const E=x.useCallback((V,D)=>`/api/assets/characters/${encodeURIComponent(V)}/voice-lines/${D}.mp3`,[]),L=x.useCallback((V,D)=>`/api/assets/characters/${encodeURIComponent(V)}/voice-lines/${encodeURIComponent(D)}`,[]),j=x.useCallback((V,D)=>{const b=g.current.get(V)?.[D]??[];return b.length>0?b:s1[D].map(I=>L(V,I))},[L]),$=x.useCallback(V=>{if(v.current.has(V))return;v.current.add(V);const D=new Audio(V);D.preload="auto",D.addEventListener("canplay",()=>{S.current.set(V,!0)},{once:!0}),D.addEventListener("error",()=>{S.current.set(V,!1)},{once:!0}),D.load()},[]),W=x.useCallback(V=>{if(!l||S.current.get(V)===!1)return;const D=w.current;D&&(D.pause(),D.currentTime=0);const b=new Audio(V);b.preload="auto",b.addEventListener("canplay",()=>{S.current.set(V,!0)},{once:!0}),b.addEventListener("error",()=>{S.current.set(V,!1)},{once:!0}),w.current=b,b.play().catch(()=>{})},[l]),Q=x.useCallback((V,D)=>{W(E(V.avatarType,D))},[W,E]),K=x.useCallback(V=>{if(!l)return;const D=V.filter(O=>S.current.get(O)===!0),b=V.filter(O=>S.current.get(O)!==!1),I=D.length>0?D:b,N=ep(I);N&&W(N)},[W,l]),Z=x.useCallback(V=>{const D=j(V.avatarType,"selected");K(D)},[K,j]),fe=x.useCallback(V=>{const D=j(V.avatarType,"move");K(D)},[K,j]),oe=x.useCallback(async V=>{try{const D=await fetch(`/api/avatars/${encodeURIComponent(V)}/voice-lines`);if(!D.ok)return;const b=await D.json();if(!Array.isArray(b.files))return;const I=b.files.filter(O=>typeof O=="string"&&O.toLowerCase().endsWith(".mp3")).sort((O,Y)=>O.localeCompare(Y)),N={move:[],selected:[]};for(const O of Jc)N[O]=I.filter(Y=>Y.toLowerCase().startsWith(O)).map(Y=>L(V,Y));g.current.set(V,N);for(const O of Jc)for(const Y of N[O])$(Y)}catch{y.current.delete(V)}},[$,L]);x.useEffect(()=>{const V=Array.from(new Set(t.map(D=>D.avatarType)));for(const D of V)y.current.has(D)||(y.current.add(D),oe(D))},[oe,t]),x.useEffect(()=>{if(!l)return;const V=Array.from(new Set(t.map(b=>b.avatarType))),D=["arrive","attention","complete","death"];for(const b of V){for(const I of D)$(E(b,I));for(const I of Jc)for(const N of j(b,I))$(N)}},[$,E,j,l,t]),x.useEffect(()=>{if(!s)return;const V=new Map(t.map(N=>[N.id,N])),D=c.current,b=C.current,I=performance.now();if(!p.current){p.current=!0,c.current=V;return}for(const N of t){const O=D.get(N.id);if(!O){Q(N,"arrive"),b.set(N.id,I+i1);continue}o1(O.status,N.status)&&Q(N,"attention"),l1(O.status,N.status)&&!c1(N)&&Q(N,"complete")}for(const[N,O]of D.entries())V.has(N)||(Q(O,"death"),b.delete(N));c.current=V},[Q,t,s]),x.useEffect(()=>{if(!s)return;const V=new Set(n),D=h.current;if(!d.current){d.current=!0,h.current=V;return}const b=n.filter(B=>!D.has(B)),I=C.current,N=performance.now(),O=b.filter(B=>{const F=I.get(B);return F===void 0?!0:F<=N?(I.delete(B),!0):!1}),Y=ep(O);if(Y){const B=f.current.get(Y);B&&Z(B)}h.current=V},[Z,n,s]);const he=x.useCallback(V=>{const D=f.current.get(V);D&&fe(D)},[fe]);return x.useEffect(()=>()=>{const V=w.current;V&&(V.pause(),V.currentTime=0,w.current=null)},[]),{playMoveVoiceLine:he}}function o1(e,t){return e!=="attention"&&t==="attention"}function l1(e,t){return e==="working"&&t==="idle"}const a1=1e4;function c1(e){return Date.now()-new Date(e.createdAt).getTime()<a1}function ep(e){if(e.length===0)return;const t=Math.floor(Math.random()*e.length);return e[t]}function h1({workers:e,activeWorkers:t,renameModalOpen:s,setRenameModalOpen:n,renameTargetWorkerIds:l,setRenameTargetWorkerIds:c,killConfirmWorkerIds:h,setKillConfirmWorkerIds:f,setRenameDraft:p}){const d=x.useMemo(()=>{if(l.length===0)return[];const v=new Map(e.map(w=>[w.id,w]));return l.map(w=>v.get(w)).filter(w=>!!w)},[l,e]),g=x.useMemo(()=>{if(h.length===0)return[];const v=new Map(e.map(w=>[w.id,w]));return h.map(w=>v.get(w)).filter(w=>!!w)},[h,e]),y=x.useCallback(()=>{n(!1),c([])},[n,c]),C=x.useCallback(()=>{f([])},[f]),S=x.useCallback(v=>{v.length!==0&&(p(v.length===1?v[0].displayName??v[0].name:""),c(v.map(w=>w.id)),n(!0))},[p,n,c]);return x.useEffect(()=>{if(!s||l.length===0)return;const v=new Set(t.map(w=>w.id));l.some(w=>v.has(w))||y()},[t,y,s,l]),x.useEffect(()=>{if(h.length===0)return;const v=new Set(t.map(w=>w.id));h.some(w=>v.has(w))||C()},[t,C,h]),{renameTargetWorkers:d,killConfirmWorkers:g,closeRenameModal:y,closeKillConfirm:C,openRenameForWorkers:S}}function u1({workers:e,selectedWorkerIds:t,setSelectedWorkerIds:s,rosterEntries:n,rosterActiveIndex:l,setKillConfirmWorkerIds:c,closeKillConfirm:h,killConfirmWorkerIds:f,queueWorkerFade:p,removeWorkerFade:d,setWorkers:g,showError:y}){const C=x.useCallback(async E=>{const L=e.find(j=>j.id===E);if(L){h(),p(L);try{const j=await Vw(E);g($=>$.filter(W=>W.id!==j.workerId)),s($=>$.filter(W=>W!==j.workerId))}catch(j){d(E),y(j)}}},[h,p,d,s,g,y,e]),S=x.useCallback(()=>{t.length!==0&&c(t)},[t,c]),v=x.useCallback(()=>{const E=n[l];!E||E.kind!=="worker"||c([E.worker.id])},[l,n,c]),w=x.useCallback(()=>{if(f.length===0)return;const E=[...f];h();for(const L of E)C(L)},[h,f,C]);return{onKillSelected:S,onKillRosterActive:v,confirmKillSelection:w}}function d1({setWorkers:e,selectedWorkers:t,terminalWorkerId:s,applySelection:n,setSpawnDialogOpen:l,setPaletteOpen:c,renameTargetWorkerIds:h,closeRenameModal:f,showError:p}){const d=x.useCallback(async v=>{try{const w=t.map(j=>j.id),E="shortcutIndex"in v&&w.length>0?{...v,spawnNearWorkerIds:w}:v,L=await Uw(E);e(j=>tn(j,L)),n([L.id],{center:!0}),l(!1),c(!1)}catch(w){p(w)}},[n,t,c,l,e,p]),g=x.useCallback(async v=>{const w=[...h];if(w.length===0){f();return}try{if(w.length===1){const E=await J_(w[0],v);e(L=>tn(L,E))}else{const E=v.trim(),L=await Promise.all(w.map((j,$)=>J_(j,E.length>0?`${E} ${$+1}`:"")));e(j=>{let $=j;for(const W of L)$=tn($,W);return $})}f()}catch(E){p(E)}},[f,h,e,p]),y=x.useCallback(async()=>{if(t.length===0)return;const v=t.every(w=>w.movementMode==="hold")?"wander":"hold";try{const w=await Promise.all(t.map(E=>qw(E.id,v)));e(E=>{let L=E;for(const j of w)L=tn(L,j);return L})}catch(w){p(w)}},[t,e,p]),C=x.useCallback(async()=>{if(s)try{await Xw(s)}catch(v){p(v)}},[p,s]),S=x.useCallback((v,w)=>{Yw(v,w.x,w.y).then(E=>{e(L=>tn(L,E))}).catch(E=>{p(E)})},[e,p]);return{runSpawn:d,submitRename:g,onToggleMovementModeSelected:y,onOpenSelectedInTerminal:C,onPositionCommit:S}}function f1({selectedWorkers:e,rallyCommandDraft:t,setRallyCommandDraft:s,rallyCommandSending:n,setRallyCommandSending:l,rallyCommandResultText:c,setRallyCommandResultText:h,showError:f}){const p=x.useCallback(async()=>{if(n)return;const g=e.map(y=>y.id);if(!(g.length<=1)){if(t.length===0){h("Enter a command to broadcast.");return}l(!0),h(void 0);try{const C=t.includes("$NAME")?iv(await Promise.all(e.map(async S=>{const v=t.replace(/\$NAME/g,S.displayName??S.name);try{return await Z_([S.id],v,!0)}catch(w){return{requestedCount:1,deliveredWorkerIds:[],skippedWorkerIds:[],failed:[{workerId:S.id,error:w instanceof Error?w.message:"Failed to send input"}]}}}))):await Z_(g,t,!0);s(""),h(rv(C))}catch(y){f(y)}finally{l(!1)}}},[t,n,e,s,h,l,f]),d=x.useCallback(g=>{s(g),c&&h(void 0)},[c,s,h]);return{onSendRallyCommand:p,onRallyCommandDraftChange:d}}function _1({workers:e,activeWorkers:t,setWorkers:s,selectedWorkers:n,selectedWorkerIds:l,setSelectedWorkerIds:c,focusedSelectedWorkerId:h,terminalWorkerId:f,rosterEntries:p,rosterActiveIndex:d,setRosterActiveIndex:g,applySelection:y,setSpawnDialogOpen:C,setPaletteOpen:S,renameModalOpen:v,setRenameModalOpen:w,renameTargetWorkerIds:E,setRenameTargetWorkerIds:L,setRenameDraft:j,killConfirmWorkerIds:$,setKillConfirmWorkerIds:W,rallyCommandDraft:Q,setRallyCommandDraft:K,rallyCommandSending:Z,setRallyCommandSending:fe,rallyCommandResultText:oe,setRallyCommandResultText:he,queueWorkerFade:V,removeWorkerFade:D,setErrorText:b}){const I=x.useCallback(Fe=>{b(Fe instanceof Error?Fe.message:"Unknown request failure")},[b]),{renameTargetWorkers:N,killConfirmWorkers:O,closeRenameModal:Y,closeKillConfirm:B,openRenameForWorkers:F}=h1({workers:e,activeWorkers:t,renameModalOpen:v,setRenameModalOpen:w,renameTargetWorkerIds:E,setRenameTargetWorkerIds:L,killConfirmWorkerIds:$,setKillConfirmWorkerIds:W,setRenameDraft:j}),{runSpawn:J,submitRename:R,onToggleMovementModeSelected:U,onOpenSelectedInTerminal:le,onPositionCommit:ie}=d1({setWorkers:s,selectedWorkers:n,terminalWorkerId:f,applySelection:y,setSpawnDialogOpen:C,setPaletteOpen:S,renameTargetWorkerIds:E,closeRenameModal:Y,showError:I}),{onKillSelected:ue,onKillRosterActive:_e,confirmKillSelection:Re}=u1({workers:e,selectedWorkerIds:l,setSelectedWorkerIds:c,rosterEntries:p,rosterActiveIndex:d,setKillConfirmWorkerIds:W,closeKillConfirm:B,killConfirmWorkerIds:$,queueWorkerFade:V,removeWorkerFade:D,setWorkers:s,showError:I}),{onSendRallyCommand:ge,onRallyCommandDraftChange:ke}=f1({selectedWorkers:n,rallyCommandDraft:Q,setRallyCommandDraft:K,rallyCommandSending:Z,setRallyCommandSending:fe,rallyCommandResultText:oe,setRallyCommandResultText:he,showError:I}),Ae=x.useCallback(Fe=>{const be=p[Fe];if(be){if(g(Fe),be.kind==="worker"){y([be.worker.id],{center:!0});return}J({shortcutIndex:be.shortcutIndex})}},[y,p,J,g]),Ye=x.useCallback(()=>{if(n.length===0)return;const Fe=n.length>1?n.find(be=>be.id===h):void 0;F(Fe?[Fe]:n)},[h,F,n]);return{renameTargetWorkers:N,killConfirmWorkers:O,runSpawn:J,closeRenameModal:Y,closeKillConfirm:B,openRenameForWorkers:F,submitRename:R,onKillSelected:ue,onKillRosterActive:_e,confirmKillSelection:Re,onToggleMovementModeSelected:U,onActivateRosterIndex:Ae,onOpenSelectedInTerminal:le,onSendRallyCommand:ge,onRallyCommandDraftChange:ke,onRenameSelected:Ye,onPositionCommit:ie}}function p1(e){const t=[];return e.forEach((s,n)=>{for(const l of s.hotkeys??[]){const c=v1(l);c&&t.push({shortcutIndex:n,hotkey:c})}}),t}function m1(e,t){const s=[],n=new Set;for(const l of e)g1(l.hotkey,t)&&(n.has(l.shortcutIndex)||(n.add(l.shortcutIndex),s.push(l.shortcutIndex)));return s}function g1(e,t){return t.ctrlKey!==e.ctrl||t.metaKey!==e.meta||t.altKey!==e.alt||t.shiftKey!==e.shift?!1:w1(t.key)===e.key?!0:e.code?t.code===e.code:!1}function v1(e){const t=y1(e);if(t.length===0)return;let s=!1,n=!1,l=!1,c=!1,h;for(const p of t){const d=p.trim().toLowerCase();if(d){if(d==="ctrl"||d==="control"){s=!0;continue}if(d==="cmd"||d==="meta"||d==="super"){n=!0;continue}if(d==="alt"||d==="option"){l=!0;continue}if(d==="shift"){c=!0;continue}if(h)return;h=p}}if(!h)return;const f=S1(h);if(f)return{key:f.key,code:f.code,ctrl:s,meta:n,alt:l,shift:c}}function y1(e){const t=e.trim().replace(/\s+/g,"");if(!t)return[];if(t.includes("+"))return t.split("+").filter(l=>l.length>0);const s=t.toLowerCase();return s.includes("ctrl-")||s.includes("control-")||s.includes("cmd-")||s.includes("meta-")||s.includes("super-")||s.includes("alt-")||s.includes("option-")||s.includes("shift-")?t.split("-").filter(l=>l.length>0):[t]}function S1(e){const t=e.trim();if(!t)return;const s=t.toLowerCase();if(s==="space"||s==="spacebar")return{key:" ",code:"Space"};if(s==="esc")return{key:"escape",code:"Escape"};if(s==="return")return{key:"enter",code:"Enter"};if(s==="up")return{key:"arrowup",code:"ArrowUp"};if(s==="down")return{key:"arrowdown",code:"ArrowDown"};if(s==="left")return{key:"arrowleft",code:"ArrowLeft"};if(s==="right")return{key:"arrowright",code:"ArrowRight"};if(/^key[a-z]$/.test(s)){const n=s.slice(3);return{key:n,code:`Key${n.toUpperCase()}`}}if(/^digit[0-9]$/.test(s)){const n=s.slice(5);return{key:n,code:`Digit${n}`}}if(/^numpad[0-9]$/.test(s)){const n=s.slice(6);return{key:n,code:`Numpad${n}`}}if(/^f[0-9]{1,2}$/.test(s))return{key:s,code:s.toUpperCase()};if(t.length===1){if(/^[a-z]$/i.test(t)){const n=t.toLowerCase();return{key:n,code:`Key${n.toUpperCase()}`}}return/^[0-9]$/.test(t)?{key:t}:{key:t}}return{key:s}}function w1(e){const t=e.toLowerCase();return t==="spacebar"?" ":t}function C1(){const[e,t]=x.useState(!1),[s,n]=x.useState(!1),[l,c]=x.useState(!1),[h,f]=x.useState(!1),[p,d]=x.useState([]),[g,y]=x.useState(""),[C,S]=x.useState([]),[v,w]=x.useState(""),[E,L]=x.useState(!1),[j,$]=x.useState(void 0),[W,Q]=x.useState(void 0),K=x.useRef(null),{config:Z,workers:fe,setWorkers:oe,workersHydrated:he}=Qw(Q),{fadingWorkers:V,queueWorkerFade:D,removeWorkerFade:b}=r1(qg),I=x.useMemo(()=>fe.filter(ne=>ne.status!=="stopped"),[fe]),{controlGroups:N,setControlGroups:O,controlGroupByDigitRef:Y,mapColumnRatio:B,setMapColumnRatio:F,nudgeMapColumnRatio:J,resetMapColumnRatio:R}=Jw(I,he),U=x.useRef(null),le=x.useRef(void 0),[ie,ue]=x.useState(!1),_e=x.useMemo(()=>Z?.shortcuts??[],[Z]),Re=x.useMemo(()=>p1(_e),[_e]),ge=x.useMemo(()=>[...I.map(ne=>({kind:"worker",worker:ne})),..._e.map((ne,De)=>({kind:"shortcut",shortcut:ne,shortcutIndex:De}))],[I,_e]),ke=x.useMemo(()=>{const ne=ge.findIndex(De=>De.kind==="shortcut");return ne>=0?ne:void 0},[ge]),Ae=x.useCallback(()=>{w(""),$(void 0)},[]),{selectedWorkerIds:Ye,setSelectedWorkerIds:Fe,selectedWorkerId:be,selectedWorkers:et,mapCenterToken:ni,mapCenterWorkerId:it,terminalFocusToken:dr,rosterActiveIndex:Ft,setRosterActiveIndex:at,selectedGroupActiveIndex:qt,setSelectedGroupActiveIndex:Nt,focusedSelectedWorkerId:fr,setFocusedSelectedWorkerId:Ci,applySelection:ki,requestTerminalFocus:bi,onSelectWorker:Ji,onSelectionChange:xi,onActivateWorker:Tr,cycleSelection:Oe,cycleIdleSelection:oi,cycleSelectedGroupFocus:Fr}=Zw(I,ge,Ae),rr=x.useMemo(()=>I.find(ne=>ne.id===be),[I,be]),Pr=x.useMemo(()=>{if(fr)return et.find(ne=>ne.id===fr)},[fr,et]),Ei=rr??(et.length>1?Pr:void 0),_r=Ei?.id,pr=et.length>1&&!Ei,Hr=e1(_r),{pendingCompletionWorkerIds:Lr}=t1({workers:I,reviewedWorkerId:_r}),{playMoveVoiceLine:jr}=n1({config:Z,workers:I,workersHydrated:he,selectedWorkerIds:Ye}),Zi=x.useCallback(()=>{const ne=document.activeElement;return!(ne instanceof HTMLElement)||!ne.closest(".terminal-panel")?!1:(ne.blur(),document.querySelector(".map-canvas")?.focus(),!0)},[]),$r=x.useCallback(()=>{const ne=K.current;if(!ne)return!1;ne.focus();const De=ne.value.length;return ne.setSelectionRange(De,De),!0},[]),ir=x.useCallback(ne=>{const De=U.current;if(!De)return;const Ge=De.getBoundingClientRect(),$e=Math.max(1,Ge.width-Bc),sr=ne-Ge.left-Bc/2,Me=vi(sr/$e,no,oo);F(Me)},[F]);x.useEffect(()=>{if(ie)return document.body.classList.add("split-pane-dragging"),()=>{document.body.classList.remove("split-pane-dragging")}},[ie]);const{renameTargetWorkers:Kr,killConfirmWorkers:Es,runSpawn:Ur,closeRenameModal:es,closeKillConfirm:ts,openRenameForWorkers:rs,submitRename:Ms,onKillSelected:Vr,onKillRosterActive:is,confirmKillSelection:mr,onToggleMovementModeSelected:Rs,onActivateRosterIndex:q,onOpenSelectedInTerminal:X,onSendRallyCommand:se,onRallyCommandDraftChange:de,onRenameSelected:ye,onPositionCommit:ze}=_1({workers:fe,activeWorkers:I,setWorkers:oe,selectedWorkers:et,selectedWorkerIds:Ye,setSelectedWorkerIds:Fe,focusedSelectedWorkerId:fr,terminalWorkerId:_r,rosterEntries:ge,rosterActiveIndex:Ft,setRosterActiveIndex:at,applySelection:ki,setSpawnDialogOpen:t,setPaletteOpen:n,renameModalOpen:h,setRenameModalOpen:f,renameTargetWorkerIds:C,setRenameTargetWorkerIds:S,setRenameDraft:y,killConfirmWorkerIds:p,setKillConfirmWorkerIds:d,rallyCommandDraft:v,setRallyCommandDraft:w,rallyCommandSending:E,setRallyCommandSending:L,rallyCommandResultText:j,setRallyCommandResultText:$,queueWorkerFade:D,removeWorkerFade:b,setErrorText:Q});return jw({activeWorkers:I,applySelection:ki,clampNumber:vi,closeKillConfirm:ts,closeRenameModal:es,confirmKillSelection:mr,controlGroupByDigitRef:Y,cycleIdleSelection:oi,cycleSelectedGroupFocus:Fr,cycleSelection:Oe,escapeTerminalFocus:Zi,findMatchingShortcutIndexes:m1,firstSummonEntryIndex:ke,focusRallyCommandInput:$r,focusedSelectedWorkerId:fr,inSelectedGroupView:pr,isEditableTarget:jh,isTerminalEscapeShortcut:nv,isTerminalTarget:Zg,killConfirmWorkerIds:p,mapColumnRatioStep:Hh,nudgeMapColumnRatio:J,onActivateRosterIndex:q,onKillRosterActive:is,onKillSelected:Vr,onToggleMovementModeSelected:Rs,openRenameForWorkers:rs,paletteOpen:s,parseControlGroupDigit:sv,renameModalOpen:h,requestTerminalFocus:bi,resetMapColumnRatio:R,rosterActiveIndex:Ft,rosterEntries:ge,runSpawn:Ur,selectedGroupActiveIndex:qt,selectedWorkerId:be,selectedWorkerIds:Ye,selectedWorkers:et,setControlGroups:O,setFocusedSelectedWorkerId:Ci,setPaletteOpen:n,setRosterActiveIndex:at,setSelectedGroupActiveIndex:Nt,setShortcutsOverlayOpen:c,setSpawnDialogOpen:t,shortcutHotkeyBindings:Re,shortcutsOverlayOpen:l,spawnDialogOpen:e}),T.jsxs("div",{ref:U,className:"app-shell",style:{gridTemplateColumns:`minmax(0px, ${B.toFixed(3)}fr) ${Bc}px minmax(0px, ${(1-B).toFixed(3)}fr)`},children:[T.jsxs("div",{className:"map-column",children:[T.jsx(v0,{workers:I,fadingWorkers:V,selectedWorkerId:be,selectedWorkerIds:Ye,focusedSelectedWorkerId:fr,terminalFocusedSelected:!!(be&&Hr),terminalFocusedWorkerId:Hr?_r:void 0,controlGroups:N,completionPendingWorkerIds:Lr,onSelect:Ji,onSelectionChange:xi,onActivateWorker:Tr,onMoveOrderIssued:jr,onPositionCommit:ze,centerOnWorkerId:it,centerRequestKey:ni}),T.jsx(ov,{shortcuts:Z?.shortcuts??[],selectedWorker:rr,selectedWorkers:et,onSpawnShortcut:ne=>{Ur({shortcutIndex:ne})},onOpenSpawnDialog:()=>{t(!0),n(!1)},onOpenPalette:()=>{n(!0),t(!1)},onDeselect:()=>Ji(void 0),onKillSelected:()=>{Vr()},onRenameSelected:()=>{ye()},onToggleMovementMode:()=>{Rs()}})]}),T.jsx("div",{className:`layout-divider${ie?" layout-divider-active":""}`,role:"separator","aria-label":"Resize map and terminal columns","aria-orientation":"vertical",onPointerDown:ne=>{ne.button===0&&(ne.preventDefault(),le.current=ne.pointerId,ue(!0),ne.currentTarget.setPointerCapture(ne.pointerId),ir(ne.clientX))},onPointerMove:ne=>{le.current===ne.pointerId&&(ne.preventDefault(),ir(ne.clientX))},onPointerUp:ne=>{le.current===ne.pointerId&&(ne.preventDefault(),ne.currentTarget.hasPointerCapture(ne.pointerId)&&ne.currentTarget.releasePointerCapture(ne.pointerId),le.current=void 0,ue(!1))},onPointerCancel:ne=>{le.current===ne.pointerId&&(ne.currentTarget.hasPointerCapture(ne.pointerId)&&ne.currentTarget.releasePointerCapture(ne.pointerId),le.current=void 0,ue(!1))}}),T.jsx(zw,{activeWorkers:I,selectedWorkers:et,terminalWorker:Ei,terminalFocused:Hr,selectedGroupActiveIndex:qt,setSelectedGroupActiveIndex:Nt,setFocusedSelectedWorkerId:Ci,rallyCommandInputRef:K,rallyCommandDraft:v,rallyCommandSending:E,rallyCommandResultText:j,onRallyCommandDraftChange:de,onSendRallyCommand:se,rosterEntries:ge,completionPendingWorkerIds:Lr,rosterActiveIndex:Ft,setRosterActiveIndex:at,onActivateRosterIndex:q,onOpenSelectedInTerminal:X,terminalFocusToken:dr}),Z?T.jsx(w0,{open:e,projects:Z.projects,runtimes:Z.runtimes,onClose:()=>t(!1),onSpawn:(ne,De)=>{Ur({projectId:ne,runtimeId:De})}}):null,Z?T.jsx(lv,{open:s,config:Z,onClose:()=>n(!1),onSpawnShortcut:ne=>{Ur({shortcutIndex:ne})},onSpawnProjectRuntime:(ne,De)=>{Ur({projectId:ne,runtimeId:De})}}):null,T.jsx(S0,{open:l,onClose:()=>{c(!1)}}),T.jsx(av,{workerIds:p,workers:Es,onClose:ts,onConfirm:mr}),T.jsx(y0,{open:h,targetWorkerIds:C,targetWorkers:Kr,initialDraft:g,onClose:es,onSubmit:Ms}),W?T.jsx("div",{className:"error-toast",onClick:()=>Q(void 0),children:W}):null]})}const Zp=document.getElementById("root");if(!Zp)throw new Error("Missing #root container");Yg.createRoot(Zp).render(T.jsx(x.StrictMode,{children:T.jsx(C1,{})}));
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Arcane Agents</title>
7
- <script type="module" crossorigin src="/assets/index-B0hs4377.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-CR1vBG8R.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-TxsheMVB.css">
9
9
  </head>
10
10
  <body>
@@ -30,10 +30,10 @@ async function resolveTranscriptPath({ worker, state, paneCurrentPath, nowMs })
30
30
  return directPath;
31
31
  }
32
32
  }
33
- const latest = await findLatestTranscriptFile(transcriptDir, nowMs);
34
- if (latest) {
33
+ const match = await findMatchingTranscriptFile(transcriptDir, nowMs, state.claudeSessionStartAtMs);
34
+ if (match) {
35
35
  state.nextTranscriptLookupAtMs = 0;
36
- return latest;
36
+ return match;
37
37
  }
38
38
  }
39
39
  state.nextTranscriptLookupAtMs = nowMs + constants_1.transcriptLookupRetryMs;
@@ -68,31 +68,70 @@ function buildTranscriptCandidateDirs(workerProjectPath, paneCurrentPath) {
68
68
  }
69
69
  return [...unique];
70
70
  }
71
- async function findLatestTranscriptFile(directoryPath, nowMs) {
72
- let latestPath;
73
- let latestMtimeMs = 0;
71
+ const sessionMatchWindowMs = 10_000;
72
+ async function findMatchingTranscriptFile(directoryPath, nowMs, claudeSessionStartAtMs) {
74
73
  const entries = await promises_1.default.readdir(directoryPath, { withFileTypes: true });
75
- for (const entry of entries) {
76
- if (!entry.isFile() || !entry.name.endsWith(".jsonl")) {
77
- continue;
78
- }
74
+ const jsonlEntries = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl"));
75
+ if (jsonlEntries.length === 0) {
76
+ return undefined;
77
+ }
78
+ const candidates = (await Promise.all(jsonlEntries.map(async (entry) => {
79
79
  const fullPath = node_path_1.default.join(directoryPath, entry.name);
80
80
  const stats = await promises_1.default.stat(fullPath);
81
- if (!stats.isFile()) {
81
+ if (nowMs - stats.mtimeMs > constants_1.maxRecentTranscriptAgeMs) {
82
+ return undefined;
83
+ }
84
+ return { fullPath, mtimeMs: stats.mtimeMs, firstRecordTimestampMs: undefined };
85
+ }))).filter((c) => c !== undefined);
86
+ if (candidates.length === 0) {
87
+ return undefined;
88
+ }
89
+ if (claudeSessionStartAtMs) {
90
+ await Promise.all(candidates.map(async (candidate) => {
91
+ candidate.firstRecordTimestampMs = await readFirstRecordTimestamp(candidate.fullPath);
92
+ }));
93
+ const matched = findClosestByStartTime(candidates, claudeSessionStartAtMs);
94
+ if (matched) {
95
+ return matched;
96
+ }
97
+ }
98
+ candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
99
+ return candidates[0]?.fullPath;
100
+ }
101
+ function findClosestByStartTime(candidates, targetMs) {
102
+ let bestPath;
103
+ let bestDistance = Infinity;
104
+ for (const candidate of candidates) {
105
+ if (candidate.firstRecordTimestampMs === undefined) {
82
106
  continue;
83
107
  }
84
- if (stats.mtimeMs > latestMtimeMs) {
85
- latestMtimeMs = stats.mtimeMs;
86
- latestPath = fullPath;
108
+ const distance = Math.abs(candidate.firstRecordTimestampMs - targetMs);
109
+ if (distance <= sessionMatchWindowMs && distance < bestDistance) {
110
+ bestDistance = distance;
111
+ bestPath = candidate.fullPath;
87
112
  }
88
113
  }
89
- if (!latestPath) {
90
- return undefined;
114
+ return bestPath;
115
+ }
116
+ async function readFirstRecordTimestamp(filePath) {
117
+ try {
118
+ const chunk = await readFileRange(filePath, 0, 4096);
119
+ const newlineIndex = chunk.indexOf("\n");
120
+ const firstLine = newlineIndex >= 0 ? chunk.slice(0, newlineIndex) : chunk;
121
+ if (!firstLine.trim()) {
122
+ return undefined;
123
+ }
124
+ const record = JSON.parse(firstLine);
125
+ const timestamp = record.timestamp;
126
+ if (typeof timestamp === "string") {
127
+ const parsed = Date.parse(timestamp);
128
+ return Number.isFinite(parsed) ? parsed : undefined;
129
+ }
91
130
  }
92
- if (nowMs - latestMtimeMs > constants_1.maxRecentTranscriptAgeMs) {
131
+ catch {
93
132
  return undefined;
94
133
  }
95
- return latestPath;
134
+ return undefined;
96
135
  }
97
136
  async function readFileRange(filePath, startOffset, length) {
98
137
  if (length <= 0) {
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findClaudeSessionStartTimeMs = findClaudeSessionStartTimeMs;
4
+ const node_child_process_1 = require("node:child_process");
5
+ const node_util_1 = require("node:util");
6
+ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
7
+ const maxProcessTreeDepth = 4;
8
+ async function findClaudeSessionStartTimeMs(panePid) {
9
+ const claudePid = await findClaudeChildPid(panePid, 0);
10
+ if (!claudePid) {
11
+ return undefined;
12
+ }
13
+ return getProcessStartTimeMs(claudePid);
14
+ }
15
+ async function findClaudeChildPid(parentPid, depth) {
16
+ if (depth >= maxProcessTreeDepth) {
17
+ return undefined;
18
+ }
19
+ try {
20
+ const { stdout } = await execFileAsync("pgrep", ["-P", String(parentPid)], {
21
+ maxBuffer: 1024 * 16
22
+ });
23
+ const childPids = stdout
24
+ .trim()
25
+ .split("\n")
26
+ .map((line) => Number.parseInt(line.trim(), 10))
27
+ .filter((pid) => Number.isFinite(pid) && pid > 0);
28
+ for (const childPid of childPids) {
29
+ if (await isClaudeProcess(childPid)) {
30
+ return childPid;
31
+ }
32
+ const nestedClaude = await findClaudeChildPid(childPid, depth + 1);
33
+ if (nestedClaude) {
34
+ return nestedClaude;
35
+ }
36
+ }
37
+ }
38
+ catch {
39
+ return undefined;
40
+ }
41
+ return undefined;
42
+ }
43
+ async function isClaudeProcess(pid) {
44
+ try {
45
+ const { stdout } = await execFileAsync("ps", ["-o", "comm=", "-p", String(pid)], {
46
+ maxBuffer: 1024 * 4
47
+ });
48
+ return stdout.trim().toLowerCase() === "claude";
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ }
54
+ async function getProcessStartTimeMs(pid) {
55
+ try {
56
+ const { stdout } = await execFileAsync("ps", ["-o", "etimes=", "-p", String(pid)], {
57
+ maxBuffer: 1024 * 4
58
+ });
59
+ const elapsedSeconds = Number.parseInt(stdout.trim(), 10);
60
+ if (!Number.isFinite(elapsedSeconds) || elapsedSeconds < 0) {
61
+ return undefined;
62
+ }
63
+ return Date.now() - elapsedSeconds * 1000;
64
+ }
65
+ catch {
66
+ return undefined;
67
+ }
68
+ }
@@ -4,16 +4,22 @@ exports.ClaudeTranscriptTracker = void 0;
4
4
  const accumulator_1 = require("./claudeTranscript/accumulator");
5
5
  const io_1 = require("./claudeTranscript/io");
6
6
  const parser_1 = require("./claudeTranscript/parser");
7
+ const process_1 = require("./claudeTranscript/process");
7
8
  const snapshot_1 = require("./claudeTranscript/snapshot");
8
9
  const sessionDetection_1 = require("./runtime/sessionDetection");
10
+ const failedSessionLookupSentinel = 0;
9
11
  class ClaudeTranscriptTracker {
10
12
  states = new Map();
11
- async poll(worker, paneCurrentCommand, paneCurrentPath) {
13
+ async poll(worker, paneCurrentCommand, paneCurrentPath, panePid) {
12
14
  if (!(0, sessionDetection_1.isLikelyClaudeSession)(worker, paneCurrentCommand.toLowerCase())) {
13
15
  this.states.delete(worker.id);
14
16
  return undefined;
15
17
  }
16
18
  const state = this.getState(worker.id);
19
+ if (panePid && state.claudeSessionStartAtMs === undefined) {
20
+ const startTime = await (0, process_1.findClaudeSessionStartTimeMs)(panePid).catch(() => undefined);
21
+ state.claudeSessionStartAtMs = startTime ?? failedSessionLookupSentinel;
22
+ }
17
23
  let transcriptPath;
18
24
  try {
19
25
  transcriptPath = await (0, io_1.resolveTranscriptPath)({
@@ -34,9 +40,13 @@ class ClaudeTranscriptTracker {
34
40
  (0, accumulator_1.resetTranscriptState)(state);
35
41
  }
36
42
  try {
43
+ const wasInitialized = state.initialized;
37
44
  const lines = await (0, io_1.collectTranscriptInputLines)(state);
38
45
  const records = (0, parser_1.extractTranscriptRecords)(lines);
39
46
  (0, accumulator_1.applyParsedTranscriptRecords)(state, records);
47
+ if (!wasInitialized && state.initialized) {
48
+ state.busyUntilMs = 0;
49
+ }
40
50
  }
41
51
  catch {
42
52
  return undefined;
@@ -12,7 +12,9 @@ function collectWorkingEvidence(context, hasRecoverableParserError) {
12
12
  const activityToolCandidates = [];
13
13
  const activityPathCandidates = [];
14
14
  const suppressShellHistorySignals = (0, helpers_1.shouldSuppressShellHistorySignals)(context);
15
+ const transcriptIsIdle = context.transcriptSnapshot !== undefined && context.transcriptSnapshot.status !== "working";
15
16
  const parsedStrongSignal = !suppressShellHistorySignals &&
17
+ !transcriptIsIdle &&
16
18
  (Boolean(context.parsed.activity.filePath) ||
17
19
  (Boolean(context.parsed.activity.tool) && context.parsed.activity.tool !== "terminal"));
18
20
  if (context.transcriptSnapshot?.status === "working") {
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.observePane = observePane;
4
+ const initialObservationAgeMs = 30_000;
4
5
  function observePane(observations, workerId, currentCommand, output) {
5
6
  const now = Date.now();
6
7
  const signature = outputSignature(output);
@@ -8,9 +9,9 @@ function observePane(observations, workerId, currentCommand, output) {
8
9
  if (!existing) {
9
10
  const initial = {
10
11
  lastCommand: currentCommand,
11
- lastCommandChangeAtMs: now,
12
+ lastCommandChangeAtMs: now - initialObservationAgeMs,
12
13
  lastOutputSignature: signature,
13
- lastOutputChangeAtMs: now
14
+ lastOutputChangeAtMs: now - initialObservationAgeMs
14
15
  };
15
16
  observations.set(workerId, initial);
16
17
  return initial;
@@ -13,7 +13,7 @@ async function collectWorkerStatusSignals({ worker, tmux, paneObservation, claud
13
13
  }
14
14
  const [output, transcriptSnapshot] = await Promise.all([
15
15
  tmux.capturePane(worker.tmuxRef, (0, runtimeSignals_1.capturePaneLineCount)(worker, paneState.currentCommand.toLowerCase())),
16
- claudeTranscript.poll(worker, paneState.currentCommand, paneState.currentPath)
16
+ claudeTranscript.poll(worker, paneState.currentCommand, paneState.currentPath, paneState.panePid)
17
17
  ]);
18
18
  const observation = (0, paneObservation_1.observePane)(paneObservation, worker.id, paneState.currentCommand, output);
19
19
  return {
@@ -207,13 +207,15 @@ class TmuxAdapter {
207
207
  "-t",
208
208
  this.target(ref),
209
209
  "-F",
210
- "#{pane_current_command}\t#{pane_dead}\t#{pane_current_path}"
210
+ "#{pane_current_command}\t#{pane_dead}\t#{pane_current_path}\t#{pane_pid}"
211
211
  ]);
212
- const [currentCommand = "", deadFlag = "0", currentPath = ""] = firstLine(output).split("\t");
212
+ const [currentCommand = "", deadFlag = "0", currentPath = "", panePidRaw = ""] = firstLine(output).split("\t");
213
+ const panePid = Number.parseInt(panePidRaw, 10);
213
214
  return {
214
215
  currentCommand,
215
216
  isDead: deadFlag === "1",
216
- currentPath: currentPath.trim().length > 0 ? currentPath : undefined
217
+ currentPath: currentPath.trim().length > 0 ? currentPath : undefined,
218
+ panePid: Number.isFinite(panePid) && panePid > 0 ? panePid : undefined
217
219
  };
218
220
  }
219
221
  async hasSession() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-agents",
3
- "version": "0.1.0",
3
+ "version": "1.0.1",
4
4
  "description": "Local-first visual control room for tmux-backed coding agents",
5
5
  "bin": {
6
6
  "arcane-agents": "dist/server/server/cli.js"