arcane-agents 1.0.1 → 1.0.2

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
@@ -226,6 +226,7 @@ arcane-agents config edit # open config.yaml in $VISUAL/$EDITOR
226
226
  - `shortcuts`: saved `project + runtime` combinations; can also include hotkeys.
227
227
  - `discovery`: optional auto-discovery rules for additional projects.
228
228
  - `avatars`: avatar selection settings (for example disabling specific avatar types from random allocation).
229
+ - `status`: status detection settings (for example interactive command filtering).
229
230
  - `audio`: client sound settings.
230
231
  - `backend.tmux`: tmux session and status poll settings.
231
232
  - `server`: API bind host/port.
@@ -404,6 +405,39 @@ backend:
404
405
  pollIntervalMs: 2500
405
406
  ```
406
407
 
408
+ ### `status`
409
+
410
+ Controls how Arcane Agents detects agent activity.
411
+
412
+ - `interactiveCommands`: programs where terminal output changes are user-driven
413
+ (scrolling, status bar updates, etc.) and should not trigger `working`/`idle`
414
+ transitions. Setting this replaces the defaults entirely.
415
+ - `extraInteractiveCommands`: additional commands to add to the default list
416
+ without replacing it.
417
+
418
+ Default interactive commands: `nvim`, `vim`, `vi`, `nano`, `helix`, `hx`,
419
+ `emacs`, `emacsclient`, `less`, `more`, `man`, `htop`, `btop`, `top`, `watch`,
420
+ `lazygit`, `lazydocker`, `ranger`, `nnn`, `lf`, `yazi`, `tmux`.
421
+
422
+ Example — replace defaults entirely:
423
+
424
+ ```yaml
425
+ status:
426
+ interactiveCommands:
427
+ - nvim
428
+ - vim
429
+ - my-custom-editor
430
+ ```
431
+
432
+ Example — extend defaults with extra commands:
433
+
434
+ ```yaml
435
+ status:
436
+ extraInteractiveCommands:
437
+ - my-custom-editor
438
+ - my-other-tool
439
+ ```
440
+
407
441
  ### `audio`
408
442
 
409
443
  - `enableSound`: enable or disable in-app voice/sound playback (default `true`).
@@ -67,6 +67,28 @@ shortcuts:
67
67
  # type: worktrees
68
68
  # path: ~/code/my-app
69
69
 
70
+ # Status detection settings.
71
+ #
72
+ # interactiveCommands: programs where terminal output changes are user-driven
73
+ # (scrolling, status bars, etc.) and should not trigger working/idle transitions.
74
+ # Set this to replace defaults entirely, or use extraInteractiveCommands to add
75
+ # to the defaults without replacing them.
76
+ #
77
+ # Default interactive commands:
78
+ # nvim, vim, vi, nano, helix, hx, emacs, emacsclient, less, more, man,
79
+ # htop, btop, top, watch, lazygit, lazydocker, ranger, nnn, lf, yazi, tmux
80
+ status:
81
+ # Replace the default list entirely:
82
+ # interactiveCommands:
83
+ # - nvim
84
+ # - vim
85
+ # - my-custom-editor
86
+ #
87
+ # Or extend the defaults with additional commands:
88
+ # extraInteractiveCommands:
89
+ # - my-custom-editor
90
+ # - my-other-tool
91
+
70
92
  # Audio playback settings.
71
93
  audio:
72
94
  enableSound: true
@@ -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: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,{})}));
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(),e.stopPropagation();for(const n of s)t.runSpawn({shortcutIndex:n});return!0}}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-CR1vBG8R.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-CyA5FKrE.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-TxsheMVB.css">
9
9
  </head>
10
10
  <body>
@@ -41,7 +41,7 @@ async function createServerContext() {
41
41
  type: "worker-removed",
42
42
  workerId
43
43
  });
44
- });
44
+ }, baseConfig);
45
45
  const terminalBridge = new terminalBridge_1.TerminalBridge(workers, {
46
46
  onSubmittedInput: () => {
47
47
  statusMonitor.requestPollSoon();
@@ -27,6 +27,7 @@ function loadResolvedConfig(paths = getArcaneAgentsPaths()) {
27
27
  const userConfig = readConfigFile(paths.configPath);
28
28
  const localOverride = readConfigFile(paths.localOverridePath);
29
29
  const merged = deepMerge(deepMerge(defaults, userConfig), localOverride);
30
+ applyExtraInteractiveCommands(merged);
30
31
  const parsed = schema_1.resolvedConfigSchema.parse(merged);
31
32
  const normalizedProjects = Object.fromEntries(Object.entries(parsed.projects).map(([projectId, project]) => [
32
33
  projectId,
@@ -119,3 +120,16 @@ function normalizeShortcutProjectReferences(config) {
119
120
  shortcuts: normalizedShortcuts
120
121
  };
121
122
  }
123
+ function applyExtraInteractiveCommands(merged) {
124
+ const status = merged.status;
125
+ if (!isRecord(status)) {
126
+ return;
127
+ }
128
+ const extra = status.extraInteractiveCommands;
129
+ if (!Array.isArray(extra) || extra.length === 0) {
130
+ return;
131
+ }
132
+ const base = Array.isArray(status.interactiveCommands) ? status.interactiveCommands : [];
133
+ status.interactiveCommands = [...new Set([...base, ...extra])];
134
+ delete status.extraInteractiveCommands;
135
+ }
@@ -1,8 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolvedConfigSchema = exports.partialConfigSchema = void 0;
3
+ exports.resolvedConfigSchema = exports.partialConfigSchema = exports.defaultInteractiveCommands = void 0;
4
4
  exports.createDefaultConfig = createDefaultConfig;
5
5
  const zod_1 = require("zod");
6
+ exports.defaultInteractiveCommands = [
7
+ "nvim", "vim", "vi", "nano", "helix", "hx",
8
+ "emacs", "emacsclient",
9
+ "less", "more", "man",
10
+ "htop", "btop", "top",
11
+ "watch", "lazygit", "lazydocker",
12
+ "ranger", "nnn", "lf", "yazi",
13
+ "tmux"
14
+ ];
6
15
  const avatarSchema = zod_1.z.string().trim().min(1);
7
16
  const projectSchema = zod_1.z.object({
8
17
  path: zod_1.z.string().min(1),
@@ -40,6 +49,9 @@ const serverSchema = zod_1.z.object({
40
49
  host: zod_1.z.string().min(1),
41
50
  port: zod_1.z.number().int().min(1).max(65535)
42
51
  });
52
+ const statusSchema = zod_1.z.object({
53
+ interactiveCommands: zod_1.z.array(zod_1.z.string().min(1))
54
+ });
43
55
  const audioSchema = zod_1.z.object({
44
56
  enableSound: zod_1.z.boolean()
45
57
  });
@@ -53,6 +65,9 @@ exports.partialConfigSchema = zod_1.z
53
65
  shortcuts: zod_1.z.array(shortcutSchema).optional(),
54
66
  discovery: zod_1.z.array(discoveryRuleSchema).optional(),
55
67
  avatars: avatarsSchema.partial().optional(),
68
+ status: statusSchema.partial().extend({
69
+ extraInteractiveCommands: zod_1.z.array(zod_1.z.string().min(1)).optional()
70
+ }).optional(),
56
71
  audio: audioSchema.partial().optional(),
57
72
  backend: zod_1.z
58
73
  .object({
@@ -73,6 +88,7 @@ exports.resolvedConfigSchema = zod_1.z.object({
73
88
  shortcuts: zod_1.z.array(shortcutSchema),
74
89
  discovery: zod_1.z.array(discoveryRuleSchema),
75
90
  avatars: avatarsSchema,
91
+ status: statusSchema,
76
92
  audio: audioSchema,
77
93
  backend: backendSchema,
78
94
  server: serverSchema
@@ -104,6 +120,9 @@ function createDefaultConfig() {
104
120
  avatars: {
105
121
  disabled: []
106
122
  },
123
+ status: {
124
+ interactiveCommands: exports.defaultInteractiveCommands
125
+ },
107
126
  audio: {
108
127
  enableSound: true
109
128
  },
@@ -63,7 +63,7 @@ class OrchestratorService {
63
63
  const worker = {
64
64
  id: workerId,
65
65
  name: windowName,
66
- displayName: plan.displayName,
66
+ displayName: deduplicateDisplayName(plan.displayName, currentWorkers),
67
67
  projectId: plan.projectId,
68
68
  projectPath: plan.project.path,
69
69
  runtimeId: plan.runtimeId,
@@ -376,3 +376,20 @@ class OrchestratorService {
376
376
  }
377
377
  }
378
378
  exports.OrchestratorService = OrchestratorService;
379
+ function deduplicateDisplayName(baseName, workers) {
380
+ if (!baseName) {
381
+ return undefined;
382
+ }
383
+ const existing = new Set(workers
384
+ .map((w) => w.displayName)
385
+ .filter((n) => typeof n === "string"));
386
+ if (!existing.has(baseName)) {
387
+ return baseName;
388
+ }
389
+ for (let n = 2;; n += 1) {
390
+ const candidate = `${baseName} ${n}`;
391
+ if (!existing.has(candidate)) {
392
+ return candidate;
393
+ }
394
+ }
395
+ }
@@ -24,6 +24,9 @@ function createConfig() {
24
24
  pollIntervalMs: 2500
25
25
  }
26
26
  },
27
+ status: {
28
+ interactiveCommands: []
29
+ },
27
30
  server: {
28
31
  host: "127.0.0.1",
29
32
  port: 7600
@@ -34,6 +34,9 @@ function createConfig() {
34
34
  pollIntervalMs: 2500
35
35
  }
36
36
  },
37
+ status: {
38
+ interactiveCommands: []
39
+ },
37
40
  server: {
38
41
  host: "127.0.0.1",
39
42
  port: 7600
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildWorkerStatusSignalContext = buildWorkerStatusSignalContext;
4
4
  const activityParser_1 = require("../activityParser");
5
5
  const runtimeSignals_1 = require("../runtimeSignals");
6
- function buildWorkerStatusSignalContext({ worker, currentCommand, output, observation, transcriptSnapshot, nowMs }) {
6
+ function buildWorkerStatusSignalContext({ worker, currentCommand, output, observation, transcriptSnapshot, nowMs, interactiveCommands }) {
7
7
  const parsed = (0, activityParser_1.parseActivity)(currentCommand, output);
8
8
  const commandLower = currentCommand.toLowerCase();
9
9
  const isClaude = (0, runtimeSignals_1.isLikelyClaudeSession)(worker, commandLower);
@@ -36,6 +36,7 @@ function buildWorkerStatusSignalContext({ worker, currentCommand, output, observ
36
36
  isOpenCodeSession: isOpenCode,
37
37
  outputQuietForMs,
38
38
  commandQuietForMs,
39
- workerAgeMs
39
+ workerAgeMs,
40
+ interactiveCommands
40
41
  };
41
42
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.shellPromptTailMatchers = exports.recoverableToolErrorMatchers = exports.fatalRuntimeErrorMatchers = exports.openCodeWorkingFreshWindowMs = exports.claudeWorkingFreshWindowMs = exports.genericWorkingFreshWindowMs = exports.claudeSpawnGraceMs = exports.cachedActivityWindowMs = exports.stickyWorkingWindowMs = exports.commandWarmupWindowMs = exports.recentErrorSignalWindowMs = exports.parsedStrongEvidenceWindowMs = void 0;
3
+ exports.recoverableToolErrorMatchers = exports.fatalRuntimeErrorMatchers = exports.openCodeWorkingFreshWindowMs = exports.claudeWorkingFreshWindowMs = exports.genericWorkingFreshWindowMs = exports.claudeSpawnGraceMs = exports.cachedActivityWindowMs = exports.stickyWorkingWindowMs = exports.commandWarmupWindowMs = exports.recentErrorSignalWindowMs = exports.parsedStrongEvidenceWindowMs = void 0;
4
4
  const parsedStrongEvidenceWindowMs = 8_000;
5
5
  exports.parsedStrongEvidenceWindowMs = parsedStrongEvidenceWindowMs;
6
6
  const recentErrorSignalWindowMs = 15_000;
@@ -36,10 +36,3 @@ const recoverableToolErrorMatchers = [
36
36
  /\bhttp(?:\s+status)?\s*(?:code)?\s*:?\s*(?:401|403|404|408|409|410|422|429|500|502|503|504)\b/i
37
37
  ];
38
38
  exports.recoverableToolErrorMatchers = recoverableToolErrorMatchers;
39
- const shellPromptTailMatchers = [
40
- /[$#%]\s*$/,
41
- /(?:❯|›|»|λ|➜|❱)\s*$/,
42
- /^ps\s+[^>]*>\s*$/i,
43
- /^[A-Za-z]:\\[^>]*>\s*$/
44
- ];
45
- exports.shellPromptTailMatchers = shellPromptTailMatchers;
@@ -26,7 +26,7 @@ function deriveWorkerStatusDecision(context) {
26
26
  parsedStrongSignal: false
27
27
  });
28
28
  }
29
- if (context.parsed.activity.needsInput) {
29
+ if (context.parsed.activity.needsInput && !(0, helpers_1.isInteractiveCommand)(context)) {
30
30
  pushReason({ code: "parser-input-prompt", message: "Terminal output indicates input is required." });
31
31
  return finalizeDecision(context, {
32
32
  status: "attention",
@@ -65,7 +65,7 @@ function deriveWorkerStatusDecision(context) {
65
65
  parsedStrongSignal: false
66
66
  });
67
67
  }
68
- const parserErrorClassification = (0, parserErrorRules_1.classifyParserError)(context);
68
+ const parserErrorClassification = (0, helpers_1.isInteractiveCommand)(context) ? "none" : (0, parserErrorRules_1.classifyParserError)(context);
69
69
  if (parserErrorClassification === "fatal" && transcriptStatus !== "working") {
70
70
  pushReason({
71
71
  code: "parser-error-signal",
@@ -58,6 +58,7 @@ function createContext(overrides = {}) {
58
58
  outputQuietForMs: 1_000,
59
59
  commandQuietForMs: 5_000,
60
60
  workerAgeMs: 30_000,
61
+ interactiveCommands: new Set(),
61
62
  ...overrides
62
63
  };
63
64
  }
@@ -3,8 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.recentNormalizedLines = recentNormalizedLines;
4
4
  exports.isAgentRuntime = isAgentRuntime;
5
5
  exports.shouldSuppressShellHistorySignals = shouldSuppressShellHistorySignals;
6
- exports.hasLikelyInteractiveShellPrompt = hasLikelyInteractiveShellPrompt;
7
- exports.normalizePromptLine = normalizePromptLine;
6
+ exports.isInteractiveCommand = isInteractiveCommand;
8
7
  exports.looksLikeActiveRuntimeText = looksLikeActiveRuntimeText;
9
8
  exports.statusFreshnessWindowMs = statusFreshnessWindowMs;
10
9
  exports.isShellCommand = isShellCommand;
@@ -26,31 +25,16 @@ function isAgentRuntime(context) {
26
25
  return context.isOpenCodeSession || context.isClaudeSession;
27
26
  }
28
27
  function shouldSuppressShellHistorySignals(context) {
29
- if (!isShellCommand(context.commandLower)) {
28
+ if (!isShellCommand(context.commandLower) && !isInteractiveCommand(context)) {
30
29
  return false;
31
30
  }
32
31
  if (isAgentRuntime(context)) {
33
32
  return false;
34
33
  }
35
- return hasLikelyInteractiveShellPrompt(context.output);
34
+ return true;
36
35
  }
37
- function hasLikelyInteractiveShellPrompt(output) {
38
- const lines = output
39
- .split("\n")
40
- .map((line) => normalizePromptLine(line))
41
- .filter((line) => line.length > 0)
42
- .slice(-6)
43
- .reverse();
44
- for (const line of lines) {
45
- if (constants_1.shellPromptTailMatchers.some((matcher) => matcher.test(line))) {
46
- return true;
47
- }
48
- return false;
49
- }
50
- return false;
51
- }
52
- function normalizePromptLine(line) {
53
- return line.replace(/^[\s│┃╹▀▣⬝■]+/, "").trim();
36
+ function isInteractiveCommand(context) {
37
+ return context.interactiveCommands.has(context.commandLower);
54
38
  }
55
39
  function looksLikeActiveRuntimeText(activityText) {
56
40
  if (!activityText) {
@@ -58,6 +58,7 @@ function createContext(overrides = {}) {
58
58
  outputQuietForMs: 200,
59
59
  commandQuietForMs: 300,
60
60
  workerAgeMs: 10_000,
61
+ interactiveCommands: new Set(),
61
62
  ...overrides
62
63
  };
63
64
  }
@@ -80,10 +81,6 @@ function createContext(overrides = {}) {
80
81
  });
81
82
  (0, vitest_1.expect)((0, helpers_1.shouldSuppressShellHistorySignals)(opencodeContext)).toBe(false);
82
83
  });
83
- (0, vitest_1.it)("detects likely prompt tails in recent output", () => {
84
- (0, vitest_1.expect)((0, helpers_1.hasLikelyInteractiveShellPrompt)("running\n$ ")).toBe(true);
85
- (0, vitest_1.expect)((0, helpers_1.hasLikelyInteractiveShellPrompt)("running\nstill working")).toBe(false);
86
- });
87
84
  (0, vitest_1.it)("recognizes active runtime text and ignores waiting text", () => {
88
85
  (0, vitest_1.expect)((0, helpers_1.looksLikeActiveRuntimeText)("Reading src/index.ts")).toBe(true);
89
86
  (0, vitest_1.expect)((0, helpers_1.looksLikeActiveRuntimeText)("Waiting for approval")).toBe(false);
@@ -68,6 +68,7 @@ function collectWorkingEvidence(context, hasRecoverableParserError) {
68
68
  }
69
69
  if (context.commandQuietForMs <= constants_1.commandWarmupWindowMs &&
70
70
  !(0, helpers_1.isShellCommand)(context.commandLower) &&
71
+ !(0, helpers_1.isInteractiveCommand)(context) &&
71
72
  !context.parsed.activity.needsInput &&
72
73
  !context.parsed.activity.hasError) {
73
74
  weakReasons.push({
@@ -3,14 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.evaluateWorkerStatus = evaluateWorkerStatus;
4
4
  const signalContext_1 = require("./engine/signalContext");
5
5
  const stateMachine_1 = require("./engine/stateMachine");
6
- function evaluateWorkerStatus({ worker, currentCommand, output, observation, transcriptSnapshot }) {
6
+ function evaluateWorkerStatus({ worker, currentCommand, output, observation, transcriptSnapshot, interactiveCommands }) {
7
7
  const context = (0, signalContext_1.buildWorkerStatusSignalContext)({
8
8
  worker,
9
9
  currentCommand,
10
10
  output,
11
11
  observation,
12
12
  transcriptSnapshot,
13
- nowMs: Date.now()
13
+ nowMs: Date.now(),
14
+ interactiveCommands
14
15
  });
15
16
  return (0, stateMachine_1.deriveWorkerStatusDecision)(context);
16
17
  }
@@ -40,12 +40,14 @@ class StatusMonitor {
40
40
  recentPollTiming = [];
41
41
  traceMode = resolveStatusTraceMode();
42
42
  workerPollConcurrency = resolveStatusPollConcurrency();
43
- constructor(workers, tmux, pollIntervalMs, onWorkerUpdated, onWorkerRemoved) {
43
+ interactiveCommands;
44
+ constructor(workers, tmux, pollIntervalMs, onWorkerUpdated, onWorkerRemoved, config) {
44
45
  this.workers = workers;
45
46
  this.tmux = tmux;
46
47
  this.pollIntervalMs = pollIntervalMs;
47
48
  this.onWorkerUpdated = onWorkerUpdated;
48
49
  this.onWorkerRemoved = onWorkerRemoved;
50
+ this.interactiveCommands = new Set(config.status.interactiveCommands.map((cmd) => cmd.toLowerCase()));
49
51
  }
50
52
  start() {
51
53
  if (this.intervalId) {
@@ -167,7 +169,8 @@ class StatusMonitor {
167
169
  worker,
168
170
  tmux: this.tmux,
169
171
  paneObservation: this.paneObservation,
170
- claudeTranscript: this.claudeTranscript
172
+ claudeTranscript: this.claudeTranscript,
173
+ interactiveCommands: this.interactiveCommands
171
174
  });
172
175
  if (!signals) {
173
176
  this.removeWorker(worker.id);
@@ -8,6 +8,7 @@ vitest_1.vi.mock("./statusPipeline", () => ({
8
8
  evaluateWorkerStatusSignals: vitest_1.vi.fn(),
9
9
  normalizeWorkerStatusEvaluation: vitest_1.vi.fn((evaluation) => evaluation)
10
10
  }));
11
+ const testConfig = { status: { interactiveCommands: [] } };
11
12
  const defaultFacts = {
12
13
  command: "claude",
13
14
  commandQuietForMs: 0,
@@ -88,7 +89,8 @@ function createSignals() {
88
89
  lastOutputSignature: "",
89
90
  lastOutputChangeAtMs: Date.now()
90
91
  },
91
- transcriptSnapshot: undefined
92
+ transcriptSnapshot: undefined,
93
+ interactiveCommands: new Set()
92
94
  };
93
95
  }
94
96
  function createEvaluation(status) {
@@ -120,7 +122,7 @@ function createEvaluation(status) {
120
122
  };
121
123
  const onWorkerUpdated = vitest_1.vi.fn();
122
124
  const onWorkerRemoved = vitest_1.vi.fn();
123
- const monitor = new statusMonitor_1.StatusMonitor(repository.repo, tmux, 1_000, onWorkerUpdated, onWorkerRemoved);
125
+ const monitor = new statusMonitor_1.StatusMonitor(repository.repo, tmux, 1_000, onWorkerUpdated, onWorkerRemoved, testConfig);
124
126
  const statusSequence = ["working", "attention", "error", "idle", "stopped"];
125
127
  let nextStatusIndex = 0;
126
128
  evaluateMock.mockImplementation(() => {
@@ -160,7 +162,7 @@ function createEvaluation(status) {
160
162
  return true;
161
163
  })
162
164
  };
163
- const monitor = new statusMonitor_1.StatusMonitor(repository.repo, tmux, 1_000, () => undefined, () => undefined);
165
+ const monitor = new statusMonitor_1.StatusMonitor(repository.repo, tmux, 1_000, () => undefined, () => undefined, testConfig);
164
166
  await monitor.pollOnce();
165
167
  (0, vitest_1.expect)(maxInFlight).toBeLessThanOrEqual(2);
166
168
  (0, vitest_1.expect)(maxInFlight).toBeGreaterThanOrEqual(1);
@@ -170,7 +172,7 @@ function createEvaluation(status) {
170
172
  const tmux = {
171
173
  windowExists: vitest_1.vi.fn(async () => true)
172
174
  };
173
- const monitor = new statusMonitor_1.StatusMonitor(repository.repo, tmux, 1_000, () => undefined, () => undefined);
175
+ const monitor = new statusMonitor_1.StatusMonitor(repository.repo, tmux, 1_000, () => undefined, () => undefined, testConfig);
174
176
  evaluateMock.mockImplementation((worker) => {
175
177
  if (worker.id === "worker-1") {
176
178
  return createEvaluation("working");
@@ -6,7 +6,7 @@ exports.normalizeWorkerStatusEvaluation = normalizeWorkerStatusEvaluation;
6
6
  const runtimeSignals_1 = require("./runtimeSignals");
7
7
  const statusEvaluator_1 = require("./statusEvaluator");
8
8
  const paneObservation_1 = require("./paneObservation");
9
- async function collectWorkerStatusSignals({ worker, tmux, paneObservation, claudeTranscript }) {
9
+ async function collectWorkerStatusSignals({ worker, tmux, paneObservation, claudeTranscript, interactiveCommands }) {
10
10
  const paneState = await tmux.getPaneState(worker.tmuxRef);
11
11
  if (paneState.isDead) {
12
12
  return undefined;
@@ -20,7 +20,8 @@ async function collectWorkerStatusSignals({ worker, tmux, paneObservation, claud
20
20
  currentCommand: paneState.currentCommand,
21
21
  output,
22
22
  observation,
23
- transcriptSnapshot
23
+ transcriptSnapshot,
24
+ interactiveCommands
24
25
  };
25
26
  }
26
27
  function evaluateWorkerStatusSignals(worker, signals) {
@@ -29,7 +30,8 @@ function evaluateWorkerStatusSignals(worker, signals) {
29
30
  currentCommand: signals.currentCommand,
30
31
  output: signals.output,
31
32
  observation: signals.observation,
32
- transcriptSnapshot: signals.transcriptSnapshot
33
+ transcriptSnapshot: signals.transcriptSnapshot,
34
+ interactiveCommands: signals.interactiveCommands
33
35
  });
34
36
  }
35
37
  function normalizeWorkerStatusEvaluation(evaluation) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-agents",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
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"