arcane-agents 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +30 -0
  2. package/dist/client/assets/index-BCnWppkv.js +83 -0
  3. package/dist/client/assets/{index-CWU29xaz.css → index-Di_KBFPW.css} +1 -1
  4. package/dist/client/index.html +2 -2
  5. package/dist/server/server/bootstrap/serverContext.js +9 -2
  6. package/dist/server/server/bootstrapApp.js +9 -3
  7. package/dist/server/server/cli.js +122 -6
  8. package/dist/server/server/config/loadConfig.js +10 -2
  9. package/dist/server/server/http/routes/registerApiRoutes.js +10 -0
  10. package/dist/server/server/orchestrator/orchestratorService.js +29 -0
  11. package/dist/server/server/orchestrator/orchestratorService.test.js +58 -0
  12. package/dist/server/server/status/activityParser.js +1 -1
  13. package/dist/server/server/status/engine/signalContext.js +18 -6
  14. package/dist/server/server/status/engine/stateMachine/constants.js +5 -1
  15. package/dist/server/server/status/engine/stateMachine/decision.js +17 -0
  16. package/dist/server/server/status/engine/stateMachine/decision.test.js +63 -0
  17. package/dist/server/server/status/engine/stateMachine/helpers.js +4 -1
  18. package/dist/server/server/status/engine/stateMachine/helpers.test.js +6 -0
  19. package/dist/server/server/status/engine/stateMachine/idleBlockers.js +13 -0
  20. package/dist/server/server/status/engine/stateMachine/workingEvidence.js +23 -0
  21. package/dist/server/server/status/runtime/activityTextExtractors.js +40 -0
  22. package/dist/server/server/status/runtime/claudeSignals.js +114 -33
  23. package/dist/server/server/status/runtime/claudeSignals.test.js +23 -0
  24. package/dist/server/server/status/runtime/codexSignals.js +132 -0
  25. package/dist/server/server/status/runtime/codexSignals.test.js +47 -0
  26. package/dist/server/server/status/runtime/runtimeProcess.js +86 -0
  27. package/dist/server/server/status/runtime/sessionDetection.js +15 -0
  28. package/dist/server/server/status/runtimeSignals.js +8 -1
  29. package/dist/server/server/status/statusEvaluator.js +2 -1
  30. package/dist/server/server/status/statusMonitor.js +8 -1
  31. package/dist/server/server/status/statusMonitor.test.js +6 -0
  32. package/dist/server/server/status/statusPipeline.js +9 -1
  33. package/dist/server/server/ws/terminalBridge.js +11 -0
  34. package/package.json +1 -1
  35. package/dist/client/assets/index-HfXsReQG.js +0 -83
@@ -29,4 +29,4 @@
29
29
  * The original design remains. The terminal itself
30
30
  * has been extended to include xterm CSI codes, among
31
31
  * other features.
