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