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.
- package/README.md +40 -0
- package/config.example.yaml +22 -0
- package/dist/client/assets/{index-B0hs4377.js → index-CyA5FKrE.js} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/server/server/bootstrap/serverContext.js +1 -1
- package/dist/server/server/config/loadConfig.js +14 -0
- package/dist/server/server/config/schema.js +20 -1
- package/dist/server/server/orchestrator/orchestratorService.js +18 -1
- package/dist/server/server/orchestrator/orchestratorService.test.js +3 -0
- package/dist/server/server/orchestrator/spawn/resolveSpawnPlan.test.js +3 -0
- package/dist/server/server/status/claudeTranscript/io.js +57 -18
- package/dist/server/server/status/claudeTranscript/process.js +68 -0
- package/dist/server/server/status/claudeTranscriptTracker.js +11 -1
- package/dist/server/server/status/engine/signalContext.js +3 -2
- package/dist/server/server/status/engine/stateMachine/constants.js +1 -8
- package/dist/server/server/status/engine/stateMachine/decision.js +2 -2
- package/dist/server/server/status/engine/stateMachine/decision.test.js +1 -0
- package/dist/server/server/status/engine/stateMachine/helpers.js +5 -21
- package/dist/server/server/status/engine/stateMachine/helpers.test.js +1 -4
- package/dist/server/server/status/engine/stateMachine/workingEvidence.js +3 -0
- package/dist/server/server/status/paneObservation.js +3 -2
- package/dist/server/server/status/statusEvaluator.js +3 -2
- package/dist/server/server/status/statusMonitor.js +5 -2
- package/dist/server/server/status/statusMonitor.test.js +6 -4
- package/dist/server/server/status/statusPipeline.js +6 -4
- package/dist/server/server/tmux/tmuxAdapter.js +5 -3
- 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`).
|
package/config.example.yaml
CHANGED
|
@@ -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,{})}));
|
package/dist/client/index.html
CHANGED
|
@@ -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-
|
|
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>
|
|
@@ -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
|
+
}
|
|
@@ -30,10 +30,10 @@ async function resolveTranscriptPath({ worker, state, paneCurrentPath, nowMs })
|
|
|
30
30
|
return directPath;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
const
|
|
34
|
-
if (
|
|
33
|
+
const match = await findMatchingTranscriptFile(transcriptDir, nowMs, state.claudeSessionStartAtMs);
|
|
34
|
+
if (match) {
|
|
35
35
|
state.nextTranscriptLookupAtMs = 0;
|
|
36
|
-
return
|
|
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
|
-
|
|
72
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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 (
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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
|
-
|
|
131
|
+
catch {
|
|
93
132
|
return undefined;
|
|
94
133
|
}
|
|
95
|
-
return
|
|
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.
|
|
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",
|
|
@@ -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.
|
|
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
|
|
34
|
+
return true;
|
|
36
35
|
}
|
|
37
|
-
function
|
|
38
|
-
|
|
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
|
-
|
|
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() {
|