32
- */.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;inset:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;inset:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{font-family:monospace;user-select:text;white-space:pre}.xterm .xterm-accessibility-tree>div{transform-origin:left;width:fit-content}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}.xterm .xterm-scrollable-element>.scrollbar{cursor:default}.xterm .xterm-scrollable-element>.scrollbar>.scra{cursor:pointer;font-size:11px!important}.xterm .xterm-scrollable-element>.visible{opacity:1;background:#0000;transition:opacity .1s linear;z-index:11}.xterm .xterm-scrollable-element>.invisible{opacity:0;pointer-events:none}.xterm .xterm-scrollable-element>.invisible.fade{transition:opacity .8s linear}.xterm .xterm-scrollable-element>.shadow{position:absolute;display:none}.xterm .xterm-scrollable-element>.shadow.top{display:block;top:0;left:3px;height:3px;width:100%;box-shadow:var(--vscode-scrollbar-shadow, #000) 0 6px 6px -6px inset}.xterm .xterm-scrollable-element>.shadow.left{display:block;top:3px;left:0;height:100%;width:3px;box-shadow:var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset}.xterm .xterm-scrollable-element>.shadow.top-left-corner{display:block;top:0;left:0;height:3px;width:3px}.xterm .xterm-scrollable-element>.shadow.top.left{box-shadow:var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset}:root{color-scheme:dark;font-family:Trebuchet MS,Segoe UI,sans-serif;background:#0f1815;color:#e9f0df}*{box-sizing:border-box}html,body,#root{margin:0;width:100%;height:100%}body{background:radial-gradient(circle at 15% 10%,#2d5c4c,#162821 45%,#0d1412)}.app-shell{width:100%;height:100%;display:grid;grid-template-columns:minmax(380px,1.55fr) minmax(360px,1fr);gap:0;padding:10px}.layout-divider{position:relative;cursor:col-resize;touch-action:none}.layout-divider:before{content:"";position:absolute;top:8px;bottom:8px;left:50%;width:2px;border-radius:999px;transform:translate(-50%);background:#d6e6ba3d;transition:background-color .11s ease}.layout-divider:hover:before,.layout-divider.layout-divider-active:before{background:#eaf3d6ad}body.split-pane-dragging{cursor:col-resize;user-select:none}.map-column{min-height:0;display:flex;flex-direction:column;border:1px solid rgba(218,235,186,.22);border-radius:14px;overflow:hidden;background:#12221b99}.map-container{position:relative;flex:1;min-height:0}.map-canvas{width:100%;height:100%;display:block;cursor:pointer}.map-canvas:focus{outline:none}.map-tooltip{position:absolute;z-index:4;min-width:190px;max-width:280px;padding:8px 10px;font-size:12px;line-height:1.4;color:#f4f6e7;background:#0b100ee0;border:1px solid rgba(223,234,189,.22);border-radius:8px;pointer-events:none}.map-tooltip-title{font-weight:700;margin-bottom:2px}.bottom-bar{display:flex;align-items:center;gap:8px;height:74px;flex-shrink:0;padding:11px;overflow:hidden;border-top:1px solid rgba(218,235,186,.18);background:linear-gradient(180deg,#101a16eb,#0d1412f2)}.bar-btn{border:1px solid rgba(207,224,181,.34);border-radius:9px;background:#2b4336f2;color:#f0f5e3;padding:9px 13px;font-weight:600;cursor:pointer;transition:transform 90ms ease,background-color 90ms ease}.bar-btn:hover{transform:translateY(-1px);background:#3d5b49f2}.bar-btn:disabled{opacity:.5;cursor:not-allowed;transform:none}.bar-btn.subtle{background:#202a25f2}.bar-btn.danger{background:#682c2cf2;border-color:#dc8c828c}.bar-btn.accent{font-size:20px;line-height:1;padding:7px 13px}.selected-worker-meta{margin-right:auto;min-width:0}.selected-worker-name{font-weight:700;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.selected-worker-subline{font-size:12px;color:#eef3dfcc;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.terminal-column{min-height:0;border:1px solid rgba(218,235,186,.22);border-radius:14px;overflow:hidden;display:flex;flex-direction:column;background:#0c1311eb}.terminal-column.terminal-column-selected{border-color:#ecf2d45c}.terminal-column.terminal-column-focused{border-color:#88e9ffdb;box-shadow:0 0 0 1px #37adca73,0 0 22px #1a627338}.terminal-header{min-height:52px;padding:14px 16px;font-weight:700;border-bottom:1px solid rgba(218,235,186,.18);display:flex;align-items:center;justify-content:space-between;gap:10px}.terminal-header-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.terminal-ready-chip{flex:0 0 auto;border:1px solid rgba(255,233,164,.9);border-radius:999px;background:linear-gradient(110deg,#c1922dfa,#f6d273fa,#a77d22fa);background-size:220% 100%;color:#2f2205;padding:3px 10px;font-size:11px;font-weight:700;line-height:1.25;letter-spacing:.03em;box-shadow:inset 0 1px #fff8df8c,0 0 0 1px #70501252;animation:ready-badge-sheen 1.9s linear infinite}.terminal-open-external{border:1px solid rgba(207,224,181,.34);border-radius:8px;background:#20342af2;color:#f0f5e3;width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;font-size:16px;line-height:1;cursor:pointer}.terminal-open-external:hover{background:#345041f2}.terminal-open-external:disabled{opacity:.45;cursor:not-allowed}.terminal-panel{flex:1;min-height:0;padding:8px}.worker-roster{flex:1;min-height:0;padding:10px;display:flex;flex-direction:column;gap:8px;overflow:auto}.worker-roster-section-label{margin:4px 2px 2px;font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#e5edd3c2}.worker-roster-item{border:1px solid rgba(208,224,181,.22);border-radius:9px;background:#14221be6;color:#f1f5e6;text-align:left;padding:9px 10px;cursor:pointer;display:flex;flex-direction:column;gap:2px}.worker-roster-main{display:flex;align-items:center;gap:10px;min-width:0}.worker-roster-avatar{width:42px;height:42px;image-rendering:pixelated;object-fit:contain;flex:0 0 auto}.worker-roster-text{min-width:0;display:flex;flex-direction:column;gap:2px}.worker-roster-item:hover{background:#20352aeb;border-color:#e2edc761}.worker-roster-item.active{border-color:#eff4d4bd;background:#355040eb}.worker-roster-item.worker-roster-item-summon{background:#1c2a22e6}.worker-roster-item.worker-roster-item-summon:hover{background:#283f32eb}.worker-roster-item.worker-roster-item-summon.active{border-color:#9de5b0d6;background:#345643f0}.worker-roster-summon-avatar{border-radius:10px;border:1px solid rgba(170,227,186,.44);background:#284636f2;padding:2px}.worker-roster-summon-glyph{width:42px;height:42px;border-radius:10px;border:1px solid rgba(170,227,186,.44);background:#284636f2;color:#e8f7de;display:inline-flex;align-items:center;justify-content:center;font-size:22px;font-weight:700;flex:0 0 auto}.worker-roster-name{min-width:0;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.worker-roster-name-row{min-width:0;display:flex;align-items:center;gap:6px}.worker-complete-badge{flex:0 0 auto;border-radius:999px;border:1px solid rgba(252,229,153,.88);background:linear-gradient(115deg,#b28426fa,#f4d06efa,#9e751dfa);background-size:220% 100%;color:#2d2106;display:inline-flex;align-items:center;justify-content:center;padding:1px 6px;font-size:9px;font-weight:700;letter-spacing:.05em;line-height:1.25;box-shadow:inset 0 1px #fff8df80;animation:ready-badge-sheen 2.05s linear infinite}.worker-roster-item.active .worker-complete-badge{border-color:#fff0b8f5;background:linear-gradient(115deg,#c4952cfc,#ffdd7efc,#ab8022fc);color:#2b1f05}@keyframes ready-badge-sheen{0%{background-position:200% 50%}to{background-position:-20% 50%}}.worker-roster-meta{font-size:12px;color:#e6ecd8c7}.worker-roster-activity{font-size:12px;color:#eef4e1db}.worker-roster-empty{border:1px dashed rgba(207,223,182,.28);border-radius:8px;padding:12px;color:#e8f0dad1;font-size:13px}.rally-command-card{margin-top:4px;padding:10px;border:1px solid rgba(185,225,198,.36);border-radius:10px;background:linear-gradient(180deg,#13261ff5,#0f1f1af5);display:flex;flex-direction:column;gap:8px}.rally-command-header{display:flex;align-items:center;justify-content:space-between;gap:10px}.rally-command-title{font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#e4f0cfeb}.rally-command-count{font-size:11px;color:#ddeac7b8}.rally-command-input{font-family:Consolas,Monaco,Lucida Console,monospace;min-height:78px;line-height:1.35;resize:vertical}.rally-command-actions{display:flex;align-items:center;justify-content:space-between;gap:10px}.rally-command-hint{font-size:11px;color:#d9e5c2c7}.rally-command-result{font-size:12px;color:#e9f1d5e6}.overlay{position:fixed;inset:0;z-index:25;display:grid;place-items:center;background:#050a08ad;backdrop-filter:blur(2px)}.overlay-no-blur{backdrop-filter:none}.dialog{width:min(880px,calc(100vw - 34px));max-height:min(78vh,740px);overflow:auto;padding:16px;border-radius:12px;border:1px solid rgba(222,236,193,.26);background:linear-gradient(180deg,#141e1af7,#0d1311fa)}.dialog-title{margin-bottom:12px;font-size:20px;font-weight:700}.input,.palette-input{width:100%;border-radius:9px;border:1px solid rgba(218,235,186,.32);background:#090f0df2;color:#ecf3e1;padding:10px 12px;font-size:14px}.dialog-grid{margin-top:12px;display:grid;grid-template-columns:1fr 1fr;gap:12px}.dialog-section-label{font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;margin-bottom:8px;color:#e6ecd8d6}.option-list{display:flex;flex-direction:column;gap:6px;max-height:340px;overflow:auto}.option-btn,.palette-item{border:1px solid rgba(208,224,181,.22);border-radius:8px;background:#14221be6;color:#f1f5e6;text-align:left;padding:8px 10px;cursor:pointer;display:flex;flex-direction:column;gap:2px}.option-btn small,.palette-item small{color:#e2ebd2b8;font-size:11px}.option-btn.selected,.palette-item.active{border-color:#f0f3c8c7;background:#354f40eb}.dialog-actions{margin-top:14px;display:flex;justify-content:flex-end;gap:8px}.shortcuts-dialog{width:min(720px,calc(100vw - 34px))}.shortcut-grid{display:grid;grid-template-columns:1fr;gap:8px}.shortcut-row{display:grid;grid-template-columns:140px 1fr;gap:10px;align-items:center;padding:8px 10px;border-radius:8px;border:1px solid rgba(214,229,188,.16);background:#101a15d1}kbd{display:inline-block;min-width:52px;padding:4px 7px;border-radius:6px;border:1px solid rgba(233,242,206,.36);background:#0a0e0ceb;color:#edf4dd;font-size:12px;font-weight:700;text-align:center}.rename-dialog{width:min(540px,calc(100vw - 34px))}.rename-subtitle{margin-top:-2px;margin-bottom:10px;color:#e3ebd3c2;font-size:13px}.rename-form{display:flex;flex-direction:column}.kill-confirm-dialog{width:min(520px,calc(100vw - 34px))}.kill-confirm-copy{color:#e7eed9d9;font-size:14px;line-height:1.4}.kill-confirm-hint{margin-top:8px;color:#d6e0c0c2;font-size:12px}.palette{width:min(760px,calc(100vw - 30px));border-radius:12px;border:1px solid rgba(222,236,193,.26);background:linear-gradient(180deg,#121e18f7,#0d1411fa);padding:12px}.palette-list{margin-top:8px;max-height:410px;overflow:auto;display:flex;flex-direction:column;gap:6px}.palette-empty{padding:12px;font-size:13px;color:#e2ebd2cc}.error-toast{position:fixed;right:14px;bottom:14px;z-index:40;max-width:420px;border:1px solid rgba(242,169,158,.58);background:#4b1713e6;border-radius:8px;padding:10px 12px;cursor:pointer}.batch-spawn-dialog{width:min(640px,calc(100vw - 34px))}.batch-spawn-config-list{margin-top:8px}.batch-spawn-config-summary{display:flex;align-items:center;gap:10px;margin-bottom:10px}.batch-spawn-config-summary small{color:#e2ebd2b8;font-size:12px}.batch-spawn-back{border:1px solid rgba(207,224,181,.34);border-radius:8px;background:#20342af2;color:#f0f5e3;width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;font-size:16px;cursor:pointer}.batch-spawn-back:hover{background:#345041f2}.batch-spawn-textarea{min-height:160px;resize:vertical;font-family:Consolas,Monaco,Lucida Console,monospace;line-height:1.4}.batch-spawn-footer{margin-top:12px;display:flex;align-items:center;justify-content:space-between;gap:10px}.batch-spawn-count{font-size:13px;color:#e2ebd2c7}.batch-spawn-progress{display:flex;align-items:center;gap:10px;flex:1}.batch-spawn-progress-bar{flex:1;height:6px;border-radius:3px;background:#daebba2e;overflow:hidden}.batch-spawn-progress-fill{height:100%;border-radius:3px;background:#8cdcaad9;transition:width .15s ease}.batch-spawn-progress-text{font-size:12px;color:#e2ebd2d1;white-space:nowrap}@media(max-width:960px){.app-shell{grid-template-columns:1fr;grid-template-rows:1fr minmax(280px,38vh)}.dialog-grid{grid-template-columns:1fr}}
32
+ */.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;inset:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;inset:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{font-family:monospace;user-select:text;white-space:pre}.xterm .xterm-accessibility-tree>div{transform-origin:left;width:fit-content}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}.xterm .xterm-scrollable-element>.scrollbar{cursor:default}.xterm .xterm-scrollable-element>.scrollbar>.scra{cursor:pointer;font-size:11px!important}.xterm .xterm-scrollable-element>.visible{opacity:1;background:#0000;transition:opacity .1s linear;z-index:11}.xterm .xterm-scrollable-element>.invisible{opacity:0;pointer-events:none}.xterm .xterm-scrollable-element>.invisible.fade{transition:opacity .8s linear}.xterm .xterm-scrollable-element>.shadow{position:absolute;display:none}.xterm .xterm-scrollable-element>.shadow.top{display:block;top:0;left:3px;height:3px;width:100%;box-shadow:var(--vscode-scrollbar-shadow, #000) 0 6px 6px -6px inset}.xterm .xterm-scrollable-element>.shadow.left{display:block;top:3px;left:0;height:100%;width:3px;box-shadow:var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset}.xterm .xterm-scrollable-element>.shadow.top-left-corner{display:block;top:0;left:0;height:3px;width:3px}.xterm .xterm-scrollable-element>.shadow.top.left{box-shadow:var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset}:root{color-scheme:dark;font-family:Trebuchet MS,Segoe UI,sans-serif;background:#0f1815;color:#e9f0df}*{box-sizing:border-box}html,body,#root{margin:0;width:100%;height:100%}body{background:radial-gradient(circle at 15% 10%,#2d5c4c,#162821 45%,#0d1412)}.app-shell{width:100%;height:100%;display:grid;grid-template-columns:minmax(380px,1.55fr) minmax(360px,1fr);gap:0;padding:10px}.layout-divider{position:relative;cursor:col-resize;touch-action:none}.layout-divider:before{content:"";position:absolute;top:8px;bottom:8px;left:50%;width:2px;border-radius:999px;transform:translate(-50%);background:#d6e6ba3d;transition:background-color .11s ease}.layout-divider:hover:before,.layout-divider.layout-divider-active:before{background:#eaf3d6ad}body.split-pane-dragging{cursor:col-resize;user-select:none}.map-column{min-height:0;display:flex;flex-direction:column;border:1px solid rgba(218,235,186,.22);border-radius:14px;overflow:hidden;background:#12221b99}.map-container{position:relative;flex:1;min-height:0}.map-canvas{width:100%;height:100%;display:block;cursor:pointer}.map-canvas:focus{outline:none}.map-tooltip{position:absolute;z-index:4;min-width:190px;max-width:280px;padding:8px 10px;font-size:12px;line-height:1.4;color:#f4f6e7;background:#0b100ee0;border:1px solid rgba(223,234,189,.22);border-radius:8px;pointer-events:none}.map-tooltip-title{font-weight:700;margin-bottom:2px}.bottom-bar{display:flex;align-items:center;gap:8px;height:74px;flex-shrink:0;padding:11px;overflow:hidden;border-top:1px solid rgba(218,235,186,.18);background:linear-gradient(180deg,#101a16eb,#0d1412f2)}.bar-btn{border:1px solid rgba(207,224,181,.34);border-radius:9px;background:#2b4336f2;color:#f0f5e3;padding:9px 13px;font-weight:600;cursor:pointer;transition:transform 90ms ease,background-color 90ms ease}.bar-btn:hover{transform:translateY(-1px);background:#3d5b49f2}.bar-btn:disabled{opacity:.5;cursor:not-allowed;transform:none}.bar-btn.subtle{background:#202a25f2}.bar-btn.danger{background:#682c2cf2;border-color:#dc8c828c}.bar-btn.accent{font-size:20px;line-height:1;padding:7px 13px}.selected-worker-meta{margin-right:auto;min-width:0}.selected-worker-name{font-weight:700;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.selected-worker-subline{font-size:12px;color:#eef3dfcc;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.terminal-column{min-height:0;border:1px solid rgba(218,235,186,.22);border-radius:14px;overflow:hidden;display:flex;flex-direction:column;background:#0c1311eb}.terminal-column.terminal-column-selected{border-color:#ecf2d45c}.terminal-column.terminal-column-focused{border-color:#88e9ffdb;box-shadow:0 0 0 1px #37adca73,0 0 22px #1a627338}.terminal-header{min-height:52px;padding:14px 16px;font-weight:700;border-bottom:1px solid rgba(218,235,186,.18);display:flex;align-items:center;justify-content:space-between;gap:10px}.terminal-header-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.terminal-ready-chip{flex:0 0 auto;border:1px solid rgba(255,233,164,.9);border-radius:999px;background:linear-gradient(110deg,#c1922dfa,#f6d273fa,#a77d22fa);background-size:220% 100%;color:#2f2205;padding:3px 10px;font-size:11px;font-weight:700;line-height:1.25;letter-spacing:.03em;box-shadow:inset 0 1px #fff8df8c,0 0 0 1px #70501252;animation:ready-badge-sheen 1.9s linear infinite}.terminal-open-external{border:1px solid rgba(207,224,181,.34);border-radius:8px;background:#20342af2;color:#f0f5e3;width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;font-size:16px;line-height:1;cursor:pointer}.terminal-open-external:hover{background:#345041f2}.terminal-open-external:disabled{opacity:.45;cursor:not-allowed}.terminal-panel{flex:1;min-height:0;padding:8px}.worker-roster{flex:1;min-height:0;padding:10px;display:flex;flex-direction:column;gap:8px;overflow:auto}.worker-roster-section-label{margin:4px 2px 2px;font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#e5edd3c2}.worker-roster-item{border:1px solid rgba(208,224,181,.22);border-radius:9px;background:#14221be6;color:#f1f5e6;text-align:left;padding:9px 10px;cursor:pointer;display:flex;flex-direction:column;gap:2px}.worker-roster-main{display:flex;align-items:center;gap:10px;min-width:0}.worker-roster-avatar{width:42px;height:42px;image-rendering:pixelated;object-fit:contain;flex:0 0 auto}.worker-roster-text{min-width:0;display:flex;flex-direction:column;gap:2px}.worker-roster-item:hover{background:#20352aeb;border-color:#e2edc761}.worker-roster-item.active{border-color:#eff4d4bd;background:#355040eb}.worker-roster-item.worker-roster-item-summon{background:#1c2a22e6}.worker-roster-item.worker-roster-item-summon:hover{background:#283f32eb}.worker-roster-item.worker-roster-item-summon.active{border-color:#9de5b0d6;background:#345643f0}.worker-roster-summon-avatar{border-radius:10px;border:1px solid rgba(170,227,186,.44);background:#284636f2;padding:2px}.worker-roster-summon-glyph{width:42px;height:42px;border-radius:10px;border:1px solid rgba(170,227,186,.44);background:#284636f2;color:#e8f7de;display:inline-flex;align-items:center;justify-content:center;font-size:22px;font-weight:700;flex:0 0 auto}.worker-roster-name{min-width:0;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.worker-roster-name-row{min-width:0;display:flex;align-items:center;gap:6px}.worker-complete-badge{flex:0 0 auto;border-radius:999px;border:1px solid rgba(252,229,153,.88);background:linear-gradient(115deg,#b28426fa,#f4d06efa,#9e751dfa);background-size:220% 100%;color:#2d2106;display:inline-flex;align-items:center;justify-content:center;padding:1px 6px;font-size:9px;font-weight:700;letter-spacing:.05em;line-height:1.25;box-shadow:inset 0 1px #fff8df80;animation:ready-badge-sheen 2.05s linear infinite}.worker-roster-item.active .worker-complete-badge{border-color:#fff0b8f5;background:linear-gradient(115deg,#c4952cfc,#ffdd7efc,#ab8022fc);color:#2b1f05}@keyframes ready-badge-sheen{0%{background-position:200% 50%}to{background-position:-20% 50%}}.worker-roster-meta{font-size:12px;color:#e6ecd8c7}.worker-roster-activity{font-size:12px;color:#eef4e1db}.worker-roster-empty{border:1px dashed rgba(207,223,182,.28);border-radius:8px;padding:12px;color:#e8f0dad1;font-size:13px}.rally-command-card{margin-top:4px;padding:10px;border:1px solid rgba(185,225,198,.36);border-radius:10px;background:linear-gradient(180deg,#13261ff5,#0f1f1af5);display:flex;flex-direction:column;gap:8px}.rally-command-header{display:flex;align-items:center;justify-content:space-between;gap:10px}.rally-command-title{font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#e4f0cfeb}.rally-command-count{font-size:11px;color:#ddeac7b8}.rally-command-input{font-family:Consolas,Monaco,Lucida Console,monospace;min-height:78px;line-height:1.35;resize:vertical}.rally-command-actions{display:flex;align-items:center;justify-content:space-between;gap:10px}.rally-command-hint{font-size:11px;color:#d9e5c2c7}.rally-command-result{font-size:12px;color:#e9f1d5e6}.overlay{position:fixed;inset:0;z-index:25;display:grid;place-items:center;background:#050a08ad;backdrop-filter:blur(2px)}.overlay-no-blur{backdrop-filter:none}.dialog{width:min(880px,calc(100vw - 34px));max-height:min(78vh,740px);overflow:auto;padding:16px;border-radius:12px;border:1px solid rgba(222,236,193,.26);background:linear-gradient(180deg,#141e1af7,#0d1311fa)}.dialog-title{margin-bottom:12px;font-size:20px;font-weight:700}.input,.palette-input{width:100%;border-radius:9px;border:1px solid rgba(218,235,186,.32);background:#090f0df2;color:#ecf3e1;padding:10px 12px;font-size:14px}.dialog-grid{margin-top:12px;display:grid;grid-template-columns:1fr 1fr;gap:12px}.dialog-section-label{font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;margin-bottom:8px;color:#e6ecd8d6}.option-list{display:flex;flex-direction:column;gap:6px;max-height:340px;overflow:auto}.option-btn,.palette-item{border:1px solid rgba(208,224,181,.22);border-radius:8px;background:#14221be6;color:#f1f5e6;text-align:left;padding:8px 10px;cursor:pointer;display:flex;flex-direction:column;gap:2px}.option-btn small,.palette-item small{color:#e2ebd2b8;font-size:11px}.option-btn.selected,.palette-item.active{border-color:#f0f3c8c7;background:#354f40eb}.dialog-actions{margin-top:14px;display:flex;justify-content:flex-end;gap:8px}.shortcuts-dialog{width:min(720px,calc(100vw - 34px))}.shortcut-grid{display:grid;grid-template-columns:1fr;gap:8px}.shortcut-row{display:grid;grid-template-columns:140px 1fr;gap:10px;align-items:center;padding:8px 10px;border-radius:8px;border:1px solid rgba(214,229,188,.16);background:#101a15d1}kbd{display:inline-block;min-width:52px;padding:4px 7px;border-radius:6px;border:1px solid rgba(233,242,206,.36);background:#0a0e0ceb;color:#edf4dd;font-size:12px;font-weight:700;text-align:center}.rename-dialog{width:min(540px,calc(100vw - 34px))}.rename-subtitle{margin-top:-2px;margin-bottom:10px;color:#e3ebd3c2;font-size:13px}.rename-form{display:flex;flex-direction:column}.confirm-dialog{width:min(520px,calc(100vw - 34px))}.confirm-copy{color:#e7eed9d9;font-size:14px;line-height:1.4}.confirm-hint{margin-top:8px;color:#d6e0c0c2;font-size:12px}.palette{width:min(760px,calc(100vw - 30px));border-radius:12px;border:1px solid rgba(222,236,193,.26);background:linear-gradient(180deg,#121e18f7,#0d1411fa);padding:12px}.palette-list{margin-top:8px;max-height:410px;overflow:auto;display:flex;flex-direction:column;gap:6px}.palette-empty{padding:12px;font-size:13px;color:#e2ebd2cc}.error-toast{position:fixed;right:14px;bottom:14px;z-index:40;max-width:420px;border:1px solid rgba(242,169,158,.58);background:#4b1713e6;border-radius:8px;padding:10px 12px;cursor:pointer}.batch-spawn-dialog{width:min(640px,calc(100vw - 34px))}.batch-spawn-config-list{margin-top:8px}.batch-spawn-config-summary{display:flex;align-items:center;gap:10px;margin-bottom:10px}.batch-spawn-config-summary small{color:#e2ebd2b8;font-size:12px}.batch-spawn-back{border:1px solid rgba(207,224,181,.34);border-radius:8px;background:#20342af2;color:#f0f5e3;width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;font-size:16px;cursor:pointer}.batch-spawn-back:hover{background:#345041f2}.batch-spawn-textarea{min-height:160px;resize:vertical;font-family:Consolas,Monaco,Lucida Console,monospace;line-height:1.4}.batch-spawn-footer{margin-top:12px;display:flex;align-items:center;justify-content:space-between;gap:10px}.batch-spawn-count{font-size:13px;color:#e2ebd2c7}.batch-spawn-progress{display:flex;align-items:center;gap:10px;flex:1}.batch-spawn-progress-bar{flex:1;height:6px;border-radius:3px;background:#daebba2e;overflow:hidden}.batch-spawn-progress-fill{height:100%;border-radius:3px;background:#8cdcaad9;transition:width .15s ease}.batch-spawn-progress-text{font-size:12px;color:#e2ebd2d1;white-space:nowrap}@media(max-width:960px){.app-shell{grid-template-columns:1fr;grid-template-rows:1fr minmax(280px,38vh)}.dialog-grid{grid-template-columns:1fr}}
@@ -4,8 +4,8 @@
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-HfXsReQG.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-CWU29xaz.css">
7
+ <script type="module" crossorigin src="/assets/index-BCnWppkv.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-Di_KBFPW.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
@@ -13,12 +13,16 @@ const statusMonitor_1 = require("../status/statusMonitor");
13
13
  const tmuxAdapter_1 = require("../tmux/tmuxAdapter");
14
14
  const realtimeHub_1 = require("../ws/realtimeHub");
15
15
  const terminalBridge_1 = require("../ws/terminalBridge");
16
- async function createServerContext() {
17
- const paths = (0, loadConfig_1.getArcaneAgentsPaths)();
16
+ async function createServerContext(sessionName) {
17
+ const paths = (0, loadConfig_1.getArcaneAgentsPaths)(sessionName);
18
18
  node_fs_1.default.mkdirSync(paths.configDir, { recursive: true });
19
19
  node_fs_1.default.mkdirSync(paths.stateDir, { recursive: true });
20
20
  node_fs_1.default.mkdirSync(paths.cacheDir, { recursive: true });
21
21
  const baseConfig = (0, loadConfig_1.loadResolvedConfig)(paths);
22
+ if ((0, loadConfig_1.isNonDefaultSession)(sessionName)) {
23
+ const baseTmuxName = baseConfig.backend.tmux.sessionName;
24
+ baseConfig.backend.tmux.sessionName = `${baseTmuxName}-${sessionName}`;
25
+ }
22
26
  const discoveryService = new discovery_1.DiscoveryService();
23
27
  const initialDiscovery = await discoveryService.discover(baseConfig);
24
28
  for (const warning of initialDiscovery.warnings) {
@@ -45,6 +49,9 @@ async function createServerContext() {
45
49
  const terminalBridge = new terminalBridge_1.TerminalBridge(workers, {
46
50
  onSubmittedInput: () => {
47
51
  statusMonitor.requestPollSoon();
52
+ },
53
+ onTerminalOutput: () => {
54
+ statusMonitor.requestPollSoon(20);
48
55
  }
49
56
  });
50
57
  return {
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.bootstrap = bootstrap;
7
7
  const node_http_1 = __importDefault(require("node:http"));
8
8
  const httpApp_1 = require("./bootstrap/httpApp");
9
+ const loadConfig_1 = require("./config/loadConfig");
9
10
  const serverContext_1 = require("./bootstrap/serverContext");
10
11
  const shutdown_1 = require("./bootstrap/shutdown");
11
12
  const websocketUpgrade_1 = require("./bootstrap/websocketUpgrade");
@@ -61,10 +62,15 @@ function renderStartupBanner() {
61
62
  })
62
63
  .join("\n");
63
64
  }
64
- async function bootstrap() {
65
+ async function bootstrap(sessionName) {
65
66
  console.log(renderStartupBanner());
66
- console.log("[arcane-agents] launching Arcane Agents...");
67
- const context = await (0, serverContext_1.createServerContext)();
67
+ if ((0, loadConfig_1.isNonDefaultSession)(sessionName)) {
68
+ console.log(`[arcane-agents] launching Arcane Agents (session: ${sessionName})...`);
69
+ }
70
+ else {
71
+ console.log("[arcane-agents] launching Arcane Agents...");
72
+ }
73
+ const context = await (0, serverContext_1.createServerContext)(sessionName);
68
74
  context.statusMonitor.start();
69
75
  const app = (0, httpApp_1.createHttpApp)(context);
70
76
  const server = node_http_1.default.createServer(app);
@@ -7,13 +7,41 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const node_child_process_1 = require("node:child_process");
8
8
  const node_fs_1 = __importDefault(require("node:fs"));
9
9
  const node_path_1 = __importDefault(require("node:path"));
10
+ const node_readline_1 = __importDefault(require("node:readline"));
10
11
  const bootstrapApp_1 = require("./bootstrapApp");
11
12
  const loadConfig_1 = require("./config/loadConfig");
12
13
  const appRoot_1 = require("./utils/appRoot");
13
14
  const path_1 = require("./utils/path");
15
+ function extractSessionFlag(args) {
16
+ const remaining = [...args];
17
+ let sessionName;
18
+ for (let i = 0; i < remaining.length; i++) {
19
+ if (remaining[i] === "--session" || remaining[i] === "-s") {
20
+ const value = remaining[i + 1];
21
+ if (!value || value.startsWith("-")) {
22
+ console.error("[arcane-agents] --session requires a name argument.");
23
+ process.exit(1);
24
+ }
25
+ sessionName = value;
26
+ remaining.splice(i, 2);
27
+ break;
28
+ }
29
+ const eqMatch = remaining[i].match(/^(?:--session|-s)=(.+)$/);
30
+ if (eqMatch) {
31
+ sessionName = eqMatch[1];
32
+ remaining.splice(i, 1);
33
+ break;
34
+ }
35
+ }
36
+ if (sessionName !== undefined && !/^[a-zA-Z0-9_-]+$/.test(sessionName)) {
37
+ console.error("[arcane-agents] session name must only contain letters, digits, hyphens, and underscores.");
38
+ process.exit(1);
39
+ }
40
+ return { sessionName, remainingArgs: remaining };
41
+ }
14
42
  async function runCli() {
15
43
  (0, appRoot_1.setAppRoot)((0, appRoot_1.resolveAppRoot)());
16
- const args = process.argv.slice(2);
44
+ const { sessionName, remainingArgs: args } = extractSessionFlag(process.argv.slice(2));
17
45
  const firstArg = args[0];
18
46
  if (firstArg === "--help" || firstArg === "-h") {
19
47
  printHelp();
@@ -26,13 +54,15 @@ async function runCli() {
26
54
  const [command = "start", ...commandArgs] = args;
27
55
  switch (command) {
28
56
  case "start":
29
- return runStart();
57
+ return runStart(sessionName);
30
58
  case "init":
31
59
  return runInit(commandArgs);
32
60
  case "config":
33
61
  return runConfig(commandArgs);
34
62
  case "doctor":
35
63
  return runDoctor();
64
+ case "sessions":
65
+ return runSessions(commandArgs);
36
66
  case "help":
37
67
  printHelp();
38
68
  return 0;
@@ -45,11 +75,11 @@ async function runCli() {
45
75
  return 1;
46
76
  }
47
77
  }
48
- async function runStart() {
78
+ async function runStart(sessionName) {
49
79
  if (!process.env.NODE_ENV) {
50
80
  process.env.NODE_ENV = "production";
51
81
  }
52
- const paths = (0, loadConfig_1.getArcaneAgentsPaths)();
82
+ const paths = (0, loadConfig_1.getArcaneAgentsPaths)(sessionName);
53
83
  let configResult;
54
84
  try {
55
85
  configResult = ensureStarterConfig(paths);
@@ -63,7 +93,7 @@ async function runStart() {
63
93
  console.log(`[arcane-agents] no config found; wrote starter config to ${paths.configPath}`);
64
94
  console.log("[arcane-agents] next: edit it with 'arcane-agents config edit'.");
65
95
  }
66
- await (0, bootstrapApp_1.bootstrap)();
96
+ await (0, bootstrapApp_1.bootstrap)(sessionName);
67
97
  return 0;
68
98
  }
69
99
  function runInit(args) {
@@ -235,6 +265,85 @@ Commands:
235
265
  help Show this config help message
236
266
  `);
237
267
  }
268
+ async function runSessions(args) {
269
+ const [subcommand = "list", ...subcommandArgs] = args;
270
+ switch (subcommand) {
271
+ case "list":
272
+ break;
273
+ case "delete":
274
+ return runSessionsDelete(subcommandArgs);
275
+ default:
276
+ console.error(`[arcane-agents] unknown sessions command '${subcommand}'.`);
277
+ console.log("Usage: arcane-agents sessions [list|delete <name>]");
278
+ return 1;
279
+ }
280
+ const defaultPaths = (0, loadConfig_1.getArcaneAgentsPaths)();
281
+ const sessionsDir = node_path_1.default.join(defaultPaths.stateDir, "sessions");
282
+ const defaultDbPath = defaultPaths.dbPath;
283
+ const sessions = [];
284
+ if (node_fs_1.default.existsSync(defaultDbPath)) {
285
+ sessions.push("default");
286
+ }
287
+ if (node_fs_1.default.existsSync(sessionsDir)) {
288
+ try {
289
+ const entries = node_fs_1.default.readdirSync(sessionsDir, { withFileTypes: true });
290
+ for (const entry of entries) {
291
+ if (entry.isDirectory()) {
292
+ const dbPath = node_path_1.default.join(sessionsDir, entry.name, "arcane-agents.db");
293
+ if (node_fs_1.default.existsSync(dbPath)) {
294
+ sessions.push(entry.name);
295
+ }
296
+ }
297
+ }
298
+ }
299
+ catch {
300
+ // no-op
301
+ }
302
+ }
303
+ if (sessions.length === 0) {
304
+ console.log("[arcane-agents] no sessions found.");
305
+ }
306
+ else {
307
+ console.log("[arcane-agents] sessions:");
308
+ for (const session of sessions) {
309
+ console.log(` ${session}`);
310
+ }
311
+ }
312
+ return 0;
313
+ }
314
+ async function runSessionsDelete(args) {
315
+ const name = args[0];
316
+ if (!name) {
317
+ console.error("[arcane-agents] usage: arcane-agents sessions delete <name>");
318
+ return 1;
319
+ }
320
+ if (name === "default") {
321
+ console.error("[arcane-agents] cannot delete the default session.");
322
+ return 1;
323
+ }
324
+ const sessionDir = (0, loadConfig_1.getArcaneAgentsPaths)(name).stateDir;
325
+ if (!node_fs_1.default.existsSync(sessionDir)) {
326
+ console.error(`[arcane-agents] session '${name}' not found.`);
327
+ return 1;
328
+ }
329
+ const answer = await promptConfirm(`Delete session '${name}' and all its data (${sessionDir})? [y/N] `);
330
+ if (!answer) {
331
+ console.log("[arcane-agents] aborted.");
332
+ return 0;
333
+ }
334
+ node_fs_1.default.rmSync(sessionDir, { recursive: true, force: true });
335
+ console.log(`[arcane-agents] deleted session '${name}'.`);
336
+ return 0;
337
+ }
338
+ function promptConfirm(question) {
339
+ const rl = node_readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
340
+ return new Promise((resolve) => {
341
+ rl.question(question, (answer) => {
342
+ rl.close();
343
+ resolve(answer.trim().toLowerCase() === "y");
344
+ });
345
+ });
346
+ }
238
347
  function runDoctor() {
239
348
  const checks = [];
240
349
  const nodeVersion = process.versions.node;
@@ -357,9 +466,10 @@ function printHelp() {
357
466
  console.log(`Arcane Agents CLI
358
467
 
359
468
  Usage:
360
- arcane-agents [start]
469
+ arcane-agents [start] [--session <name>]
361
470
  arcane-agents init [--force]
362
471
  arcane-agents config [path|show|edit]
472
+ arcane-agents sessions [list|delete <name>]
363
473
  arcane-agents doctor
364
474
  arcane-agents --help
365
475
  arcane-agents --version
@@ -368,10 +478,16 @@ Commands:
368
478
  start Start the Arcane Agents server
369
479
  init Write ~/.config/arcane-agents/config.yaml from config.example.yaml
370
480
  config Print, show, or edit config files
481
+ sessions List or delete named sessions
371
482
  doctor Check dependencies and runtime command availability
372
483
  help Show this help message
373
484
  version Print CLI version
374
485
 
486
+ Options:
487
+ --session <name>, -s <name>
488
+ Run with a named session (separate DB and tmux session).
489
+ Default session uses the standard paths for backwards compatibility.
490
+
375
491
  Config paths:
376
492
  primary: ${paths.configPath}
377
493
  local override: ${paths.localOverridePath}
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isNonDefaultSession = isNonDefaultSession;
6
7
  exports.getArcaneAgentsPaths = getArcaneAgentsPaths;
7
8
  exports.loadResolvedConfig = loadResolvedConfig;
8
9
  const node_fs_1 = __importDefault(require("node:fs"));
@@ -10,9 +11,16 @@ const node_path_1 = __importDefault(require("node:path"));
10
11
  const yaml_1 = __importDefault(require("yaml"));
11
12
  const schema_1 = require("./schema");
12
13
  const path_1 = require("../utils/path");
13
- function getArcaneAgentsPaths() {
14
+ function isNonDefaultSession(sessionName) {
15
+ return sessionName !== undefined && sessionName !== "default";
16
+ }
17
+ function getArcaneAgentsPaths(sessionName) {
14
18
  const configDir = (0, path_1.resolveUserPath)("~/.config/arcane-agents");
15
- const stateDir = (0, path_1.resolveUserPath)("~/.local/state/arcane-agents");
19
+ const baseStateDir = (0, path_1.resolveUserPath)("~/.local/state/arcane-agents");
20
+ const isNamedSession = sessionName !== undefined && sessionName !== "default";
21
+ const stateDir = isNamedSession
22
+ ? node_path_1.default.join(baseStateDir, "sessions", sessionName)
23
+ : baseStateDir;
16
24
  return {
17
25
  configDir,
18
26
  configPath: node_path_1.default.join(configDir, "config.yaml"),
@@ -76,6 +76,16 @@ function registerApiRoutes(app, { orchestrator, hub, statusMonitor }) {
76
76
  (0, errorResponse_1.handleRequestError)(res, error);
77
77
  }
78
78
  });
79
+ app.post("/api/workers/:workerId/restart", async (req, res) => {
80
+ try {
81
+ const worker = await orchestrator.restart(req.params.workerId);
82
+ hub.broadcast({ type: "worker-updated", worker });
83
+ res.json(worker);
84
+ }
85
+ catch (error) {
86
+ (0, errorResponse_1.handleRequestError)(res, error);
87
+ }
88
+ });
79
89
  app.patch("/api/workers/:workerId/position", (req, res) => {
80
90
  try {
81
91
  const x = Number(req.body?.x);
@@ -102,6 +102,35 @@ class OrchestratorService {
102
102
  alreadyStopped: !removed
103
103
  };
104
104
  }
105
+ async restart(workerId) {
106
+ const worker = this.requireWorker(workerId);
107
+ try {
108
+ await this.tmux.stop(worker.tmuxRef);
109
+ const tmuxRef = await this.tmux.spawnWorker({
110
+ workerId: worker.id,
111
+ windowName: worker.name,
112
+ projectPath: worker.projectPath,
113
+ command: worker.command,
114
+ projectId: worker.projectId,
115
+ runtimeId: worker.runtimeId,
116
+ runtimeLabel: worker.runtimeLabel
117
+ });
118
+ const restarted = {
119
+ ...worker,
120
+ status: "idle",
121
+ activityText: undefined,
122
+ activityTool: undefined,
123
+ activityPath: undefined,
124
+ tmuxRef,
125
+ updatedAt: new Date().toISOString()
126
+ };
127
+ this.workers.saveWorker(restarted);
128
+ return restarted;
129
+ }
130
+ catch {
131
+ throw (0, appError_1.conflictError)(`Failed to restart agent '${workerId}'.`, "worker_restart_failed");
132
+ }
133
+ }
105
134
  updatePosition(workerId, position) {
106
135
  const updated = this.workers.updatePosition(workerId, position);
107
136
  if (!updated) {
@@ -112,3 +112,61 @@ function createWorker() {
112
112
  (0, vitest_1.expect)(workers.deleteWorker).not.toHaveBeenCalled();
113
113
  });
114
114
  });
115
+ (0, vitest_1.describe)("OrchestratorService.restart", () => {
116
+ (0, vitest_1.it)("restarts tmux in place and preserves the worker record", async () => {
117
+ const worker = createWorker();
118
+ const workers = {
119
+ getWorker: vitest_1.vi.fn(() => worker),
120
+ saveWorker: vitest_1.vi.fn()
121
+ };
122
+ const nextTmuxRef = { session: "arcane-agents", window: "worker-1", pane: "%7" };
123
+ const tmux = {
124
+ stop: vitest_1.vi.fn(async () => undefined),
125
+ spawnWorker: vitest_1.vi.fn(async () => nextTmuxRef)
126
+ };
127
+ const service = new orchestratorService_1.OrchestratorService(createConfig(), workers, tmux);
128
+ const result = await service.restart(worker.id);
129
+ (0, vitest_1.expect)(tmux.stop).toHaveBeenCalledWith(worker.tmuxRef);
130
+ (0, vitest_1.expect)(tmux.spawnWorker).toHaveBeenCalledWith({
131
+ workerId: worker.id,
132
+ windowName: worker.name,
133
+ projectPath: worker.projectPath,
134
+ command: worker.command,
135
+ projectId: worker.projectId,
136
+ runtimeId: worker.runtimeId,
137
+ runtimeLabel: worker.runtimeLabel
138
+ });
139
+ (0, vitest_1.expect)(result).toMatchObject({
140
+ ...worker,
141
+ status: "idle",
142
+ tmuxRef: nextTmuxRef,
143
+ activityText: undefined,
144
+ activityTool: undefined,
145
+ activityPath: undefined,
146
+ updatedAt: vitest_1.expect.any(String)
147
+ });
148
+ (0, vitest_1.expect)(workers.saveWorker).toHaveBeenCalledWith(result);
149
+ const stopCallOrder = tmux.stop.mock.invocationCallOrder[0] ?? 0;
150
+ const spawnCallOrder = tmux.spawnWorker.mock.invocationCallOrder[0] ?? 0;
151
+ (0, vitest_1.expect)(stopCallOrder).toBeLessThan(spawnCallOrder);
152
+ });
153
+ (0, vitest_1.it)("surfaces a conflict when restart fails", async () => {
154
+ const worker = createWorker();
155
+ const workers = {
156
+ getWorker: vitest_1.vi.fn(() => worker),
157
+ saveWorker: vitest_1.vi.fn()
158
+ };
159
+ const tmux = {
160
+ stop: vitest_1.vi.fn(async () => {
161
+ throw new Error("tmux failure");
162
+ }),
163
+ spawnWorker: vitest_1.vi.fn()
164
+ };
165
+ const service = new orchestratorService_1.OrchestratorService(createConfig(), workers, tmux);
166
+ await (0, vitest_1.expect)(service.restart(worker.id)).rejects.toMatchObject({
167
+ status: 409,
168
+ code: "worker_restart_failed"
169
+ });
170
+ (0, vitest_1.expect)(workers.saveWorker).not.toHaveBeenCalled();
171
+ });
172
+ });
@@ -12,7 +12,7 @@ const toolMatchers = [
12
12
  { tool: "task", label: "Subtask", regex: /\b(Task|subagent|agent)\b/i },
13
13
  { tool: "todo", label: "Planning", regex: /\b(TodoWrite|todo\b)\b/i },
14
14
  { tool: "web", label: "Fetching", regex: /\b(WebFetch|http|https)\b/i },
15
- { tool: "terminal", label: "Terminal", regex: /\b(claude|terminal|tmux)\b/i }
15
+ { tool: "terminal", label: "Terminal", regex: /\b(claude|codex|terminal|tmux)\b/i }
16
16
  ];
17
17
  const inputPromptLineMatchers = [
18
18
  /\[(?:Y\/n|y\/N|y\/n|N\/y)\]\s*$/i,
@@ -3,17 +3,24 @@ 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, interactiveCommands }) {
6
+ function buildWorkerStatusSignalContext({ worker, currentCommand, output, observation, transcriptSnapshot, runtimeProcess, nowMs, interactiveCommands }) {
7
7
  const parsed = (0, activityParser_1.parseActivity)(currentCommand, output);
8
8
  const commandLower = currentCommand.toLowerCase();
9
- const isClaude = (0, runtimeSignals_1.isLikelyClaudeSession)(worker, commandLower);
9
+ const wrappedRuntime = runtimeProcess?.runtime;
10
+ const isClaude = wrappedRuntime === "claude" || (0, runtimeSignals_1.isLikelyClaudeSession)(worker, commandLower);
11
+ const claudeSignals = (0, runtimeSignals_1.detectClaudeSignals)(output);
10
12
  const openCodeSignals = (0, runtimeSignals_1.detectOpenCodeSignals)(output);
11
- const isOpenCode = (0, runtimeSignals_1.isLikelyOpenCodeSession)(worker, commandLower) || openCodeSignals.prompt || openCodeSignals.active;
12
- const runtimeActivityText = (0, runtimeSignals_1.extractRuntimeActivityText)(output, { isClaude, isOpenCode });
13
- const activeClaudeTask = (0, runtimeSignals_1.extractClaudeActiveTask)(output);
14
- const hasClaudeProgressSignal = isClaude && (0, runtimeSignals_1.hasClaudeLiveProgressSignal)(output);
13
+ const codexSignals = (0, runtimeSignals_1.detectCodexSignals)(output);
14
+ const isOpenCode = wrappedRuntime === "opencode" || (0, runtimeSignals_1.isLikelyOpenCodeSession)(worker, commandLower) || openCodeSignals.prompt || openCodeSignals.active;
15
+ const isCodex = wrappedRuntime === "codex" || (0, runtimeSignals_1.isLikelyCodexSession)(worker, commandLower) || codexSignals.prompt || codexSignals.active;
16
+ const runtimeActivityText = (0, runtimeSignals_1.extractRuntimeActivityText)(output, { isClaude, isOpenCode, isCodex });
17
+ const activeClaudeTask = isClaude ? (0, runtimeSignals_1.extractClaudeActiveTask)(output) : undefined;
18
+ const hasClaudePromptSignal = isClaude && claudeSignals.prompt;
19
+ const hasClaudeProgressSignal = isClaude && claudeSignals.active;
15
20
  const openCodePromptSignal = isOpenCode && openCodeSignals.prompt;
16
21
  const openCodeActiveSignal = isOpenCode && openCodeSignals.active;
22
+ const codexPromptSignal = isCodex && codexSignals.prompt;
23
+ const codexActiveSignal = isCodex && codexSignals.active;
17
24
  const outputQuietForMs = Math.max(0, nowMs - observation.lastOutputChangeAtMs);
18
25
  const commandQuietForMs = Math.max(0, nowMs - observation.lastCommandChangeAtMs);
19
26
  const createdAtMs = Date.parse(worker.createdAt);
@@ -29,11 +36,16 @@ function buildWorkerStatusSignalContext({ worker, currentCommand, output, observ
29
36
  parsed,
30
37
  runtimeActivityText,
31
38
  activeClaudeTask,
39
+ activeRuntimeProcess: runtimeProcess,
40
+ hasClaudePromptSignal,
32
41
  hasClaudeProgressSignal,
33
42
  hasOpenCodePromptSignal: openCodePromptSignal,
34
43
  hasOpenCodeActiveSignal: openCodeActiveSignal,
44
+ hasCodexPromptSignal: codexPromptSignal,
45
+ hasCodexActiveSignal: codexActiveSignal,
35
46
  isClaudeSession: isClaude,
36
47
  isOpenCodeSession: isOpenCode,
48
+ isCodexSession: isCodex,
37
49
  outputQuietForMs,
38
50
  commandQuietForMs,
39
51
  workerAgeMs,
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.recoverableToolErrorMatchers = exports.fatalRuntimeErrorMatchers = exports.openCodeWorkingFreshWindowMs = exports.claudeWorkingFreshWindowMs = exports.genericWorkingFreshWindowMs = exports.openCodeSpawnGraceMs = exports.claudeSpawnGraceMs = exports.cachedActivityWindowMs = exports.stickyWorkingWindowMs = exports.commandWarmupWindowMs = exports.recentErrorSignalWindowMs = exports.parsedStrongEvidenceWindowMs = void 0;
3
+ exports.recoverableToolErrorMatchers = exports.fatalRuntimeErrorMatchers = exports.codexWorkingFreshWindowMs = exports.openCodeWorkingFreshWindowMs = exports.claudeWorkingFreshWindowMs = exports.genericWorkingFreshWindowMs = exports.codexSpawnGraceMs = exports.openCodeSpawnGraceMs = 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;
@@ -15,12 +15,16 @@ const claudeSpawnGraceMs = 5_000;
15
15
  exports.claudeSpawnGraceMs = claudeSpawnGraceMs;
16
16
  const openCodeSpawnGraceMs = 5_000;
17
17
  exports.openCodeSpawnGraceMs = openCodeSpawnGraceMs;
18
+ const codexSpawnGraceMs = 5_000;
19
+ exports.codexSpawnGraceMs = codexSpawnGraceMs;
18
20
  const genericWorkingFreshWindowMs = 12_000;
19
21
  exports.genericWorkingFreshWindowMs = genericWorkingFreshWindowMs;
20
22
  const claudeWorkingFreshWindowMs = 10_000;
21
23
  exports.claudeWorkingFreshWindowMs = claudeWorkingFreshWindowMs;
22
24
  const openCodeWorkingFreshWindowMs = 12_000;
23
25
  exports.openCodeWorkingFreshWindowMs = openCodeWorkingFreshWindowMs;
26
+ const codexWorkingFreshWindowMs = 10_000;
27
+ exports.codexWorkingFreshWindowMs = codexWorkingFreshWindowMs;
24
28
  const fatalRuntimeErrorMatchers = [
25
29
  /^traceback\b/i,
26
30
  /^unhandled(?:\s+\w+)?\s+exception\b/i,
@@ -26,6 +26,18 @@ function deriveWorkerStatusDecision(context) {
26
26
  parsedStrongSignal: false
27
27
  });
28
28
  }
29
+ if (context.hasCodexPromptSignal && !context.hasCodexActiveSignal && !(0, helpers_1.isInteractiveCommand)(context)) {
30
+ pushReason({ code: "codex-approval-prompt", message: "Codex is waiting on an approval or question response." });
31
+ return finalizeDecision(context, {
32
+ status: "attention",
33
+ activityText: context.runtimeActivityText ?? context.parsed.activity.text ?? "Waiting for approval",
34
+ activityTool: "terminal",
35
+ activityPath: undefined,
36
+ confidence: 0.94,
37
+ reasons,
38
+ parsedStrongSignal: false
39
+ });
40
+ }
29
41
  if (context.parsed.activity.needsInput && !(0, helpers_1.isInteractiveCommand)(context)) {
30
42
  pushReason({ code: "parser-input-prompt", message: "Terminal output indicates input is required." });
31
43
  return finalizeDecision(context, {
@@ -200,10 +212,15 @@ function finalizeDecision(context, partial) {
200
212
  workerAgeMs: context.workerAgeMs,
201
213
  isClaudeSession: context.isClaudeSession,
202
214
  isOpenCodeSession: context.isOpenCodeSession,
215
+ isCodexSession: context.isCodexSession,
216
+ hasClaudePromptSignal: context.hasClaudePromptSignal,
203
217
  hasOpenCodePromptSignal: context.hasOpenCodePromptSignal,
204
218
  hasOpenCodeActiveSignal: context.hasOpenCodeActiveSignal,
219
+ hasCodexPromptSignal: context.hasCodexPromptSignal,
220
+ hasCodexActiveSignal: context.hasCodexActiveSignal,
205
221
  hasClaudeProgressSignal: context.hasClaudeProgressSignal,
206
222
  hasActiveClaudeTask: Boolean(context.activeClaudeTask),
223
+ hasActiveRuntimeProcess: Boolean(context.activeRuntimeProcess),
207
224
  hasRuntimeActivityText: Boolean(context.runtimeActivityText),
208
225
  hasParsedStrongSignal: partial.parsedStrongSignal,
209
226
  hasParsedNeedsInput: context.parsed.activity.needsInput,