agent-dag 1.30.6 → 1.30.8
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 +17 -0
- package/bin/agent-dag.js +18 -0
- package/dist/web/assets/index-1i-Vckvo.js +66 -0
- package/dist/web/assets/index-CEAcuttN.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/src/server/claude-accounts.mjs +128 -14
- package/src/server/index.mjs +17 -1
- package/src/server/self-update.mjs +179 -0
- package/dist/web/assets/index-BHfNMFBX.css +0 -1
- package/dist/web/assets/index-CBP4rn8o.js +0 -66
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.react-flow{direction:ltr}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1;cursor:-webkit-grab;cursor:grab}.react-flow__pane.selection{cursor:pointer}.react-flow__pane.dragging{cursor:-webkit-grabbing;cursor:grabbing}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow .react-flow__edges{pointer-events:none;overflow:visible}.react-flow__edge-path,.react-flow__connection-path{stroke:#b1b1b7;stroke-width:1;fill:none}.react-flow__edge{pointer-events:visibleStroke;cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;-webkit-animation:dashdraw .5s linear infinite;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;-webkit-animation:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge:focus .react-flow__edge-path,.react-flow__edge:focus-visible .react-flow__edge-path{stroke:#555}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge-textbg{fill:#fff}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;-webkit-animation:dashdraw .5s linear infinite;animation:dashdraw .5s linear infinite}.react-flow__connectionline{z-index:1001}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:-webkit-grab;cursor:grab}.react-flow__node.dragging{cursor:-webkit-grabbing;cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:-webkit-grab;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background:#1a192b;border:1px solid white;border-radius:100%}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:-4px;transform:translate(-50%)}.react-flow__handle-top{left:50%;top:-4px;transform:translate(-50%)}.react-flow__handle-left{top:50%;left:-4px;transform:translateY(-50%)}.react-flow__handle-right{right:-4px;top:50%;transform:translateY(-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.center{left:50%;transform:translate(-50%)}.react-flow__attribution{font-size:10px;background:#ffffff80;padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@-webkit-keyframes dashdraw{0%{stroke-dashoffset:10}}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-default,.react-flow__node-input,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:3px;width:150px;font-size:12px;color:#222;text-align:center;border-width:1px;border-style:solid;border-color:#1a192b;background-color:#fff}.react-flow__node-default.selectable:hover,.react-flow__node-input.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:0 1px 4px 1px #00000014}.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:0 0 0 .5px #1a192b}.react-flow__node-group{background-color:#f0f0f040}.react-flow__nodesselection-rect,.react-flow__selection{background:#0059dc14;border:1px dotted rgba(0,89,220,.8)}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls{box-shadow:0 0 2px 1px #00000014}.react-flow__controls-button{border:none;background:#fefefe;border-bottom:1px solid #eee;box-sizing:content-box;display:flex;justify-content:center;align-items:center;width:16px;height:16px;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;padding:5px}.react-flow__controls-button:hover{background:#f4f4f4}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__minimap{background-color:#fff}.react-flow__minimap svg{display:block}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:4px;height:4px;border:1px solid #fff;border-radius:1px;background-color:#3367d9;transform:translate(-50%,-50%)}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:#3367d9;border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}:root,:root[data-theme=dark]{--bg: #0b0c10;--bg-soft: #0f1116;--panel: #14161b;--line: #1f2229;--line-soft: #1a1c22;--text: #d8dae0;--muted: #7e828c;--muted-dim: #50535b;--accent: #7dd3fc;--accent-dim: #38bdf850;--ok: #86efac;--warn: #fcd34d;--err: #fca5a5;--inflight: #f0abfc;--shadow-1: 0 6px 18px rgba(0,0,0,.25);--shadow-2: 0 10px 24px rgba(0,0,0,.32);--node-grad: linear-gradient(180deg, var(--panel), var(--bg-soft));--topbar-grad: linear-gradient(to bottom, var(--panel), var(--bg-soft));--bg-grid-1: rgba(125,211,252,.06);--bg-grid-2: rgba(240,171,252,.05);--grid-line: #1a1d24;--minimap-mask: rgba(11,12,16,.85);color-scheme:dark}:root[data-theme=light]{--bg: #eef1f6;--bg-soft: #ffffff;--panel: #ffffff;--line: #c8cdd6;--line-soft: #dde1e8;--text: #0d1117;--muted: #4a5260;--muted-dim: #7c8493;--accent: #0369a1;--accent-dim: rgba(3,105,161,.22);--ok: #15803d;--warn: #b45309;--err: #b91c1c;--inflight: #7e22ce;--shadow-1: 0 4px 14px rgba(15,23,42,.1);--shadow-2: 0 10px 28px rgba(15,23,42,.16);--node-grad: linear-gradient(180deg, #ffffff, #f2f5fa);--topbar-grad: linear-gradient(to bottom, #ffffff, #eef1f6);--bg-grid-1: rgba(3,105,161,.1);--bg-grid-2: rgba(126,34,206,.08);--grid-line: #d0d5dd;--minimap-mask: rgba(238,241,246,.85);color-scheme:light}*{box-sizing:border-box}html,body,#root{margin:0;height:100%;background:var(--bg);color:var(--text);font:13px/1.45 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;-webkit-font-smoothing:antialiased;overscroll-behavior:none}*::-webkit-scrollbar{width:10px;height:10px}*::-webkit-scrollbar-track{background:transparent}*::-webkit-scrollbar-thumb{background:var(--line);border:2px solid var(--bg);border-radius:999px}*::-webkit-scrollbar-thumb:hover{background:var(--muted-dim)}*{scrollbar-color:var(--line) transparent;scrollbar-width:thin}:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:4px}button:focus-visible,.search input:focus-visible{outline-offset:1px}.app{display:grid;grid-template-columns:1fr 360px;grid-template-rows:52px auto 1fr;height:100vh}.app:has(.session-list){grid-template-columns:240px 1fr 360px}.app:has(.accounts-panel){grid-template-columns:288px 1fr 360px}.app:has(.session-list) .canvas-wrap,.app:has(.accounts-panel) .canvas-wrap{grid-column:2}.app:has(.session-list) .conn-banner,.app:has(.accounts-panel) .conn-banner,.app:has(.session-list) .ver-banner,.app:has(.accounts-panel) .ver-banner{grid-column:2}.canvas-wrap{grid-column:1;grid-row:3}.detail{grid-column:2;grid-row:2 / -1}.app:has(.session-list) .detail{grid-column:3}.topbar{grid-column:1 / -1;display:flex;align-items:center;justify-content:space-between;padding:0 18px;background:var(--topbar-grad);border-bottom:1px solid var(--line);-webkit-user-select:none;user-select:none}.topbar .brand{font-weight:600;letter-spacing:.02em;color:var(--text);display:flex;align-items:center;gap:10px;font-size:14px;white-space:nowrap;flex:none}.topbar .brand .logo{width:14px;height:14px;border-radius:4px;background:conic-gradient(from 220deg,#7dd3fc,#f0abfc,#86efac,#7dd3fc);box-shadow:0 0 12px #7dd3fc73}.topbar .brand .v{color:var(--muted);font-weight:400;font-size:11px;margin-left:2px}.topbar .brand button.v{display:inline-flex;align-items:center;flex:none;gap:5px;padding:2px 7px;border:1px solid transparent;border-radius:999px;background:none;font:inherit;font-size:11px;font-weight:500;cursor:pointer;transition:background .13s,border-color .13s,color .13s}.topbar .brand button.v.stale{color:var(--warn);border-color:color-mix(in srgb,var(--warn) 34%,transparent);background:color-mix(in srgb,var(--warn) 10%,transparent)}.topbar .brand button.v.stale:hover{border-color:color-mix(in srgb,var(--warn) 55%,transparent);background:color-mix(in srgb,var(--warn) 18%,transparent)}.topbar .brand button.v .v-dot{width:5px;height:5px;border-radius:50%;background:var(--warn);box-shadow:0 0 7px var(--warn);animation:pulse 2.4s infinite}.topbar .actions{display:flex;gap:8px;align-items:center}.selected-ribbon{display:inline-flex;align-items:center;gap:8px;padding:4px 6px 4px 10px;margin:0 12px 0 4px;background:var(--bg-soft);border:1px solid var(--line);border-radius:999px;color:var(--text);font:inherit;font-size:12px;cursor:pointer;max-width:380px;min-width:0;transition:border-color .12s,background .12s}.selected-ribbon:hover{border-color:var(--accent-dim);background:var(--bg)}.selected-ribbon:focus-visible{outline:2px solid var(--accent);outline-offset:1px}.selected-ribbon .selected-label{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;flex:0 1 auto}.selected-ribbon .selected-cost{color:var(--ok);font-variant-numeric:tabular-nums;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px}.selected-ribbon .selected-rate{color:var(--muted);font-size:10.5px}.selected-ribbon .selected-extra{display:inline-flex;align-items:center;padding:1px 6px;border-radius:999px;background:var(--accent-dim);color:var(--text);font-size:10.5px;font-weight:600;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.selected-ribbon .selected-close{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:999px;color:var(--muted);font-size:14px;line-height:1;cursor:pointer;transition:background .12s,color .12s}.selected-ribbon .selected-close:hover{background:var(--line);color:var(--text)}.topbar .status{color:var(--muted);font-size:12px;margin-right:6px;display:inline-flex;align-items:center;gap:14px}.topbar .status .stat{display:inline-flex;align-items:baseline;gap:4px;font-variant-numeric:tabular-nums}.topbar .status .stat .lbl{color:var(--muted)}.topbar .status .stat+.stat:before{content:"";display:inline-block;width:1px;height:12px;background:var(--line);margin-right:10px;position:relative;top:1px}.topbar .status .pill{display:inline-flex;align-items:center;gap:6px;padding:3px 8px;border-radius:999px;background:var(--bg-soft);border:1px solid var(--line);font-size:11px}.topbar .status .pill.live{color:var(--ok);border-color:#86efac40}.topbar .status .pill.live:before{content:"";display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--ok);box-shadow:0 0 6px var(--ok);animation:pulse 1.6s ease-in-out infinite}.topbar .status .pill.dead{color:var(--err);border-color:#fca5a559;background:#fca5a50f}.topbar .status .pill.dead:before{content:"";display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--err);box-shadow:0 0 6px var(--err)}.topbar .status .count{color:var(--text);font-variant-numeric:tabular-nums}.topbar .status .mcp-legend{display:inline-flex;align-items:center;gap:3px;cursor:default}.topbar .status .mcp-legend .mcp-dot{display:inline-block;width:8px;height:8px;border-radius:50%;border:1px solid rgba(0,0,0,.35);box-shadow:0 0 4px #00000040}.topbar .status .mcp-legend .mcp-more{font-size:9.5px;color:var(--muted);margin-left:1px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}@keyframes pulse{0%,to{opacity:1}50%{opacity:.35}}button.btn{background:transparent;color:var(--text);border:1px solid var(--line);padding:6px 12px;border-radius:6px;cursor:pointer;font:inherit;font-size:12px;transition:border-color .12s,color .12s,background .12s}button.btn:hover{border-color:var(--accent-dim);color:var(--accent)}button.btn:active{transform:translateY(1px)}button.btn.primary{background:var(--accent-dim);color:var(--text);border-color:var(--accent-dim)}button.btn.warn{border-color:#fcd34d80;color:var(--warn)}button.btn.warn:hover{border-color:var(--warn);color:var(--warn)}button.btn.icon-btn{width:30px;height:30px;padding:0;display:inline-flex;align-items:center;justify-content:center;font-size:14px;line-height:1}.search{position:relative;display:inline-flex;align-items:center;margin-right:4px}.search-icon{position:absolute;left:8px;color:var(--muted);font-size:14px;pointer-events:none}.search input{background:var(--bg-soft);color:var(--text);border:1px solid var(--line);padding:6px 26px 6px 24px;border-radius:6px;font:inherit;font-size:12px;min-width:220px;outline:none;transition:border-color .12s,box-shadow .12s}.search input::placeholder{color:var(--muted)}.search input:focus{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-dim)}.search-clear{position:absolute;right:4px;background:transparent;border:none;color:var(--muted);cursor:pointer;font-size:16px;padding:0 6px;line-height:1}.search-clear:hover{color:var(--text)}.search-kbd{position:absolute;right:6px;pointer-events:none;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;font-weight:600;color:var(--muted);background:var(--bg);border:1px solid var(--line);border-radius:4px;padding:0 5px;line-height:16px;height:16px}.search input:focus~.search-kbd{opacity:0}.conn-banner{grid-column:1;grid-row:2;display:flex;align-items:center;gap:10px;padding:8px 18px;background:linear-gradient(90deg,rgba(252,165,165,.12),transparent);border-bottom:1px solid rgba(252,165,165,.3);color:var(--err);font-size:12.5px;font-weight:500;animation:fadeIn .2s ease}.conn-banner .conn-dot{width:7px;height:7px;border-radius:50%;background:var(--err);box-shadow:0 0 8px var(--err);animation:pulse 1.2s infinite}.ver-banner{grid-column:1;grid-row:2;display:flex;align-items:center;gap:10px;padding:8px 18px;background:linear-gradient(90deg,color-mix(in srgb,var(--warn) 13%,transparent),transparent);border-bottom:1px solid color-mix(in srgb,var(--warn) 30%,transparent);color:var(--warn);font-size:12.5px;font-weight:500;animation:fadeIn .2s ease}.ver-banner .ver-dot{flex:none;width:7px;height:7px;border-radius:50%;background:var(--warn);box-shadow:0 0 8px var(--warn);animation:pulse 2.4s infinite}.ver-banner strong{font-weight:600}.ver-banner strong,.ver-banner .ver-sub{white-space:nowrap}.ver-banner .ver-sub{color:var(--muted);font-weight:400;min-width:0;overflow:hidden;text-overflow:ellipsis}.ver-banner .ver-cmd{display:inline-flex;align-items:center;gap:8px;padding:3px 5px 3px 9px;border:1px solid var(--line);border-radius:999px;background:var(--bg-soft);color:var(--text);cursor:pointer;transition:border-color .13s,background .13s}.ver-banner .ver-cmd:hover{border-color:var(--accent-dim);background:var(--panel)}.ver-banner .ver-cmd code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;-webkit-user-select:text;user-select:text}.ver-banner .ver-cmd-hint{font-size:10px;letter-spacing:.04em;text-transform:uppercase;color:var(--muted);background:var(--line);border-radius:999px;padding:2px 7px}.ver-banner .ver-close{display:inline-flex;align-items:center;justify-content:center;margin-left:2px;flex:none;width:18px;height:18px;border-radius:999px;color:var(--muted);font-size:14px;line-height:1;cursor:pointer;transition:background .12s,color .12s}.ver-banner .ver-close:hover{background:var(--line);color:var(--text)}.react-flow__node.rf-dim{opacity:.22;filter:saturate(.4)}.react-flow__node.rf-dim:hover{opacity:.6}.react-flow__node.rf-spotlit-out{opacity:.16;filter:saturate(.3) blur(.3px);transition:opacity .2s ease,filter .2s ease}.react-flow__node.rf-spotlit-out:hover{opacity:.55;filter:saturate(.7)}.react-flow__edge.rf-edge-selected .react-flow__edge-path{stroke-dasharray:6 3;animation:dashflow .8s linear infinite;filter:drop-shadow(0 0 4px var(--accent-dim))}.react-flow__node.rf-exiting{pointer-events:none}.react-flow__node.rf-exiting .agent-node{animation:nodeExit .6s cubic-bezier(.55,0,.55,1) forwards}@keyframes nodeExit{0%{opacity:1;filter:blur(0) saturate(1);transform:scale(1)}60%{opacity:.4;filter:blur(2px) saturate(.4)}to{opacity:0;filter:blur(6px) saturate(0);transform:scale(.85)}}.react-flow__edge.rf-edge-exiting .react-flow__edge-path{animation:edgeExit .6s ease forwards}@keyframes edgeExit{to{opacity:0;stroke-dashoffset:40}}.canvas-wrap{position:relative;background:radial-gradient(1200px 600px at 60% -10%,var(--bg-grid-1),transparent 60%),radial-gradient(1000px 500px at 10% 110%,var(--bg-grid-2),transparent 60%),var(--bg);overflow:hidden}.cat-filter-bar{position:absolute;top:14px;left:14px;z-index:6;display:flex;gap:4px;padding:4px;background:#14161bc7;border:1px solid var(--line);border-radius:999px;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);box-shadow:0 6px 18px #0000004d}:root[data-theme=light] .cat-filter-bar{background:#ffffffc7;box-shadow:0 6px 18px #0f172a1a}.cat-filter{display:inline-flex;align-items:center;gap:5px;padding:4px 10px 4px 8px;border-radius:999px;border:1px solid transparent;background:transparent;color:var(--text);font:inherit;font-size:11px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.02em;cursor:pointer;transition:background .12s,color .12s,opacity .12s}.cat-filter .cat-emoji{font-size:13px;line-height:1}.cat-filter .cat-name{text-transform:lowercase}.cat-filter:hover{background:var(--bg-soft)}.cat-filter:focus-visible{outline:2px solid var(--accent);outline-offset:1px}.cat-filter.off{opacity:.38;color:var(--muted)}.cat-filter.off .cat-emoji{filter:grayscale(.7)}.cat-filter.off:hover{opacity:.8}.detail{position:relative;background:var(--panel);border-left:1px solid var(--line);padding:18px;overflow:auto;display:flex;flex-direction:column;gap:12px}.detail-close{position:absolute;top:10px;right:12px;width:24px;height:24px;display:grid;place-items:center;font-size:18px;line-height:1;color:var(--muted);background:transparent;border:1px solid transparent;border-radius:6px;cursor:pointer;z-index:2;transition:background .12s ease,color .12s ease,border-color .12s ease}.detail-close:hover{color:var(--text);background:var(--bg-soft, rgba(127,127,127,.12));border-color:var(--line)}.app:has(.detail-reopen){grid-template-columns:1fr}.app:has(.session-list):has(.detail-reopen){grid-template-columns:240px 1fr}.app:has(.accounts-panel):has(.detail-reopen){grid-template-columns:288px 1fr}.detail-reopen{position:fixed;top:64px;right:0;width:22px;height:44px;display:grid;place-items:center;font-size:16px;line-height:1;color:var(--muted);background:var(--panel);border:1px solid var(--line);border-right:none;border-radius:8px 0 0 8px;cursor:pointer;z-index:10;box-shadow:-2px 2px 8px #0003;transition:color .12s ease,background .12s ease}.detail-reopen:hover{color:var(--text)}.detail h3{margin:0;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);font-weight:600}.detail .empty{color:var(--muted);font-style:italic}.detail .hint{border:1px dashed var(--line);border-radius:8px;padding:12px;color:var(--muted);font-size:12px;line-height:1.55;background:var(--bg-soft)}.detail .hint code{background:var(--bg);border:1px solid var(--line);padding:1px 6px;border-radius:4px;font-size:11.5px;color:var(--text)}.detail .row{display:flex;justify-content:space-between;align-items:baseline;padding:5px 0;border-bottom:1px solid var(--line-soft);font-size:12px;gap:12px}.detail .row:last-child{border-bottom:none}.detail .row .k{color:var(--muted)}.detail .row .v{color:var(--text);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;overflow:hidden;text-overflow:ellipsis;max-width:220px;white-space:nowrap}.detail .tool{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;padding:5px 0;color:var(--text);display:flex;justify-content:space-between;gap:8px;border-bottom:1px solid var(--line-soft)}.detail .tool:last-child{border-bottom:none}.detail .tool .name{display:inline-flex;align-items:center;gap:8px}.detail .tool .name .status-dot{display:inline-block;width:6px;height:6px;border-radius:50%}.detail .tool .name .status-dot.inflight{background:var(--inflight);animation:pulse 1.2s infinite}.detail .tool .name .status-dot.done{background:var(--ok)}.detail .tool .name .status-dot.err{background:var(--err)}.react-flow__renderer{background:transparent}.react-flow__node{transition:transform .32s cubic-bezier(.22,1,.36,1);animation:nodeSpawn .36s cubic-bezier(.22,1,.36,1) both}@keyframes nodeSpawn{0%{opacity:0;filter:blur(2px)}to{opacity:1;filter:blur(0)}}.react-flow__node.dragging,.react-flow__node:active{transition:none}.react-flow__edge-path{stroke:var(--accent-dim);stroke-width:1.5;transition:stroke .2s ease}.react-flow__edge.animated .react-flow__edge-path{stroke:var(--inflight);stroke-dasharray:5;animation:dashflow .8s linear infinite}@keyframes dashflow{to{stroke-dashoffset:-10}}.react-flow__attribution{display:none}.react-flow__controls{background:var(--panel);border:1px solid var(--line);border-radius:8px;box-shadow:none;overflow:hidden}.react-flow__controls-button{background:var(--panel);color:var(--text);border-bottom:1px solid var(--line);fill:var(--text)}.react-flow__controls-button:hover{background:var(--bg-soft)}.agent-node{position:relative;min-width:220px;max-width:260px;padding:10px 12px 10px 16px;background:var(--node-grad);border:1px solid var(--line);border-radius:10px;color:var(--text);font-size:12px;box-shadow:var(--shadow-1);transition:border-color .2s ease,box-shadow .2s ease;overflow:hidden;will-change:transform}.agent-node:hover{box-shadow:var(--shadow-2)}.agent-node.selected{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent-dim),0 10px 24px #0006}.agent-node.state-active{border-color:var(--inflight);box-shadow:0 0 0 1px #f0abfc73,0 8px 22px #f0abfc2e}.agent-node.state-done{border-color:#86efac80}.agent-node.state-err{border-color:#fca5a599}.agent-node .accent-stripe{position:absolute;left:0;top:0;bottom:0;width:4px;background:var(--accent);opacity:.7}.agent-node .head{display:flex;align-items:center;justify-content:space-between;gap:8px}.agent-node .head-right{display:flex;align-items:center;gap:6px}.ctx-donut{background:transparent;border:none;padding:0;cursor:pointer;line-height:0;border-radius:50%;transition:filter .12s ease,transform .12s ease}.ctx-donut:hover{filter:brightness(1.2);transform:scale(1.08)}.ctx-donut:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.ctx-modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;background:#0000008c;display:flex;align-items:center;justify-content:center;z-index:100;padding:24px}.ctx-modal{background:var(--panel);border:1px solid var(--line);border-radius:12px;box-shadow:0 20px 50px #00000080;width:min(560px,100%);max-height:80vh;overflow:auto;padding:18px 20px 22px;color:var(--text)}.ctx-modal-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:14px}.ctx-modal-title{font-size:15px;font-weight:600}.ctx-modal-sub{font-size:11px;color:var(--muted);margin-top:3px}.ctx-modal-close{background:transparent;border:1px solid var(--line);color:var(--text);border-radius:6px;width:28px;height:28px;font-size:18px;line-height:1;cursor:pointer}.ctx-modal-close:hover{background:var(--bg-soft)}.ctx-window-row{margin-bottom:16px}.ctx-window-bar{position:relative;height:10px;border-radius:999px;background:var(--bg-soft);border:1px solid var(--line);overflow:hidden}.ctx-window-fill{height:100%;background:linear-gradient(90deg,var(--accent),var(--inflight));transition:width .4s ease}.ctx-window-meta{display:flex;justify-content:space-between;font-size:11px;margin-top:6px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--muted)}.ctx-window-pct{color:var(--text);font-weight:600}.ctx-section-title{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin:18px 0 8px;font-weight:600}.ctx-grid{display:grid;grid-template-columns:1fr 1fr;gap:4px 16px}.ctx-row{display:flex;justify-content:space-between;padding:4px 0;font-size:12px;border-bottom:1px dashed var(--line)}.ctx-row.accent .ctx-row-val{color:var(--accent);font-weight:600}.ctx-row-label{color:var(--muted)}.ctx-row-val{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-variant-numeric:tabular-nums}.ctx-empty{font-size:12px;color:var(--muted);padding:8px 0}.ctx-md-list{list-style:none;padding:0;margin:0}.ctx-md-list li{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:6px 0;border-bottom:1px dashed var(--line);font-size:12px}.ctx-md-path{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;direction:rtl;text-align:left}.ctx-md-size{color:var(--muted);font-variant-numeric:tabular-nums;flex-shrink:0}.agent-node .title{display:flex;align-items:center;gap:8px;min-width:0}.agent-node .title .label{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:160px}.agent-node .time{font-variant-numeric:tabular-nums;font-size:11px;color:var(--muted);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.state-pill{display:inline-flex;align-items:center;padding:1px 7px;font-size:10px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;border-radius:999px;border:1px solid transparent}.state-pill.state-active{color:var(--inflight);border-color:#f0abfc66;background:#f0abfc14}.state-pill.state-active:before{content:"";width:5px;height:5px;border-radius:50%;background:var(--inflight);box-shadow:0 0 6px var(--inflight);margin-right:5px;animation:pulse 1.2s infinite}.state-pill.state-done{color:var(--ok);border-color:#86efac59;background:#86efac14}.state-pill.state-err{color:var(--err);border-color:#fca5a566;background:#fca5a514}.agent-node .sub{color:var(--muted);font-size:11px;margin-top:3px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.agent-node .chips{display:flex;flex-wrap:wrap;gap:4px;margin-top:8px;min-height:18px;align-items:center}.tool-chip{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10.5px;padding:2px 6px;border-radius:4px;border:1px solid var(--line);background:var(--bg);color:var(--text);white-space:nowrap;max-width:110px;overflow:hidden;text-overflow:ellipsis}.tool-chip.inflight{border-color:#f0abfc80;color:var(--inflight);background:#f0abfc0f;animation:pulse 1.4s infinite}.tool-chip.done{border-color:#86efac40;color:var(--ok)}.tool-chip.err{border-color:#fca5a566;color:var(--err);background:#fca5a50f}.chips-empty{color:var(--muted-dim);font-size:11px;font-style:italic}.chips-more{color:var(--muted);font-size:10.5px;padding:2px 4px}.tool-spark-row{display:flex;align-items:center;gap:8px;margin-top:6px;height:14px}.tool-spark{flex:0 0 auto;overflow:visible}.tool-spark-bar{fill:var(--muted-dim);transition:fill .2s}.tool-spark-bar.active{fill:var(--accent);opacity:.72}.tool-spark-bar.latest{fill:var(--inflight);opacity:1;filter:drop-shadow(0 0 3px rgba(240,171,252,.55))}.tool-spark-label{color:var(--muted);font-size:9.5px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.04em;text-transform:uppercase}:root[data-theme=light] .tool-spark-bar.active{fill:var(--accent);opacity:.85}:root[data-theme=light] .tool-spark-bar.latest{fill:var(--inflight);filter:drop-shadow(0 0 3px rgba(126,34,206,.45))}.agent-node .meta{color:var(--muted);font-size:11px;margin-top:8px;display:flex;gap:12px}.agent-node .meta b{color:var(--text);font-weight:600}.agent-node .meta .inflight-meta,.agent-node .meta .inflight-meta b{color:var(--inflight)}.agent-node .meta .tokens-meta{color:var(--accent);margin-left:auto}.agent-node .meta .tokens-meta b{color:var(--accent)}.agent-node .meta .cost-meta{color:var(--ok);display:inline-flex;align-items:baseline;gap:4px}.agent-node .meta .cost-meta b{color:var(--ok)}.agent-node .meta .cost-meta .cost-rate{color:var(--muted);font-size:9.5px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.02em}.tokens-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px 12px;padding:8px 10px;background:var(--bg-soft);border:1px solid var(--line);border-radius:8px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.tokens-grid>div{display:flex;justify-content:space-between;align-items:baseline}.tokens-grid .k{color:var(--muted);font-size:10.5px;text-transform:uppercase;letter-spacing:.06em}.tokens-grid b{color:var(--text);font-weight:600;font-variant-numeric:tabular-nums}.agent-node.synthetic{border-style:dashed;opacity:.92}.agent-node .synth-tag{display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;border-radius:50%;border:1px solid var(--muted);color:var(--muted);font-size:10px;font-weight:700;margin-left:4px}.spawn-badge{display:inline-flex;align-items:center;padding:1px 6px;margin-left:6px;border-radius:999px;border:1px solid var(--line);color:var(--accent);background:var(--bg-soft);font-size:10.5px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:600}.model-chip{display:inline-flex;align-items:center;padding:1px 7px;margin-left:6px;border-radius:999px;border:1px solid var(--line);color:var(--text);background:var(--bg-soft);font-size:10px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:600;letter-spacing:.02em}.model-chip[title*=opus]{color:#f0abfc;border-color:#f0abfc66;background:#f0abfc0f}.model-chip[title*=sonnet]{color:#7dd3fc;border-color:#7dd3fc66;background:#7dd3fc0f}.model-chip[title*=haiku]{color:#86efac;border-color:#86efac66;background:#86efac0f}.model-chip[title*=fable]{color:#fcd34d;border-color:#fcd34d66;background:#fcd34d0f}:root[data-theme=light] .model-chip[title*=opus]{color:#6b21a8;border-color:#7e22ce8c;background:#7e22ce24}:root[data-theme=light] .model-chip[title*=sonnet]{color:#075985;border-color:#0369a18c;background:#0369a124}:root[data-theme=light] .model-chip[title*=haiku]{color:#166534;border-color:#15803d8c;background:#15803d24}:root[data-theme=light] .model-chip[title*=fable]{color:#92400e;border-color:#b453098c;background:#b4530924}.now-running{margin-top:8px;padding:5px 8px;border-radius:6px;background:linear-gradient(90deg,#f0abfc1a,#7dd3fc14);border:1px solid rgba(240,171,252,.3);display:flex;align-items:center;gap:8px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--text)}:root[data-theme=light] .now-running{background:linear-gradient(90deg,#a21caf12,#0284c70f);border-color:#a21caf4d}.now-running .now-dot{width:6px;height:6px;border-radius:50%;background:var(--inflight);box-shadow:0 0 8px var(--inflight);animation:pulse 1.1s infinite}.now-running .now-label{text-transform:uppercase;letter-spacing:.08em;color:var(--inflight);font-size:9.5px;font-weight:700}.now-running .now-tool{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--text);font-weight:600}.now-running .now-time{color:var(--muted);font-variant-numeric:tabular-nums;font-size:10.5px}.react-flow__edge-textbg{fill:var(--bg-soft);fill-opacity:.9}.react-flow__edge-text{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;fill:var(--text)}.session-clusters{position:absolute;top:0;right:0;bottom:0;left:0;pointer-events:none;z-index:0}.cluster-card{position:absolute;border-radius:16px;border:1px solid transparent;transition:opacity .2s ease,left .32s cubic-bezier(.22,1,.36,1),top .32s cubic-bezier(.22,1,.36,1),width .32s cubic-bezier(.22,1,.36,1),height .32s cubic-bezier(.22,1,.36,1);overflow:visible;pointer-events:auto;cursor:grab}.cluster-card.dragging{transition:opacity .2s ease;cursor:grabbing}.cluster-label{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10.5px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;padding:3px 10px;border-radius:999px;background:var(--bg);border:1px solid var(--line);box-shadow:0 2px 6px #00000040;white-space:nowrap;line-height:1;cursor:grab;pointer-events:auto;touch-action:none;-webkit-user-select:none;user-select:none;transition:box-shadow .12s ease,filter .12s ease}.cluster-label:hover{filter:brightness(1.25);box-shadow:0 4px 14px #00000073,0 0 0 1px currentColor}.cluster-label.dragging{cursor:grabbing;filter:brightness(1.3);box-shadow:0 6px 18px #00000080,0 0 0 1px currentColor}.session-group-handle{cursor:grab;background:transparent}.react-flow__node-sessionGroup{cursor:grab}.react-flow__node-sessionGroup.dragging,.react-flow__node-sessionGroup:active{cursor:grabbing}.react-flow__node-sessionGroup.selected{box-shadow:none}:root[data-theme=light] .cluster-label{background:var(--bg-soft);box-shadow:0 1px 4px #0f172a14}.tool-bursts-layer{position:absolute;top:0;right:0;bottom:0;left:0;pointer-events:none;z-index:5;overflow:visible}.tool-bursts-svg{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;pointer-events:none;overflow:visible}.tool-conn{fill:none;stroke-width:1.4;stroke-dasharray:5 4;stroke-linecap:round;transition:opacity .3s ease}.tool-conn.status-inflight{stroke:var(--inflight);animation:dashflow .7s linear infinite;filter:drop-shadow(0 0 4px rgba(240,171,252,.45))}.tool-conn.status-done{stroke:#86efacd9}.tool-conn.status-err{stroke:#fca5a5d9}.tool-conn.fading{stroke-dasharray:4 6}.tool-burst-wrap{position:absolute;pointer-events:none;will-change:transform,left,top;transition:top .22s cubic-bezier(.22,1,.36,1)}.tool-burst{display:inline-flex;align-items:center;gap:6px;padding:5px 11px 5px 9px;border-radius:999px;background:var(--panel);border:1px solid var(--line);font-size:11px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;box-shadow:0 4px 14px #00000059,0 0 0 1px #00000040;white-space:nowrap;color:var(--text);animation:bubble-spawn .46s cubic-bezier(.34,1.56,.64,1) both}.tool-burst .tb-emoji{font-size:13px;line-height:1;filter:saturate(1.2);display:inline-block}.tool-burst .tb-name{font-weight:600;letter-spacing:.01em}.tool-burst .tb-spin{width:8px;height:8px;border-radius:50%;background:radial-gradient(circle at 30% 30%,var(--inflight),transparent 70%);box-shadow:0 0 8px var(--inflight);animation:pulse 1.1s ease-in-out infinite}.tool-burst .tb-mark{font-weight:700;font-size:12px;line-height:1;margin-left:2px;animation:mark-pop .26s cubic-bezier(.34,1.56,.64,1) both}.tool-burst .tb-mark.done{color:var(--ok);text-shadow:0 0 6px rgba(134,239,172,.55)}.tool-burst .tb-mark.err{color:var(--err);text-shadow:0 0 6px rgba(252,165,165,.55)}.tool-burst.status-inflight{border-color:#f0abfc8c;background:linear-gradient(180deg,#f0abfc1a,#7dd3fc0d),var(--panel);box-shadow:0 0 0 1px #f0abfc4d,0 6px 18px #f0abfc40}.tool-burst.status-inflight .tb-emoji{animation:emoji-wobble 1.6s ease-in-out infinite}.tool-burst.status-done{border-color:#86efac8c;background:linear-gradient(180deg,rgba(134,239,172,.1),transparent),var(--panel);box-shadow:0 0 0 1px #86efac38,0 4px 14px #86efac26;animation:bubble-spawn .46s cubic-bezier(.34,1.56,.64,1) both,bubble-done-flash .52s .46s ease-out}.tool-burst.status-err{border-color:#fca5a599;background:linear-gradient(180deg,rgba(252,165,165,.1),transparent),var(--panel);box-shadow:0 0 0 1px #fca5a559,0 4px 12px #fca5a52e;animation:bubble-spawn .46s cubic-bezier(.34,1.56,.64,1) both,bubble-err-shake .38s .46s ease-in-out}.tool-burst.fading{animation:bubble-fade .6s ease-in forwards}.tool-burst{position:relative}.tool-burst:before{content:"";position:absolute;left:4px;top:6px;bottom:6px;width:3px;border-radius:2px;background:var(--cat-accent, transparent);opacity:.85}.tool-burst.cat-file{--cat-accent: #7dd3fc}.tool-burst.cat-shell{--cat-accent: #fcd34d}.tool-burst.cat-web{--cat-accent: #67e8f9}.tool-burst.cat-agent{--cat-accent: #f0abfc}.tool-burst.cat-task{--cat-accent: #86efac}.tool-burst.cat-plan{--cat-accent: #c4b5fd}.tool-burst.cat-mcp{--cat-accent: #5eead4}.tool-burst.cat-other{--cat-accent: #94a3b8}.tool-burst.dim{opacity:.22;filter:saturate(.4);transition:opacity .18s ease,filter .18s ease}.tool-burst.dim:hover{opacity:.6}.tool-burst.sub{font-size:10.5px;padding:4px 10px 4px 8px;opacity:.94;animation:bubble-spawn .46s .22s cubic-bezier(.34,1.56,.64,1) both}.tool-burst.sub .tb-emoji{font-size:12px}.tool-burst.sub.status-done{animation:bubble-spawn .46s .22s cubic-bezier(.34,1.56,.64,1) both,bubble-done-flash .52s .68s ease-out}.tool-burst.sub.status-err{animation:bubble-spawn .46s .22s cubic-bezier(.34,1.56,.64,1) both,bubble-err-shake .38s .68s ease-in-out}.tool-burst.sub.fading{animation:bubble-fade .6s ease-in forwards}.tool-burst.clickable{pointer-events:auto;cursor:pointer}.tool-burst.clickable:hover{transform:translateY(-1px);box-shadow:0 6px 18px #00000073,0 0 0 1px var(--cat-accent, rgba(255,255,255,.15))}.tool-burst.clickable:focus-visible{outline:2px solid var(--accent);outline-offset:2px}@keyframes bubble-spawn{0%{opacity:0;transform:translate(var(--spawn-dx, -28px),var(--spawn-dy, 0)) scale(.35);filter:blur(3px) brightness(1.4)}55%{opacity:1;transform:translate(0) scale(1.12);filter:blur(0) brightness(1.15)}to{opacity:1;transform:translate(0) scale(1);filter:blur(0) brightness(1)}}@keyframes bubble-done-flash{0%{transform:scale(1);box-shadow:0 0 0 1px #86efac38,0 4px 14px #86efac26}40%{transform:scale(1.14);box-shadow:0 0 0 2px #86efac8c,0 0 22px #86efac8c}to{transform:scale(1);box-shadow:0 0 0 1px #86efac38,0 4px 14px #86efac26}}@keyframes bubble-err-shake{0%,to{transform:translate(0)}15%{transform:translate(-4px)}35%{transform:translate(4px)}55%{transform:translate(-3px)}75%{transform:translate(2px)}}@keyframes bubble-fade{0%{opacity:1;transform:scale(1) translate(0);filter:blur(0)}to{opacity:0;transform:scale(.82) translate(18px,-4px);filter:blur(1.5px)}}@keyframes emoji-wobble{0%,to{transform:rotate(-6deg) scale(1)}50%{transform:rotate(6deg) scale(1.08)}}@keyframes mark-pop{0%{opacity:0;transform:scale(.3) rotate(-30deg)}to{opacity:1;transform:scale(1) rotate(0)}}:root[data-theme=light] .tool-burst{box-shadow:0 2px 8px #0f172a1a,0 0 0 1px #0f172a0a}:root[data-theme=light] .tool-burst.status-inflight{border-color:#7e22ce8c;background:linear-gradient(180deg,rgba(126,34,206,.06),transparent),var(--panel);box-shadow:0 0 0 1px #7e22ce33,0 6px 14px #7e22ce2e}:root[data-theme=light] .tool-burst.status-done{border-color:#15803d80}:root[data-theme=light] .tool-burst.status-err{border-color:#b91c1c8c}:root[data-theme=light] .tool-conn.status-inflight{stroke:#7e22ced9;filter:drop-shadow(0 0 3px rgba(126,34,206,.35))}:root[data-theme=light] .tool-conn.status-done{stroke:#15803dd9}:root[data-theme=light] .tool-conn.status-err{stroke:#b91c1cd9}.cat-file{--cat-color: #7dd3fc}.cat-shell{--cat-color: #fcd34d}.cat-web{--cat-color: #67e8f9}.cat-agent{--cat-color: #f0abfc}.cat-task{--cat-color: #86efac}.cat-plan{--cat-color: #c4b5fd}.cat-mcp{--cat-color: #5eead4}.cat-other{--cat-color: #94a3b8}.session-list{grid-column:1;grid-row:2 / -1;background:var(--panel);border-right:1px solid var(--line);display:flex;flex-direction:column;min-height:0;overflow:hidden}.session-list .sl-header{display:flex;align-items:center;gap:8px;padding:12px 12px 10px 14px;border-bottom:1px solid var(--line)}.session-list .sl-header h3{margin:0;flex:1;display:inline-flex;align-items:center;gap:8px}.session-list .sl-count{display:inline-flex;align-items:center;padding:1px 7px;border-radius:999px;background:var(--bg-soft);border:1px solid var(--line);color:var(--text);font-size:10px;font-weight:600;text-transform:none;letter-spacing:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.session-list .sl-live-count{font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--inflight);font-weight:600}.session-list .sl-close{width:26px;height:26px;font-size:15px}.session-list .sl-rows{flex:1;overflow-y:auto;padding:6px;display:flex;flex-direction:column;gap:4px}.session-list .sl-empty{padding:16px 12px;color:var(--muted);font-size:12px;font-style:italic}.session-list .sl-row{display:flex;align-items:flex-start;gap:9px;padding:9px 10px;background:transparent;border:1px solid transparent;border-radius:8px;cursor:pointer;text-align:left;color:var(--text);font:inherit;transition:background .12s,border-color .12s;min-width:0}.session-list .sl-row:hover{background:var(--bg-soft)}.session-list .sl-row.selected{background:var(--bg-soft);border-color:var(--accent-dim)}.session-list .sl-row:focus-visible{outline:2px solid var(--accent);outline-offset:1px}.session-list .sl-dot{display:inline-block;width:8px;height:8px;margin-top:6px;flex:0 0 8px;border-radius:50%;background:var(--muted-dim)}.session-list .sl-dot.state-active{background:var(--inflight);box-shadow:0 0 8px var(--inflight);animation:pulse 1.4s ease-in-out infinite}.session-list .sl-dot.state-done{background:var(--ok)}.session-list .sl-dot.state-err{background:var(--err)}.session-list .sl-row-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.session-list .sl-row-head{display:flex;align-items:center;gap:6px;min-width:0}.session-list .sl-label{font-weight:600;font-size:12.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;flex:1}.session-list .sl-row-meta{display:flex;flex-wrap:wrap;gap:8px;font-size:11px;color:var(--muted);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.session-list .sl-row-meta b{color:var(--text);font-weight:600;font-variant-numeric:tabular-nums}.session-list .sl-cost b{color:var(--ok)}.session-list .sl-elapsed{margin-left:auto}.detail-hero{display:flex;flex-direction:column;gap:8px;padding:4px 0 14px;border-bottom:1px solid var(--line)}.detail-hero .hero-line{display:flex;align-items:center;gap:10px;min-width:0}.detail-hero .hero-title{margin:0;font-size:17px;font-weight:600;letter-spacing:.01em;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}.detail-hero .hero-meta{display:flex;align-items:center;gap:8px;font-size:11.5px;color:var(--muted);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;flex-wrap:wrap}.detail-hero .hero-meta-item{letter-spacing:.03em}.detail-hero .hero-sep{color:var(--muted-dim)}.detail-hero .hero-cost{margin-top:8px;padding:10px 12px;background:var(--bg-soft);border:1px solid var(--line);border-radius:10px}.detail-hero .hero-actions{display:flex;flex-wrap:wrap;gap:6px;margin-top:4px}.detail-hero .hero-action-btn{padding:5px 12px;font-size:11px}.detail-hero .hero-cost-headline{display:flex;align-items:baseline;gap:6px;margin-bottom:8px}.detail-hero .hero-cost-value{font-size:22px;font-weight:700;color:var(--ok);font-variant-numeric:tabular-nums;letter-spacing:-.01em}.detail-hero .hero-cost-label{font-size:10.5px;text-transform:uppercase;letter-spacing:.1em;color:var(--muted)}.cost-bar{display:flex;height:8px;width:100%;border-radius:4px;overflow:hidden;background:var(--line);gap:1px}.cost-bar .cb-seg{height:100%;display:block;transition:filter .12s ease}.cost-bar .cb-seg:hover{filter:brightness(1.3)}.cost-bar .cb-input{background:#5e9fed}.cost-bar .cb-output{background:#c679ec}.cost-bar .cb-cache-r{background:#5cd699}.cost-bar .cb-cache-w{background:#f2b25a}.detail-section{display:flex;flex-direction:column;gap:8px;padding-top:4px}.detail-section h3{display:flex;align-items:center;gap:8px}.detail-section h3 .section-count{display:inline-flex;align-items:center;padding:1px 7px;border-radius:999px;background:var(--bg-soft);border:1px solid var(--line);color:var(--text);font-size:10px;font-weight:600;text-transform:none;letter-spacing:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.activity-row{display:flex;flex-direction:column;gap:8px}.activity-counters{display:flex;gap:12px;font-size:12px;color:var(--muted)}.activity-counters .ac-item b{color:var(--text);font-weight:600;font-variant-numeric:tabular-nums}.activity-counters .ac-live,.activity-counters .ac-live b{color:var(--inflight)}.activity-counters .ac-err,.activity-counters .ac-err b{color:var(--err)}.cat-strip{display:flex;flex-wrap:wrap;gap:5px}.cat-chip{display:inline-flex;align-items:center;gap:5px;padding:3px 9px 3px 7px;border-radius:999px;border:1px solid var(--line);background:var(--bg-soft);font-size:11px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.cat-chip .cat-emoji{font-size:12px;line-height:1}.cat-chip .cat-count{color:var(--text);font-weight:600;font-variant-numeric:tabular-nums}.cat-chip.cat-file{border-color:#7dd3fc66}.cat-chip.cat-shell{border-color:#fcd34d66}.cat-chip.cat-web{border-color:#67e8f966}.cat-chip.cat-agent{border-color:#f0abfc66}.cat-chip.cat-task{border-color:#86efac66}.cat-chip.cat-plan{border-color:#c4b5fd66}.cat-chip.cat-mcp{border-color:#5eead466}.prompts{display:flex;flex-direction:column;gap:8px}.prompt-entry{border:1px solid var(--line);border-radius:8px;padding:8px 10px;background:var(--bg-soft)}.prompt-time{color:var(--muted);font-size:10.5px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.04em;text-transform:uppercase;margin-bottom:4px}.prompt-text{font-size:12.5px;line-height:1.5;color:var(--text);white-space:pre-wrap;word-break:break-word}button.tool.clickable{width:100%;background:transparent;border:none;border-bottom:1px solid var(--line-soft);text-align:left;cursor:pointer;color:var(--text);font:inherit;padding:6px 0;display:flex;align-items:center;gap:8px;justify-content:space-between;border-radius:4px;transition:background .12s}button.tool.clickable{padding-left:6px;padding-right:6px}button.tool.clickable:hover{background:var(--bg-soft)}button.tool.clickable:last-child{border-bottom:none}.shortcuts{display:grid;grid-template-columns:auto 1fr;gap:6px 12px;align-items:center}.shortcuts .sc{display:contents}.shortcuts .sc kbd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10.5px;font-weight:600;color:var(--text);background:var(--bg-soft);border:1px solid var(--line);border-bottom-width:2px;border-radius:4px;padding:2px 7px;text-align:center;min-width:38px}.shortcuts .sc span{color:var(--muted);font-size:12px}.empty-hero .hint-row{margin-top:14px;font-size:12px;opacity:.75}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;background:#0506098c;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;z-index:100;animation:fadeIn .14s ease-out}:root[data-theme=light] .modal-backdrop{background:#0f172a4d}.modal{width:min(820px,92vw);max-height:82vh;display:flex;flex-direction:column;background:var(--panel);border:1px solid var(--line);border-radius:12px;box-shadow:var(--shadow-2),0 0 0 1px #7dd3fc1a;overflow:hidden;animation:popIn .18s cubic-bezier(.22,1,.36,1)}.modal-head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--line);background:var(--bg-soft)}.modal-title{display:flex;align-items:center;gap:10px;min-width:0}.modal-title .status-dot{display:inline-block;width:8px;height:8px;border-radius:50%}.modal-title .status-dot.inflight{background:var(--inflight);animation:pulse 1.2s infinite}.modal-title .status-dot.done{background:var(--ok)}.modal-title .status-dot.err{background:var(--err)}.modal-tool-name{font-weight:600;font-size:14px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.modal-tool-id{color:var(--muted);font-size:11px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.modal-actions{display:flex;align-items:center;gap:8px}.modal-dur{color:var(--muted);font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.modal-body{padding:16px;overflow:auto;display:flex;flex-direction:column;gap:16px}.modal-section h4{margin:0 0 6px;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);font-weight:600;display:flex;align-items:center;gap:8px}.modal-section .err-tag{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:4px;color:var(--err);background:#fca5a51a;border:1px solid rgba(252,165,165,.3)}.modal-section pre{margin:0;padding:12px;background:var(--bg);border:1px solid var(--line);border-radius:8px;color:var(--text);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.5;white-space:pre-wrap;word-break:break-word;max-height:320px;overflow:auto}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes popIn{0%{opacity:0;transform:translateY(4px) scale(.985)}to{opacity:1;transform:none}}.session-summary{width:min(720px,92vw)}.session-summary .ss-hero{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.6fr);gap:22px;padding:18px 18px 20px;background:linear-gradient(180deg,rgba(134,239,172,.05),transparent);border-bottom:1px solid var(--line)}.session-summary .ss-hero-left{display:flex;flex-direction:column;gap:8px;min-width:0}.session-summary .ss-cost-label{font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}.session-summary .ss-cost{font-size:34px;font-weight:700;color:var(--ok);font-variant-numeric:tabular-nums;letter-spacing:-.02em;line-height:1}.session-summary .ss-models{display:flex;gap:6px;flex-wrap:wrap}.session-summary .ss-hero-right{display:flex;flex-direction:column;justify-content:center;gap:10px;min-width:0}.cost-bar.cost-bar-lg{height:14px;border-radius:7px}.session-summary .ss-cost-legend{display:grid;grid-template-columns:1fr 1fr;gap:6px 16px;font-size:11.5px;color:var(--muted);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.session-summary .ssl{display:inline-flex;align-items:baseline;gap:5px}.session-summary .ssl b{color:var(--text);font-weight:600;font-variant-numeric:tabular-nums}.session-summary .ssl:before{content:"";display:inline-block;width:8px;height:8px;border-radius:2px;margin-right:1px}.session-summary .ssl-in:before{background:#5e9fed}.session-summary .ssl-out:before{background:#c679ec}.session-summary .ssl-cr:before{background:#5cd699}.session-summary .ssl-cw:before{background:#f2b25a}.session-summary .ss-stats{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:6px;padding:14px 18px;border-bottom:1px solid var(--line)}.session-summary .ss-stat{display:flex;flex-direction:column;gap:2px;padding:8px 10px;background:var(--bg-soft);border:1px solid var(--line);border-radius:8px}.session-summary .ss-stat-value{font-size:18px;font-weight:600;color:var(--text);font-variant-numeric:tabular-nums;letter-spacing:-.01em;line-height:1.1}.session-summary .ss-stat-label{font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}.session-summary .ss-stat.tone-err .ss-stat-value{color:var(--err)}@media(max-width:640px){.session-summary .ss-hero{grid-template-columns:1fr}.session-summary .ss-stats{grid-template-columns:repeat(3,minmax(0,1fr))}}.session-summary .ss-top-tools{display:flex;flex-direction:column;gap:6px}.session-summary .ss-tt{display:grid;grid-template-columns:130px 1fr 36px;gap:10px;align-items:center}.session-summary .ss-tt-name{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.session-summary .ss-tt-bar{height:6px;background:var(--line);border-radius:3px;overflow:hidden}.session-summary .ss-tt-bar-fill{display:block;height:100%;background:linear-gradient(90deg,var(--accent),var(--inflight));border-radius:3px}.session-summary .ss-tt-count{text-align:right;font-variant-numeric:tabular-nums;font-size:11.5px;color:var(--muted)}.session-summary .ss-prompt{font-size:12.5px;line-height:1.55;color:var(--text);padding:10px 12px;background:var(--bg-soft);border:1px solid var(--line);border-left:3px solid var(--accent);border-radius:4px;white-space:pre-wrap;word-break:break-word;max-height:200px;overflow:auto}.empty-hero{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;pointer-events:none;color:var(--muted);-webkit-user-select:none;user-select:none;max-width:520px}.empty-hero .orbit-stack{position:relative;width:260px;height:260px;margin:0 auto 28px}.empty-hero .orbit{position:absolute;top:0;right:0;bottom:0;left:0;border-radius:50%;border:1px dashed rgba(125,211,252,.18)}.empty-hero .orbit.r1{top:0;right:0;bottom:0;left:0;animation:spin 22s linear infinite}.empty-hero .orbit.r2{top:30px;right:30px;bottom:30px;left:30px;animation:spin 14s linear infinite reverse;border-color:#f0abfc33}.empty-hero .orbit.r3{top:60px;right:60px;bottom:60px;left:60px;animation:spin 9s linear infinite;border-color:#86efac38}.empty-hero .orbit .dot{position:absolute;width:14px;height:14px;border-radius:50%;top:-7px;left:50%;margin-left:-7px}.empty-hero .orbit.r1 .dot{background:var(--accent);box-shadow:0 0 22px var(--accent)}.empty-hero .orbit.r2 .dot{background:var(--inflight);box-shadow:0 0 22px var(--inflight);width:12px;height:12px;margin-left:-6px;top:-6px}.empty-hero .orbit.r3 .dot{background:var(--ok);box-shadow:0 0 22px var(--ok);width:10px;height:10px;margin-left:-5px;top:-5px}.empty-hero .orbit .dot.b{top:auto;bottom:-7px}.empty-hero .orbit.r2 .dot.b{bottom:-6px}.empty-hero .orbit.r3 .dot.b{bottom:-5px}.empty-hero .core{position:absolute;top:50%;left:50%;width:56px;height:56px;margin:-28px 0 0 -28px;border-radius:50%;background:radial-gradient(circle at 30% 30%,#f0abfc,#7dd3fc 55%,#14161b);box-shadow:0 0 28px #f0abfc8c,0 0 64px #7dd3fc59;animation:corepulse 2.6s ease-in-out infinite}.empty-hero .core:after{content:"";position:absolute;top:-14px;right:-14px;bottom:-14px;left:-14px;border-radius:50%;border:1px solid rgba(255,255,255,.06);animation:corewave 2.6s ease-out infinite}@keyframes corepulse{0%,to{transform:scale(1);filter:brightness(1)}50%{transform:scale(1.08);filter:brightness(1.15)}}@keyframes corewave{0%{transform:scale(.85);opacity:.6}to{transform:scale(1.6);opacity:0}}.empty-hero h2{color:var(--text);font-size:18px;font-weight:600;margin:0 0 8px;letter-spacing:.01em}.empty-hero p{margin:0;font-size:13px;line-height:1.65}.empty-hero code{background:var(--bg-soft);border:1px solid var(--line);padding:2px 7px;border-radius:5px;color:var(--text);font-size:12.5px}@keyframes spin{to{transform:rotate(360deg)}}:root[data-theme=light] .topbar .status .pill.live{border-color:#15803d73;background:#15803d1a}:root[data-theme=light] .topbar .status .pill.dead{border-color:#b91c1c80;background:#b91c1c1a}:root[data-theme=light] .agent-node.state-active{border-color:#7e22cea6;box-shadow:0 0 0 1px #7e22ce4d,0 8px 22px #7e22ce2e}:root[data-theme=light] .agent-node.state-done{border-color:#15803d8c}:root[data-theme=light] .agent-node.state-err{border-color:#b91c1c99}:root[data-theme=light] .state-pill.state-active{border-color:#7e22ce73;background:#7e22ce1a}:root[data-theme=light] .state-pill.state-active:before{box-shadow:0 0 5px #7e22ce8c}:root[data-theme=light] .state-pill.state-done{border-color:#15803d73;background:#15803d1a}:root[data-theme=light] .state-pill.state-err{border-color:#b91c1c80;background:#b91c1c1a}:root[data-theme=light] .tool-chip{background:#f4f6fa}:root[data-theme=light] .tool-chip.inflight{border-color:#7e22cea6;background:#7e22ce29;color:#6b21a8}:root[data-theme=light] .tool-chip.done{border-color:#15803d8c;background:#15803d1f;color:#166534}:root[data-theme=light] .tool-chip.err{border-color:#b91c1ca6;background:#b91c1c29;color:#991b1b}:root[data-theme=light] .modal-section .err-tag{background:#b91c1c1a;border-color:#b91c1c73}:root[data-theme=light] .now-running .now-dot{box-shadow:0 0 6px #7e22ce99}:root[data-theme=light] .empty-hero .orbit{border-color:#0369a14d}:root[data-theme=light] .empty-hero .orbit.r2{border-color:#7e22ce4d}:root[data-theme=light] .empty-hero .orbit.r3{border-color:#15803d52}:root[data-theme=light] .empty-hero .core{background:radial-gradient(circle at 30% 30%,#c084fc,#0ea5e9 55%,#1e293b);box-shadow:0 0 28px #7e22ce66,0 0 64px #0369a140}:root[data-theme=light] .conn-banner{background:linear-gradient(90deg,rgba(185,28,28,.14),transparent);border-bottom-color:#b91c1c73}:root[data-theme=light] button.btn.warn{border-color:#b453098c;color:var(--warn)}:root[data-theme=light] .search-clear{color:var(--muted)}:root[data-theme=light] .react-flow__edge-path{stroke:#0369a166}:root[data-theme=light] .react-flow__minimap-node{stroke:#0f172a40}.usage-panel{position:fixed;top:60px;right:368px;width:280px;max-height:calc(100vh - 76px);overflow-y:auto;background:var(--panel);border:1px solid var(--line);border-radius:10px;box-shadow:var(--shadow-2);z-index:20;padding:0 0 12px}.up-header{display:flex;align-items:center;gap:8px;padding:10px 14px 8px;border-bottom:1px solid var(--line-soft);position:sticky;top:0;background:var(--panel);z-index:1}.up-header h3{margin:0;font-size:13px;font-weight:600;flex:1}.up-header-right{display:flex;align-items:center;gap:4px}.up-rate{font-size:11px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-variant-numeric:tabular-nums;color:var(--inflight);padding:2px 6px;border-radius:999px;background:#f0abfc1f}.up-close{font-size:16px;line-height:1;padding:2px 6px;color:var(--muted)}.up-total{display:flex;align-items:baseline;gap:8px;padding:14px 14px 6px}.up-total-value{font-size:26px;font-weight:700;font-variant-numeric:tabular-nums;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--ok);line-height:1}.up-total-label{font-size:11px;color:var(--muted)}.usage-panel .cost-bar{margin:4px 14px 10px;border-radius:3px}.up-tokens-row{display:flex;flex-wrap:wrap;gap:10px 16px;padding:6px 14px 10px}.up-tok{display:flex;align-items:baseline;gap:5px;font-size:12px;font-variant-numeric:tabular-nums;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.up-k{font-size:10px;color:var(--muted);font-family:inherit}.up-section{padding:0 14px;margin-top:4px;border-top:1px solid var(--line-soft)}.up-section-title{display:flex;align-items:baseline;gap:8px;margin:8px 0 6px;font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}.up-section-age{margin-left:auto;font-size:10px;font-weight:400;color:var(--muted-dim);text-transform:none;letter-spacing:0;white-space:nowrap}.up-table{width:100%;border-collapse:collapse;font-size:12px}.up-table th{text-align:left;color:var(--muted);font-weight:500;font-size:10px;padding:0 0 4px;border-bottom:1px solid var(--line-soft)}.up-table th:not(:first-child){text-align:right}.up-table td{padding:4px 0;vertical-align:middle}.up-model-name{color:var(--text);font-weight:500;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.up-num{text-align:right;font-variant-numeric:tabular-nums;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--muted);font-size:11px}.up-cost-val{color:var(--ok)}.up-sessions{display:flex;flex-direction:column;gap:4px}.up-session-row{display:flex;align-items:center;gap:7px;font-size:12px;padding:3px 0}.up-session-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text)}.up-session-tokens{font-variant-numeric:tabular-nums;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--muted)}.up-session-cost{font-variant-numeric:tabular-nums;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--ok);min-width:36px;text-align:right}.up-hint{padding:8px 14px 4px;font-size:11px;color:var(--muted);line-height:1.5}.up-empty{padding:20px 14px;font-size:12px;color:var(--muted);text-align:center;line-height:1.6}.app:not(:has(.detail)) .usage-panel{right:8px}.up-quota-section{padding-top:10px;border-top:none}.up-quota-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.up-refresh-btn{font-size:14px;padding:1px 7px;color:var(--muted);line-height:1.4}.up-quota-bars{display:flex;flex-direction:column;gap:10px}.qb-row{display:flex;flex-direction:column;gap:3px}.qb-meta{display:flex;justify-content:space-between;align-items:baseline;font-size:11px}.qb-label{color:var(--muted)}.qb-pct{font-variant-numeric:tabular-nums;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;font-weight:600}.qb-track{position:relative;height:6px;border-radius:3px;background:var(--line);overflow:hidden}.qb-fill{height:100%;border-radius:3px;transition:width .4s ease}.qb-pace-marker{position:absolute;top:0;bottom:0;width:2px;margin-left:-1px;border-radius:1px;box-shadow:0 0 2px #00000080;transition:left .4s ease;z-index:2}.qb-reset-row{display:flex;justify-content:space-between;align-items:baseline;gap:4px;min-height:14px}.qb-reset{font-size:10px;color:var(--muted-dim)}.qb-pace{font-size:10px;font-weight:500;text-align:right;flex-shrink:0}.qb-limit-badge{margin-left:4px;font-size:10px}.up-plan-badge{margin-left:6px;padding:1px 5px;border:1px solid var(--line-soft);border-radius:3px;font-size:9px;font-weight:500;color:var(--muted-dim);text-transform:none;letter-spacing:0;vertical-align:1px}.up-quota-na{display:flex;flex-direction:column;gap:3px;font-size:11px;color:var(--muted)}.up-quota-hint{color:var(--muted-dim)}.up-quota-hint code{background:var(--line);padding:0 3px;border-radius:3px;font-size:10.5px}.up-quota-loading{font-size:11px;color:var(--muted-dim)}.up-quota-sub{font-size:10px;color:var(--muted-dim);margin-top:4px}.up-credits{color:var(--accent)}.up-reset-credits{color:var(--ok)}:root[data-theme=light] .up-rate{background:#7e22ce1a}.uh-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;background:#0000008c;display:flex;align-items:center;justify-content:center;z-index:100;padding:24px}.uh-modal{background:var(--panel);border:1px solid var(--line);border-radius:12px;box-shadow:0 20px 50px #00000080;width:min(760px,100%);max-height:84vh;overflow:auto;padding:16px 18px 20px;color:var(--text)}.uh-head{display:flex;align-items:center;gap:12px;margin-bottom:14px}.uh-titlewrap{display:flex;flex-direction:column;gap:2px;margin-right:auto}.uh-title{font-size:14px;font-weight:600}.uh-sub{font-size:10.5px;color:var(--muted-dim);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.uh-range{display:inline-flex;gap:2px;background:var(--bg-soft);border:1px solid var(--line);border-radius:999px;padding:2px}.uh-range-btn{font:inherit;font-size:10.5px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;padding:2px 9px;border:none;border-radius:999px;color:var(--muted);background:transparent;cursor:pointer;transition:background .12s,color .12s}.uh-range-btn:hover{color:var(--text)}.uh-range-btn.on{background:var(--accent-dim);color:var(--text)}.uh-reload{width:24px;height:24px;font-size:13px;padding:0}.uh-close{width:24px;height:24px;font-size:16px;line-height:1;border:none;background:transparent;color:var(--muted);cursor:pointer;border-radius:6px}.uh-close:hover{color:var(--text);background:var(--bg-soft)}.uh-status{padding:40px 0;text-align:center;color:var(--muted);font-size:12px}.uh-status.uh-err{color:var(--err)}.uh-totals{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-bottom:14px}.uh-stat{display:flex;flex-direction:column;gap:2px;padding:8px 10px;background:var(--bg-soft);border:1px solid var(--line-soft);border-radius:8px}.uh-stat-val{font-size:14px;font-weight:600;font-variant-numeric:tabular-nums}.uh-stat-val.accent{color:var(--ok)}.uh-stat-label{font-size:9.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em}.uh-chart{display:flex;align-items:flex-end;gap:1px;height:168px;margin-bottom:10px;padding-bottom:16px;border-bottom:1px solid var(--line-soft)}.uh-bar-col{display:flex;flex-direction:column;align-items:center;justify-content:flex-end;height:100%;position:relative;background:none;border:none;padding:0;cursor:pointer;min-width:0}.uh-bar-col:hover .uh-bar{filter:brightness(1.15)}.uh-bar-col.sel .uh-bar{outline:1px solid var(--accent);outline-offset:1px}.uh-bar{width:78%;min-height:1px;display:flex;flex-direction:column;border-radius:2px 2px 0 0;overflow:hidden;transition:height .3s ease}.uh-bar-seg{width:100%}.uh-bar-label{position:absolute;bottom:-15px;font-size:8px;color:var(--muted-dim);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;white-space:nowrap;transform:rotate(0);overflow:hidden;max-width:100%;text-overflow:ellipsis}.uh-legend{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:12px;font-size:11px;color:var(--muted)}.uh-legend-item{display:inline-flex;align-items:center;gap:5px}.uh-legend-dot{width:8px;height:8px;border-radius:2px;display:inline-block;flex-shrink:0}.uh-legend-cost{color:var(--text);font-variant-numeric:tabular-nums}.uh-detail{background:var(--bg-soft);border:1px solid var(--line-soft);border-radius:10px;padding:12px 14px}.uh-detail-head{display:flex;align-items:baseline;gap:10px;margin-bottom:10px}.uh-detail-date{font-size:12px;font-weight:600}.uh-detail-cost{font-size:12px;color:var(--ok);font-variant-numeric:tabular-nums}.uh-detail-agents{margin-left:auto;font-size:10px;color:var(--muted-dim);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.uh-detail-mini{display:grid;grid-template-columns:repeat(4,1fr);gap:6px;margin-bottom:12px}.uh-ministat{display:flex;flex-direction:column;gap:1px}.uh-ministat-val{font-size:12px;font-variant-numeric:tabular-nums}.uh-ministat-label{font-size:9px;color:var(--muted);text-transform:uppercase;letter-spacing:.05em}.uh-detail-models{display:flex;flex-direction:column;gap:5px}.uh-model-row{display:grid;grid-template-columns:130px 1fr auto;align-items:center;gap:8px}.uh-model-name{display:inline-flex;align-items:center;gap:5px;font-size:11px}.uh-model-bar{height:6px;border-radius:3px;background:var(--line);overflow:hidden}.uh-model-bar-fill{display:block;height:100%;border-radius:3px}.uh-model-cost{font-size:11px;color:var(--text);font-variant-numeric:tabular-nums}:root[data-theme=light] .uh-backdrop{background:#0f172a4d}.accounts-panel{grid-column:1;grid-row:2 / -1;background:var(--panel);border-right:1px solid var(--line);display:flex;flex-direction:column;min-height:0;overflow-y:auto}.ap-header{display:flex;align-items:center;gap:8px;padding:12px 12px 10px 14px;border-bottom:1px solid var(--line);flex-shrink:0}.ap-header h3{margin:0;font-size:12px;font-weight:600;letter-spacing:.02em}.ap-header-right{margin-left:auto;display:flex;align-items:center;gap:4px}.ap-refresh{padding:1px 6px;font-size:11px}.ap-account{padding:9px 14px 8px;border-bottom:1px solid var(--line-soft)}.ap-account.active{background:color-mix(in srgb,var(--accent) 6%,transparent)}.ap-account.disabled{opacity:.5}.ap-account-head{display:flex;align-items:center;gap:6px;margin-bottom:6px}.ap-num{font-size:10px;color:var(--muted);min-width:9px}.ap-alias{font-size:11px;font-weight:600;color:var(--fg)}.ap-email{font-size:10.5px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1}.ap-badge-active{font-size:10px;color:var(--accent);flex-shrink:0}.ap-switch{padding:1px 7px;font-size:10px;flex-shrink:0}.ap-lane{display:grid;grid-template-columns:30px 1fr 32px;grid-template-areas:"label track pct" ". reset reset";align-items:center;gap:2px 6px;margin-bottom:3px}.ap-lane-label{grid-area:label;font-size:10px;color:var(--muted-dim)}.ap-lane-track{grid-area:track;height:4px;border-radius:2px;background:var(--line-soft);overflow:hidden}.ap-lane-fill{height:100%;border-radius:2px}.ap-lane-pct{grid-area:pct;font-size:10px;text-align:right}.ap-lane-reset{grid-area:reset;font-size:9px;color:var(--muted)}.ap-lane-reset:empty{display:none}.ap-meta{display:flex;align-items:baseline;gap:8px;margin-top:5px;font-size:9px;color:var(--muted)}.ap-err,.ap-stale{color:var(--warn)}.ap-age{margin-left:auto;white-space:nowrap;cursor:help}.ap-empty{display:flex;flex-direction:column;gap:4px;padding:12px 14px;font-size:11px;color:var(--muted)}.ap-hint{font-size:10px;line-height:1.5;color:var(--muted)}.ap-empty .ap-cmd{display:block;margin:6px 0 2px;padding:7px 9px;border-radius:5px;background:color-mix(in srgb,var(--accent) 7%,transparent);color:var(--text);font-family:var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);font-size:10px;line-height:1.5;white-space:pre-wrap;overflow-wrap:anywhere;-webkit-user-select:all;user-select:all}.ap-empty .ap-hint code{padding:1px 4px;border-radius:3px;background:color-mix(in srgb,var(--accent) 7%,transparent);font-size:10px}.ap-failure{margin:8px 14px 0;padding:6px 8px;border-radius:4px;background:color-mix(in srgb,var(--err) 12%,transparent);color:var(--err);font-size:10px;word-break:break-word}.ap-auto{padding:12px 14px 11px;border-top:1px solid var(--line)}.ap-auto-head{display:flex;align-items:center;gap:10px;min-height:24px}.ap-auto-ctl{margin-left:auto;display:flex;align-items:center;gap:8px;flex-shrink:0}.ap-auto-title{font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}.ap-auto-state{display:inline-flex;align-items:center;gap:5px;padding:2px 8px 2px 7px;border:1px solid var(--line);border-radius:999px;background:transparent;font:inherit;font-size:10px;color:var(--muted);line-height:1.5}.ap-dot{width:5px;height:5px;border-radius:50%;background:currentColor;opacity:.55;flex-shrink:0}button.ap-auto-state{cursor:pointer;transition:border-color .12s,color .12s}button.ap-auto-state:hover:not(:disabled){border-color:var(--accent-dim);color:var(--accent)}button.ap-auto-state:focus-visible{outline:2px solid var(--accent);outline-offset:1px}button.ap-auto-state:disabled{cursor:default;opacity:.6}.ap-auto-state.live{border-color:var(--accent-dim);color:var(--accent)}.ap-pulse{position:relative;width:5px;height:5px;border-radius:50%;background:currentColor;flex-shrink:0}.ap-pulse:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;border-radius:50%;border:1px solid currentColor;animation:ap-ping 2.6s cubic-bezier(.22,.61,.36,1) infinite}@keyframes ap-ping{0%{transform:scale(1);opacity:.7}50%{transform:scale(3.2);opacity:0}to{transform:scale(3.2);opacity:0}}@media(prefers-reduced-motion:reduce){.ap-pulse:after{animation:none;opacity:.35;transform:scale(2.2)}}.ap-auto-note{margin:9px 0 0;font-size:9px;line-height:1.6;color:var(--muted)}.ap-auto-note .ap-pulse{display:inline-block;margin-right:5px;vertical-align:1px;color:var(--accent)}.ap-auto-note code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fg);white-space:nowrap}.ap-auto-now{font-size:10px;color:var(--muted);font-variant-numeric:tabular-nums}.ap-auto-now:after{content:"/";margin-left:8px;color:var(--muted);opacity:.45}.ap-auto-now.near{color:var(--warn)}.ap-field{position:relative;display:inline-block}.ap-field:after{content:"";position:absolute;right:7px;top:50%;width:5px;height:5px;margin-top:-3px;border-right:1.2px solid currentColor;border-bottom:1.2px solid currentColor;transform:rotate(45deg);opacity:.5;pointer-events:none}.ap-field select{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:var(--bg-soft);color:var(--text);border:1px solid var(--line);border-radius:5px;padding:2px 19px 2px 7px;font:inherit;font-size:11px;line-height:1.5;text-align:right;cursor:pointer;transition:border-color .12s,color .12s}.ap-field select:hover:not(:disabled){border-color:var(--accent-dim);color:var(--accent)}.ap-field select:focus-visible{outline:2px solid var(--accent);outline-offset:1px}.ap-field select:disabled{cursor:default;opacity:.6}.ap-field:has(select:hover:not(:disabled)):after{color:var(--accent);opacity:1}.ap-auto-foot{display:flex;align-items:baseline;gap:8px;margin-top:8px;padding-top:8px;border-top:1px solid var(--line-soft)}.ap-auto-result{font-size:10px;line-height:1.5;color:var(--muted);min-width:0}.ap-rotate{background:none;border:none;padding:0;font:inherit;font-size:9px;color:var(--muted);cursor:pointer;text-decoration:underline dotted;text-underline-offset:2px}.ap-rotate:hover:not(:disabled){color:var(--accent)}.ap-rotate:disabled{cursor:default;opacity:.6}.ap-footnote{margin:0;padding:9px 14px 2px;border-top:1px solid var(--line-soft);font-size:9px;line-height:1.5;color:var(--muted);cursor:help}.canvas-wrap.bubbling .react-flow__node{transition:transform .42s cubic-bezier(.22,.61,.36,1)}.canvas-wrap.bubbling .react-flow__node.react-flow__node-sessionGroup{transition:transform .42s cubic-bezier(.22,.61,.36,1),width .42s cubic-bezier(.22,.61,.36,1),height .42s cubic-bezier(.22,.61,.36,1)}@media(prefers-reduced-motion:reduce){.canvas-wrap.bubbling .react-flow__node,.canvas-wrap.bubbling .react-flow__node.react-flow__node-sessionGroup{transition:none}}.canvas-wrap.bubbling .react-flow__node.dragging,.canvas-wrap.dragging-any .react-flow__node,.canvas-wrap.dragging-any .react-flow__node.react-flow__node-sessionGroup{transition:none}
|
package/dist/web/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>agents-deck</title>
|
|
7
7
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='84' font-size='84'%3E%E2%97%89%3C/text%3E%3C/svg%3E" />
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-1i-Vckvo.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CEAcuttN.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-dag",
|
|
3
|
-
"version": "1.30.
|
|
3
|
+
"version": "1.30.8",
|
|
4
4
|
"description": "Live deck of Claude Code and Codex agents — watch parallel subagents fork, call tools, and return on one calm canvas. Also available as npx ccdeck and npx agent-dag.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,9 +42,31 @@ const CACHE_MS = 5_000;
|
|
|
42
42
|
// without a marker would misrepresent them (its own trust ceiling is 3600s).
|
|
43
43
|
const STALE_AFTER_MS = 15 * 60_000;
|
|
44
44
|
|
|
45
|
-
// Nudging the collector
|
|
46
|
-
|
|
45
|
+
// Nudging the collector.
|
|
46
|
+
//
|
|
47
|
+
// Two throttles, because the cost of asking is not the cost of fetching. When
|
|
48
|
+
// claude-swap's plan says nothing is due, `cswap list` fetches nothing — the
|
|
49
|
+
// spawn rate is bounded by the plan (one per 180-600s), not by how often we
|
|
50
|
+
// ask. So asking often is cheap in requests and only costs a subprocess, and
|
|
51
|
+
// asking often is exactly how `cswap watch` stays current: it re-asks every
|
|
52
|
+
// three seconds and therefore collects the moment a plan comes due.
|
|
53
|
+
//
|
|
54
|
+
// The exception is an ask that changes nothing — a claim held by another
|
|
55
|
+
// collector, a backoff, a plan that stays overdue. Repeating that every few
|
|
56
|
+
// seconds is pure spawn churn, so a second, slower throttle applies until the
|
|
57
|
+
// store actually moves.
|
|
58
|
+
const NUDGE_EVERY_MS = 15_000; // when the last ask produced new data
|
|
59
|
+
const NUDGE_QUIET_MS = 60_000; // when it did not
|
|
47
60
|
let _lastNudge = 0;
|
|
61
|
+
let _lastSeenFetch = 0; // newest fetchedAt observed, in seconds
|
|
62
|
+
|
|
63
|
+
// claude-swap's SERVE_TTL_S (180s) plus slack: below this age it serves from
|
|
64
|
+
// the store and fetches nothing, so asking earlier only costs a subprocess.
|
|
65
|
+
const FRESH_MIN_AGE_MS = 190_000;
|
|
66
|
+
|
|
67
|
+
// claude-swap's RECENT_429_WINDOW_S. While a 429 is this recent, its own
|
|
68
|
+
// congestion control is deliberately holding back, and so do we.
|
|
69
|
+
const RECENT_429_MS = 3_600_000;
|
|
48
70
|
|
|
49
71
|
/**
|
|
50
72
|
* Ask claude-swap to collect, if its own schedule says anything is due.
|
|
@@ -91,14 +113,107 @@ export function collectionDue(rows, slots, now) {
|
|
|
91
113
|
return false;
|
|
92
114
|
}
|
|
93
115
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Whether the engine path may be used to refresh this row now.
|
|
118
|
+
*
|
|
119
|
+
* claude-swap gates fetches two different ways (usage_store._row_eligible).
|
|
120
|
+
* On-demand surfaces — `cswap list`, status, switch — need the row to be BOTH
|
|
121
|
+
* stale and past its planned poll time. The auto engine needs it to be stale
|
|
122
|
+
* OR due, and stale means older than SERVE_TTL_S, which is 180 seconds. That
|
|
123
|
+
* OR is the whole difference: it is why a plan stretched out to 30 minutes
|
|
124
|
+
* still yields three-minute-old numbers to the engine, and why `cswap list`
|
|
125
|
+
* cannot do the same however often it is called.
|
|
126
|
+
*
|
|
127
|
+
* The plan is stretched for a reason, though, and one of those reasons must be
|
|
128
|
+
* respected rather than routed around: claude-swap runs AIMD congestion
|
|
129
|
+
* control on a budget it shares with every other machine holding the same
|
|
130
|
+
* account (POST_429_BACKOFF_MULT, RECENT_429_WINDOW_S). While an account is
|
|
131
|
+
* recovering from a 429, backing off IS the correct behaviour, and polling
|
|
132
|
+
* every 180 seconds through it would re-saturate exactly the window that needs
|
|
133
|
+
* to drain. So the engine path is used only for a healthy row: no live
|
|
134
|
+
* backoff, no failures, and no 429 seen within claude-swap's own recovery
|
|
135
|
+
* window.
|
|
136
|
+
*
|
|
137
|
+
* Exported for tests.
|
|
138
|
+
*/
|
|
139
|
+
export function freshenAllowed(row, now, recent429Ms = RECENT_429_MS) {
|
|
140
|
+
if (!row || typeof row.fetchedAt !== "number") return false; // the list path owns this case
|
|
141
|
+
if (process.env.AGENTS_DECK_NO_FRESHEN === "1") return false;
|
|
142
|
+
if (typeof row.backoffUntil === "number" && row.backoffUntil * 1000 > now) return false;
|
|
143
|
+
if ((row.consecutiveFailures ?? 0) > 0) return false;
|
|
144
|
+
if (typeof row.last429At === "number" && now - row.last429At * 1000 < recent429Ms) return false;
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** freshenAllowed, plus old enough that asking would actually fetch. */
|
|
149
|
+
export function freshenDue(row, now, { minAgeMs = FRESH_MIN_AGE_MS, recent429Ms = RECENT_429_MS } = {}) {
|
|
150
|
+
if (!freshenAllowed(row, now, recent429Ms)) return false;
|
|
151
|
+
// Younger than the serve TTL: claude-swap would answer from the store
|
|
152
|
+
// without fetching, so asking achieves nothing but a subprocess.
|
|
153
|
+
return now - row.fetchedAt * 1000 >= minAgeMs;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* When this account's numbers will next be refreshed, in epoch ms.
|
|
158
|
+
*
|
|
159
|
+
* claude-swap's plan, except for a healthy active account, where the deck's
|
|
160
|
+
* own freshen tick gets there first. Exported for tests.
|
|
161
|
+
*/
|
|
162
|
+
export function nextReadAt(row, matches, fetchedAtMs, isActive, now) {
|
|
163
|
+
const planned = matches && typeof row?.nextPollAt === "number"
|
|
164
|
+
? Math.round(row.nextPollAt * 1000)
|
|
165
|
+
: null;
|
|
166
|
+
const freshenAt = (isActive && fetchedAtMs != null && freshenAllowed(row, now))
|
|
167
|
+
? fetchedAtMs + FRESH_MIN_AGE_MS
|
|
168
|
+
: null;
|
|
169
|
+
if (planned == null) return freshenAt;
|
|
170
|
+
if (freshenAt == null) return planned;
|
|
171
|
+
return Math.min(planned, freshenAt);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Keep the store moving while someone is looking at it.
|
|
176
|
+
*
|
|
177
|
+
* Two ways to ask, and the cheaper one is preferred:
|
|
178
|
+
* - something is due by claude-swap's own plan → `cswap list`, the ordinary
|
|
179
|
+
* on-demand pass every surface uses;
|
|
180
|
+
* - nothing is due but the active account's numbers have aged past the serve
|
|
181
|
+
* TTL → one `cswap auto --once --dry-run`, which is the engine path and so
|
|
182
|
+
* is judged on staleness rather than on the plan.
|
|
183
|
+
*
|
|
184
|
+
* What dry run guarantees is narrower than the name suggests, and worth stating
|
|
185
|
+
* exactly: it never switches accounts and never writes autoswitch state — the
|
|
186
|
+
* switch call is unreachable behind its dry-run return. Its collect pass, on
|
|
187
|
+
* the other hand, runs unconditionally, which is the point: it fetches, writes
|
|
188
|
+
* usage rows, and can rotate and persist an OAuth token exactly as `cswap list`
|
|
189
|
+
* does. So this is not a read-only call; it is the same collection every other
|
|
190
|
+
* surface performs, minus the switch.
|
|
191
|
+
*
|
|
192
|
+
* Either way claude-swap decides whether a network call actually happens, and
|
|
193
|
+
* this is throttled on top of that.
|
|
194
|
+
*/
|
|
195
|
+
function nudgeCollector(rows, slots, now, activeNum) {
|
|
196
|
+
// Did the last ask accomplish anything? Cheap proxy: the newest collection
|
|
197
|
+
// timestamp in the store.
|
|
198
|
+
let newest = 0;
|
|
199
|
+
for (const slot of slots) {
|
|
200
|
+
const f = rows[slot]?.fetchedAt;
|
|
201
|
+
if (typeof f === "number" && f > newest) newest = f;
|
|
202
|
+
}
|
|
203
|
+
const moved = newest > _lastSeenFetch;
|
|
204
|
+
_lastSeenFetch = Math.max(_lastSeenFetch, newest);
|
|
205
|
+
|
|
206
|
+
if (now - _lastNudge < (moved ? NUDGE_EVERY_MS : NUDGE_QUIET_MS)) return;
|
|
207
|
+
|
|
208
|
+
const due = collectionDue(rows, slots, now);
|
|
209
|
+
const freshen = !due && freshenDue(rows[String(activeNum)], now);
|
|
210
|
+
if (!due && !freshen) return;
|
|
97
211
|
|
|
98
212
|
_lastNudge = now;
|
|
213
|
+
const args = due ? ["list"] : ["auto", "--once", "--dry-run", "--json"];
|
|
99
214
|
// Fire-and-forget: this function is deliberately synchronous so callers never
|
|
100
215
|
// wait on it, and resolving the binary is the only async part.
|
|
101
|
-
cswapBin().then(bin => runDetached(bin,
|
|
216
|
+
cswapBin().then(bin => runDetached(bin, args)).catch(() => {});
|
|
102
217
|
}
|
|
103
218
|
|
|
104
219
|
async function readJson(path) {
|
|
@@ -161,7 +276,7 @@ export async function fetchClaudeAccounts({ force = false } = {}) {
|
|
|
161
276
|
// Kick a collection for the NEXT poll if anything is due — either because
|
|
162
277
|
// claude-swap's schedule says so, or because an account has never been
|
|
163
278
|
// fetched at all.
|
|
164
|
-
nudgeCollector(rows, Object.keys(seq.accounts), now);
|
|
279
|
+
nudgeCollector(rows, Object.keys(seq.accounts), now, seq.activeAccountNumber);
|
|
165
280
|
|
|
166
281
|
const order = Array.isArray(seq.sequence) && seq.sequence.length
|
|
167
282
|
? seq.sequence.map(String)
|
|
@@ -203,12 +318,11 @@ export async function fetchClaudeAccounts({ force = false } = {}) {
|
|
|
203
318
|
// this account is worth switching to.
|
|
204
319
|
headroom: lanes.length ? Math.max(0, 100 - Math.max(...lanes.map(l => l.pct))) : null,
|
|
205
320
|
fetchedAt: fetchedAtMs,
|
|
206
|
-
// When
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
nextAt: matches && typeof row.nextPollAt === "number" ? Math.round(row.nextPollAt * 1000) : null,
|
|
321
|
+
// When this account will next be read — the earlier of claude-swap's own
|
|
322
|
+
// plan and, for a healthy active account, the deck's freshen tick. The
|
|
323
|
+
// plan alone would promise "next in 15m" while the panel actually
|
|
324
|
+
// updates in three.
|
|
325
|
+
nextAt: nextReadAt(row, matches, fetchedAtMs, String(seq.activeAccountNumber) === num, now),
|
|
212
326
|
stale: fetchedAtMs == null || now - fetchedAtMs > STALE_AFTER_MS,
|
|
213
327
|
// Surfaced rather than hidden: a rate-limited or re-login-needed account
|
|
214
328
|
// is exactly the one the user is about to try switching to.
|
|
@@ -279,7 +393,7 @@ export async function requestCollection() {
|
|
|
279
393
|
const usage = await readJson(join(root, "cache", "usage.json"));
|
|
280
394
|
const rows = usage?.schemaVersion === 2 ? (usage.accounts ?? {}) : {};
|
|
281
395
|
const before = _lastNudge;
|
|
282
|
-
nudgeCollector(rows, Object.keys(seq.accounts), Date.now());
|
|
396
|
+
nudgeCollector(rows, Object.keys(seq.accounts), Date.now(), seq.activeAccountNumber);
|
|
283
397
|
return _lastNudge !== before;
|
|
284
398
|
}
|
|
285
399
|
|
package/src/server/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Single-file pure Node HTTP server, zero deps.
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
4
|
import { readFile, stat, mkdir, appendFile, open, truncate, readdir, unlink } from "node:fs/promises";
|
|
5
|
-
import { createReadStream, existsSync } from "node:fs";
|
|
5
|
+
import { createReadStream, existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { extname, join, resolve, dirname as pdirname, sep } from "node:path";
|
|
8
8
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -13,6 +13,14 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
13
13
|
const PKG_ROOT = resolve(__dirname, "..", "..");
|
|
14
14
|
const WEB_DIST = resolve(PKG_ROOT, "dist", "web");
|
|
15
15
|
|
|
16
|
+
// Read at import — i.e. at boot, before an upgrade can overwrite these files.
|
|
17
|
+
// Reading it later would report whatever npm has since installed and hide the
|
|
18
|
+
// exact drift /api/version exists to expose. See src/server/self-update.mjs.
|
|
19
|
+
const RUNNING_VERSION = (() => {
|
|
20
|
+
try { return JSON.parse(readFileSync(join(PKG_ROOT, "package.json"), "utf8"))?.version ?? null; }
|
|
21
|
+
catch { return null; }
|
|
22
|
+
})();
|
|
23
|
+
|
|
16
24
|
const MIME = {
|
|
17
25
|
".html": "text/html; charset=utf-8",
|
|
18
26
|
".js": "application/javascript; charset=utf-8",
|
|
@@ -1007,6 +1015,13 @@ function handleSse(req, res) {
|
|
|
1007
1015
|
});
|
|
1008
1016
|
}
|
|
1009
1017
|
|
|
1018
|
+
async function handleVersion(_req, res) {
|
|
1019
|
+
const { versionReport } = await import(
|
|
1020
|
+
pathToFileURL(join(PKG_ROOT, "src/server/self-update.mjs")).href
|
|
1021
|
+
);
|
|
1022
|
+
send(res, 200, await versionReport({ running: RUNNING_VERSION, pkgRoot: PKG_ROOT }));
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1010
1025
|
async function handleQuota(req, res) {
|
|
1011
1026
|
const { fetchClaudeQuota } = await import(
|
|
1012
1027
|
pathToFileURL(join(PKG_ROOT, "src/server/quota.mjs")).href
|
|
@@ -1207,6 +1222,7 @@ export async function startServer({ port = 4317, host = "127.0.0.1", persist = n
|
|
|
1207
1222
|
if (req.method === "POST" && url.pathname === "/api/event") return guard(handleEventIngest(req, res), res);
|
|
1208
1223
|
if (req.method === "GET" && url.pathname === "/api/health") return handleHealth(req, res);
|
|
1209
1224
|
if (req.method === "GET" && url.pathname === "/events") return handleSse(req, res);
|
|
1225
|
+
if (req.method === "GET" && url.pathname === "/api/version") return guard(handleVersion(req, res), res);
|
|
1210
1226
|
if (req.method === "GET" && url.pathname === "/api/quota") return guard(handleQuota(req, res), res);
|
|
1211
1227
|
if (req.method === "GET" && url.pathname === "/api/codex-usage") return guard(handleCodexUsage(req, res), res);
|
|
1212
1228
|
if (req.method === "GET" && url.pathname === "/api/codex-quota") return guard(handleCodexQuota(req, res), res);
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// Tells the deck when the code it is executing is no longer the code on disk.
|
|
2
|
+
//
|
|
3
|
+
// Node caches every module at import. A deck that was already running when
|
|
4
|
+
// `npm i -g agents-deck` replaced its files keeps executing the OLD code until
|
|
5
|
+
// the process restarts — and nothing says so. The terminal banner still prints
|
|
6
|
+
// the version it booted with, and the browser's __APP_VERSION__ is baked into
|
|
7
|
+
// whichever bundle it happened to load, so the UI can even show the NEW number
|
|
8
|
+
// while the server runs the old one.
|
|
9
|
+
//
|
|
10
|
+
// That is not theoretical. On 2026-08-12 a deck left running from before
|
|
11
|
+
// v1.30.4 kept spawning `claude --print /usage` once a minute, burning the
|
|
12
|
+
// whole hourly usage-endpoint budget and 429-ing claude-swap, while the fix sat
|
|
13
|
+
// unused on disk. The user had no way to see it.
|
|
14
|
+
//
|
|
15
|
+
// So we report three distinct versions:
|
|
16
|
+
// running — captured at boot by the caller, before an upgrade can land
|
|
17
|
+
// installed — re-read from disk per call; what a restart would run
|
|
18
|
+
// latest — npm's dist-tag, fetched at most once a day
|
|
19
|
+
//
|
|
20
|
+
// Notify only. We never run `npm i` on the user's behalf: agents-deck may be
|
|
21
|
+
// running globally, from an npx cache, or from a git checkout, and each wants a
|
|
22
|
+
// different command (or none at all).
|
|
23
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
24
|
+
import { homedir } from "node:os";
|
|
25
|
+
import { dirname, join } from "node:path";
|
|
26
|
+
|
|
27
|
+
const CHECK_MS = 24 * 3600_000; // same daily cadence as the ccusage/cswap checks
|
|
28
|
+
const FETCH_TIMEOUT_MS = 6_000;
|
|
29
|
+
// A third marker path: ccusage owns ~/.agents-deck/ccusage/.last-update-check
|
|
30
|
+
// and cswap owns ~/.agents-deck/.cswap-update-check. Sharing one would make the
|
|
31
|
+
// three features fight over a single daily slot.
|
|
32
|
+
const MARKER = join(homedir(), ".agents-deck", ".self-update-check");
|
|
33
|
+
|
|
34
|
+
let _inflight = null; // dedupe concurrent /api/version calls into one fetch
|
|
35
|
+
|
|
36
|
+
// ── version comparison ───────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
/** True when `a` sorts before `b`. Numeric-segment compare, same shape as the
|
|
39
|
+
* one in cswap-install.mjs — non-numeric segments count as 0, missing
|
|
40
|
+
* segments pad with 0, so "1.30" < "1.30.1" and "1.9.0" < "1.10.0". */
|
|
41
|
+
export function isOlder(a, b) {
|
|
42
|
+
if (typeof a !== "string" || typeof b !== "string") return false;
|
|
43
|
+
const seg = (v) => v.split(/[.\-+]/).map(n => parseInt(n, 10)).map(n => Number.isNaN(n) ? 0 : n);
|
|
44
|
+
const x = seg(a), y = seg(b);
|
|
45
|
+
for (let i = 0; i < Math.max(x.length, y.length); i++) {
|
|
46
|
+
const d = (x[i] ?? 0) - (y[i] ?? 0);
|
|
47
|
+
if (d !== 0) return d < 0;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ── what is on disk ──────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
/** Version currently written in the package's own package.json. Deliberately
|
|
55
|
+
* read fresh on every call: that is the whole point — it changes under a
|
|
56
|
+
* running process when npm replaces the install. */
|
|
57
|
+
export function installedVersion(pkgRoot) {
|
|
58
|
+
try {
|
|
59
|
+
const v = JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf8"))?.version;
|
|
60
|
+
return typeof v === "string" ? v : null;
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** npx keeps each package in its own content-addressed cache directory, so
|
|
67
|
+
* `npm i -g` is the wrong advice there — nothing global exists to upgrade. */
|
|
68
|
+
export function isNpxInstall(pkgRoot) {
|
|
69
|
+
// Split on both separators, not `path.sep`: Windows paths reach here with
|
|
70
|
+
// backslashes but a POSIX-style path is still a valid input there, and a
|
|
71
|
+
// separator-agnostic test is what keeps this identical on all three
|
|
72
|
+
// platforms. Segment-wise, so a directory merely named "my_npx-tools" is not
|
|
73
|
+
// mistaken for the npx cache.
|
|
74
|
+
return typeof pkgRoot === "string" && pkgRoot.split(/[\\/]/).includes("_npx");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** A git checkout is the maintainer's own tree. Its version routinely sits
|
|
78
|
+
* ahead of npm, and telling someone to `npm i -g` over their working copy is
|
|
79
|
+
* actively wrong, so the registry side of the check is skipped there. */
|
|
80
|
+
export function isGitCheckout(pkgRoot) {
|
|
81
|
+
try { return existsSync(join(pkgRoot, ".git")); } catch { return false; }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The exact line the user can paste. */
|
|
85
|
+
export function upgradeCommand(pkgRoot, name = "agents-deck") {
|
|
86
|
+
return isNpxInstall(pkgRoot) ? `npx -y ${name}@latest` : `npm i -g ${name}@latest`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── what npm has ─────────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
// The dist-tags endpoint answers with ~20 bytes ({"latest":"1.30.7"}); the full
|
|
92
|
+
// packument is >2 KB and needs parsing we have no use for.
|
|
93
|
+
async function fetchLatest(name) {
|
|
94
|
+
try {
|
|
95
|
+
const res = await fetch(`https://registry.npmjs.org/-/package/${name}/dist-tags`, {
|
|
96
|
+
headers: { accept: "application/json", "user-agent": "agents-deck" },
|
|
97
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
98
|
+
});
|
|
99
|
+
if (!res.ok) return null;
|
|
100
|
+
const v = (await res.json())?.latest;
|
|
101
|
+
return typeof v === "string" ? v : null;
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// The marker carries the answer, not just the timestamp. The existing markers
|
|
108
|
+
// store only an mtime, which means a restart inside the 24h window forgets what
|
|
109
|
+
// npm said and shows nothing until the window expires.
|
|
110
|
+
function readMarker() {
|
|
111
|
+
try {
|
|
112
|
+
const m = JSON.parse(readFileSync(MARKER, "utf8"));
|
|
113
|
+
return typeof m?.at === "number" ? m : null;
|
|
114
|
+
} catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function writeMarker(at, version) {
|
|
119
|
+
try {
|
|
120
|
+
mkdirSync(dirname(MARKER), { recursive: true });
|
|
121
|
+
writeFileSync(MARKER, JSON.stringify({ at, version: version ?? null }));
|
|
122
|
+
} catch { /* a read-only home must not break the deck */ }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Last known npm `latest`, refreshed at most once per CHECK_MS. Returns the
|
|
126
|
+
* cached answer immediately when the window has not elapsed. */
|
|
127
|
+
async function latestOnNpm(name, now) {
|
|
128
|
+
const m = readMarker();
|
|
129
|
+
if (m && now - m.at < CHECK_MS) return m.version ?? null;
|
|
130
|
+
if (_inflight) return _inflight;
|
|
131
|
+
_inflight = fetchLatest(name)
|
|
132
|
+
// Stamp before deciding what to keep: a failed lookup must burn the day's
|
|
133
|
+
// slot rather than retry on every poll, but it must not erase a version we
|
|
134
|
+
// already knew.
|
|
135
|
+
.then(v => { writeMarker(now, v ?? m?.version ?? null); return v ?? m?.version ?? null; })
|
|
136
|
+
.catch(() => m?.version ?? null)
|
|
137
|
+
.finally(() => { _inflight = null; });
|
|
138
|
+
return _inflight;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ── the notice ───────────────────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
/** Pure: turns three version strings into at most one thing worth saying.
|
|
144
|
+
*
|
|
145
|
+
* "restart" outranks "upgrade" because it is the free fix — the newer code is
|
|
146
|
+
* already on the machine and a restart is all that stands between the user and
|
|
147
|
+
* it. Once restarted, the next check surfaces the upgrade if one is still due. */
|
|
148
|
+
export function pickNotice({ running, installed, latest }) {
|
|
149
|
+
if (running && installed && isOlder(running, installed)) {
|
|
150
|
+
return { kind: "restart", from: running, to: installed };
|
|
151
|
+
}
|
|
152
|
+
const have = installed ?? running;
|
|
153
|
+
if (have && latest && isOlder(have, latest)) {
|
|
154
|
+
return { kind: "upgrade", from: have, to: latest };
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Full answer for GET /api/version. Never throws, never blocks on the network
|
|
160
|
+
* for longer than FETCH_TIMEOUT_MS, and answers the local half even when the
|
|
161
|
+
* registry is unreachable. */
|
|
162
|
+
export async function versionReport({ running, pkgRoot, name = "agents-deck", now = Date.now() }) {
|
|
163
|
+
const installed = installedVersion(pkgRoot);
|
|
164
|
+
// A checkout has no meaningful "latest", and an opt-out means no egress at
|
|
165
|
+
// all. Both keep the running-vs-installed half, which is purely local.
|
|
166
|
+
const skipRegistry =
|
|
167
|
+
process.env.AGENTS_DECK_NO_UPDATE_CHECK === "1" ||
|
|
168
|
+
process.env.AGENTS_DECK_NO_INSTALL === "1" ||
|
|
169
|
+
isGitCheckout(pkgRoot);
|
|
170
|
+
const latest = skipRegistry ? null : await latestOnNpm(name, now);
|
|
171
|
+
return {
|
|
172
|
+
name,
|
|
173
|
+
running: running ?? null,
|
|
174
|
+
installed,
|
|
175
|
+
latest,
|
|
176
|
+
notice: pickNotice({ running, installed, latest }),
|
|
177
|
+
command: upgradeCommand(pkgRoot, name),
|
|
178
|
+
};
|
|
179
|
+
}
|