dsh-agy-link 0.4.67 → 0.4.74

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/dist/client.js CHANGED
@@ -36,7 +36,8 @@ window.__ModuleLoader__.load({
36
36
  move: "M9 5h.01M9 12h.01M9 19h.01M15 5h.01M15 12h.01M15 19h.01",
37
37
  arrowUp: "M12 19V5M5 12l7-7 7 7",
38
38
  arrowDown: "M12 5v14M19 12l-7 7-7-7",
39
- power: "M18.36 6.64a9 9 0 1 1-12.73 0M12 2v10"
39
+ power: "M18.36 6.64a9 9 0 1 1-12.73 0M12 2v10",
40
+ flame: "M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"
40
41
  };
41
42
  //#endregion
42
43
  //#region src/client/locales.ts
@@ -181,7 +182,17 @@ window.__ModuleLoader__.load({
181
182
  "account.disableTitle": "停用此账号(切号与调度时跳过)",
182
183
  "account.enabledToast": "已启用账号:{alias}",
183
184
  "account.disabledToast": "已停用账号:{alias}",
184
- "status.disabled": "已停用"
185
+ "status.disabled": "已停用",
186
+ "header.warmup": "一键预热",
187
+ "header.warmingUp": "预热中",
188
+ "account.warmup": "预热",
189
+ "account.warmingUp": "预热中",
190
+ "account.warmupTitle": "预热该账号(向 Google 发送极小探活请求,提前激活 7 天周配额计时器)",
191
+ "warmup.success": "账号预热成功",
192
+ "warmup.allSuccess": "一键预热任务已完成",
193
+ "warmup.skipped": "当前账号或模型处于冷却中,无需预热",
194
+ "config.autoWarmup": "7天周配额自动预热",
195
+ "config.autoWarmupDesc": "周配额到达重置时间或冷启动时自动唤醒 1 次启动新周期"
185
196
  };
186
197
  const en = {
187
198
  "section.label": "Antigravity",
@@ -321,7 +332,17 @@ window.__ModuleLoader__.load({
321
332
  "account.disableTitle": "Disable this account (skip in rotation)",
322
333
  "account.enabledToast": "Account enabled: {alias}",
323
334
  "account.disabledToast": "Account disabled: {alias}",
324
- "status.disabled": "Disabled"
335
+ "status.disabled": "Disabled",
336
+ "header.warmup": "Auto Warmup",
337
+ "header.warmingUp": "Warming up...",
338
+ "account.warmup": "Warmup",
339
+ "account.warmingUp": "Warming up...",
340
+ "account.warmupTitle": "Warm up this account (trigger 7-day weekly reset timer)",
341
+ "warmup.success": "Account warmed up successfully",
342
+ "warmup.allSuccess": "Warmup completed for all accounts",
343
+ "warmup.skipped": "Account or model is in cooldown, no warmup needed",
344
+ "config.autoWarmup": "7-Day Quota Auto-Warmup",
345
+ "config.autoWarmupDesc": "Automatically trigger a 1-token probe when weekly reset time is reached"
325
346
  };
326
347
  const ptBR = {
327
348
  "section.label": "Antigravity",
@@ -461,7 +482,17 @@ window.__ModuleLoader__.load({
461
482
  "account.disableTitle": "Desativar esta conta (ignorar na rotação)",
462
483
  "account.enabledToast": "Conta ativada: {alias}",
463
484
  "account.disabledToast": "Conta desativada: {alias}",
464
- "status.disabled": "Desativado"
485
+ "status.disabled": "Desativado",
486
+ "header.warmup": "Pré-aquecimento",
487
+ "header.warmingUp": "Aquecendo...",
488
+ "account.warmup": "Aquecer",
489
+ "account.warmingUp": "Aquecendo...",
490
+ "account.warmupTitle": "Aquecer esta conta (ativar cronômetro semanal de 7 dias)",
491
+ "warmup.success": "Conta aquecida com sucesso",
492
+ "warmup.allSuccess": "Pré-aquecimento concluído",
493
+ "warmup.skipped": "Conta em espera, sem necessidade de aquecimento",
494
+ "config.autoWarmup": "Pré-aquecimento automático de 7 dias",
495
+ "config.autoWarmupDesc": "Ativa automaticamente um ciclo quando o tempo de redefinição semanal é atingido"
465
496
  };
466
497
  const es = {
467
498
  "section.label": "Antigravity",
@@ -601,9 +632,94 @@ window.__ModuleLoader__.load({
601
632
  "account.disableTitle": "Deshabilitar esta cuenta (ignorar en rotación)",
602
633
  "account.enabledToast": "Cuenta habilitada: {alias}",
603
634
  "account.disabledToast": "Cuenta deshabilitada: {alias}",
604
- "status.disabled": "Deshabilitado"
635
+ "status.disabled": "Deshabilitado",
636
+ "header.warmup": "Precalentamiento",
637
+ "header.warmingUp": "Calentando...",
638
+ "account.warmup": "Calentar",
639
+ "account.warmingUp": "Calentando...",
640
+ "account.warmupTitle": "Calentar esta cuenta (activar temporizador semanal de 7 días)",
641
+ "warmup.success": "Cuenta calentada con éxito",
642
+ "warmup.allSuccess": "Precalentamiento completado",
643
+ "warmup.skipped": "Cuenta en espera, sin necesidad de calentar",
644
+ "config.autoWarmup": "Precalentamiento automático de 7 días",
645
+ "config.autoWarmupDesc": "Activa automáticamente un ciclo cuando se alcanza el tiempo de reinicio semanal"
605
646
  };
606
647
  //#endregion
648
+ //#region src/client/scroll-guard.ts
649
+ let isScrolling = false;
650
+ let scrollTimer = null;
651
+ let listenerAttached = false;
652
+ const settleListeners = /* @__PURE__ */ new Set();
653
+ let overlaySuspended = false;
654
+ function onScrollActivity() {
655
+ isScrolling = true;
656
+ if (scrollTimer !== null) clearTimeout(scrollTimer);
657
+ scrollTimer = setTimeout(() => {
658
+ isScrolling = false;
659
+ scrollTimer = null;
660
+ for (const fn of settleListeners) try {
661
+ fn();
662
+ } catch {}
663
+ }, 150);
664
+ }
665
+ /**
666
+ * Suspend every overlay scanner while a modal/overlay owns the screen.
667
+ *
668
+ * Opening the Antigravity console must freeze the conversation behind it: any
669
+ * DOM the scanners inject while the panel composites on top becomes visible
670
+ * flashing, and the modal's own periodic re-renders keep waking the global
671
+ * observers. On resume we fire the settle listeners once so scanners catch up
672
+ * on anything that changed while they were held back.
673
+ */
674
+ function setOverlaySuspended(active) {
675
+ if (overlaySuspended === active) return;
676
+ overlaySuspended = active;
677
+ if (!active) for (const fn of settleListeners) try {
678
+ fn();
679
+ } catch {}
680
+ }
681
+ /**
682
+ * The one predicate every overlay scanner should gate on: may I mutate the DOM
683
+ * right now? False during an active scroll gesture AND while an overlay owns
684
+ * the screen. Scanners must not act while this is true.
685
+ */
686
+ function isInteractionLocked() {
687
+ return isScrolling || overlaySuspended;
688
+ }
689
+ /** Register a callback to run once scrolling has settled. Returns an unregister function. */
690
+ function onScrollSettle(fn) {
691
+ ensureScrollListener();
692
+ settleListeners.add(fn);
693
+ return () => {
694
+ settleListeners.delete(fn);
695
+ };
696
+ }
697
+ function ensureScrollListener() {
698
+ if (typeof window === "undefined" || typeof window.addEventListener !== "function" || listenerAttached) return;
699
+ listenerAttached = true;
700
+ window.addEventListener("scroll", onScrollActivity, {
701
+ passive: true,
702
+ capture: true
703
+ });
704
+ window.addEventListener("wheel", onScrollActivity, {
705
+ passive: true,
706
+ capture: true
707
+ });
708
+ }
709
+ function disposeScrollListener() {
710
+ if (typeof window === "undefined" || typeof window.removeEventListener !== "function" || !listenerAttached) return;
711
+ window.removeEventListener("scroll", onScrollActivity, true);
712
+ window.removeEventListener("wheel", onScrollActivity, true);
713
+ listenerAttached = false;
714
+ if (scrollTimer !== null) {
715
+ clearTimeout(scrollTimer);
716
+ scrollTimer = null;
717
+ }
718
+ settleListeners.clear();
719
+ isScrolling = false;
720
+ overlaySuspended = false;
721
+ }
722
+ //#endregion
607
723
  //#region src/client/decisions.ts
608
724
  const DECISION_CSS = `
609
725
  .agy-decision-container {
@@ -740,14 +856,34 @@ window.__ModuleLoader__.load({
740
856
  return options;
741
857
  }
742
858
  /**
743
- * Scan DOM for decision prompts and mount interactive choice chips
859
+ * Scan DOM for decision prompts and mount interactive choice chips.
860
+ *
861
+ * IDEMPOTENCE (incident 2026-10-23 — "scroll back and forth = insane flashing"):
862
+ * DSH renders these containers inside a TanStack-Virtual list with dynamic
863
+ * measurement (`measureElement` + `anchorTo:"start"`). Injecting a toolbar
864
+ * changes the measured item height, which makes the virtualizer rewrite
865
+ * scrollTop; the resulting re-render rebuilds the markdown/heading nodes
866
+ * (dropping the per-HEADING guard) and this scanner injected the toolbar
867
+ * again — growth on every scroll pass, i.e. a flashing flywheel.
868
+ *
869
+ * Two independent guards now make injection strictly once-per-container:
870
+ * 1. `data-agy-decisions-scanned` on the container, written on EVERY pass
871
+ * (it used to be read here but never assigned anywhere in the codebase),
872
+ * 2. "a toolbar already exists in this container" — survives React
873
+ * rebuilding the heading nodes during a virtualizer remount.
744
874
  */
745
875
  function scanAndEnhanceDecisions() {
746
876
  if (typeof document === "undefined") return;
877
+ if (isInteractionLocked()) return;
747
878
  const markdownContainers = document.querySelectorAll("div[class*=\"_markdown_\"], [data-markdown-variant]");
748
879
  for (let i = 0; i < markdownContainers.length; i++) {
749
880
  const container = markdownContainers[i];
750
881
  if (!container || container.dataset.agyDecisionsScanned === "true") continue;
882
+ if (container.querySelector(".agy-decision-container")) {
883
+ container.dataset.agyDecisionsScanned = "true";
884
+ continue;
885
+ }
886
+ container.dataset.agyDecisionsScanned = "true";
751
887
  const headings = container.querySelectorAll("h2, h3, h4, p > strong");
752
888
  for (let j = 0; j < headings.length; j++) {
753
889
  const heading = headings[j];
@@ -804,12 +940,26 @@ window.__ModuleLoader__.load({
804
940
  }
805
941
  }
806
942
  let decisionChipsDispose = null;
943
+ let decisionScanTimer = null;
944
+ let unbindDecisionSettle = null;
945
+ function scheduleDecisionScan() {
946
+ if (isInteractionLocked()) return;
947
+ if (decisionScanTimer !== null) return;
948
+ decisionScanTimer = setTimeout(() => {
949
+ decisionScanTimer = null;
950
+ if (isInteractionLocked()) return;
951
+ scanAndEnhanceDecisions();
952
+ }, 100);
953
+ }
807
954
  function installDecisionChips() {
808
955
  if (typeof window === "undefined" || typeof document === "undefined") return () => {};
809
956
  decisionChipsDispose?.();
810
957
  injectStyles$3();
811
958
  const observer = new MutationObserver(() => {
812
- scanAndEnhanceDecisions();
959
+ scheduleDecisionScan();
960
+ });
961
+ unbindDecisionSettle = onScrollSettle(() => {
962
+ scheduleDecisionScan();
813
963
  });
814
964
  const setup = () => {
815
965
  if (document.body) {
@@ -824,6 +974,14 @@ window.__ModuleLoader__.load({
824
974
  else setup();
825
975
  const dispose = () => {
826
976
  observer.disconnect();
977
+ if (unbindDecisionSettle !== null) {
978
+ unbindDecisionSettle();
979
+ unbindDecisionSettle = null;
980
+ }
981
+ if (decisionScanTimer !== null) {
982
+ clearTimeout(decisionScanTimer);
983
+ decisionScanTimer = null;
984
+ }
827
985
  window.removeEventListener("DOMContentLoaded", setup);
828
986
  if (decisionChipsDispose === dispose) decisionChipsDispose = null;
829
987
  };
@@ -924,8 +1082,10 @@ window.__ModuleLoader__.load({
924
1082
  position: fixed;
925
1083
  inset: 0;
926
1084
  z-index: 999999;
927
- background: rgba(0, 0, 0, 0.85);
928
- backdrop-filter: blur(8px);
1085
+ background: rgba(0, 0, 0, 0.9);
1086
+ /* No full-viewport backdrop blur: same flicker class as the console overlay.
1087
+ The image viewer is already near-opaque; a solid alpha is stable. */
1088
+ overscroll-behavior: contain;
929
1089
  display: flex;
930
1090
  flex-direction: column;
931
1091
  user-select: none;
@@ -1194,6 +1354,7 @@ window.__ModuleLoader__.load({
1194
1354
  */
1195
1355
  function scanAndEnhanceImages() {
1196
1356
  if (typeof document === "undefined") return;
1357
+ if (isInteractionLocked()) return;
1197
1358
  const origin = getOrigin();
1198
1359
  if (origin && !origin.includes("127.0.0.1") && !origin.includes("localhost")) {
1199
1360
  const images = document.querySelectorAll("img[src*=\"/plugins/agy-link/media\"]");
@@ -1283,12 +1444,26 @@ window.__ModuleLoader__.load({
1283
1444
  }
1284
1445
  }
1285
1446
  let imageEnhancerDispose = null;
1447
+ let imageScanTimer = null;
1448
+ let unbindImageSettle = null;
1449
+ function scheduleImageScan() {
1450
+ if (isInteractionLocked()) return;
1451
+ if (imageScanTimer !== null) return;
1452
+ imageScanTimer = setTimeout(() => {
1453
+ imageScanTimer = null;
1454
+ if (isInteractionLocked()) return;
1455
+ scanAndEnhanceImages();
1456
+ }, 100);
1457
+ }
1286
1458
  function installImageEnhancer() {
1287
1459
  if (typeof window === "undefined" || typeof document === "undefined") return () => {};
1288
1460
  imageEnhancerDispose?.();
1289
1461
  injectStyles$2();
1290
1462
  const observer = new MutationObserver(() => {
1291
- scanAndEnhanceImages();
1463
+ scheduleImageScan();
1464
+ });
1465
+ unbindImageSettle = onScrollSettle(() => {
1466
+ scheduleImageScan();
1292
1467
  });
1293
1468
  const setup = () => {
1294
1469
  if (document.body) {
@@ -1303,6 +1478,14 @@ window.__ModuleLoader__.load({
1303
1478
  else setup();
1304
1479
  const dispose = () => {
1305
1480
  observer.disconnect();
1481
+ if (unbindImageSettle !== null) {
1482
+ unbindImageSettle();
1483
+ unbindImageSettle = null;
1484
+ }
1485
+ if (imageScanTimer !== null) {
1486
+ clearTimeout(imageScanTimer);
1487
+ imageScanTimer = null;
1488
+ }
1306
1489
  window.removeEventListener("DOMContentLoaded", setup);
1307
1490
  if (imageEnhancerDispose === dispose) imageEnhancerDispose = null;
1308
1491
  };
@@ -1316,12 +1499,23 @@ window.__ModuleLoader__.load({
1316
1499
  if (mod === void 0 || mod === null) throw new Error("dsh-agy-link toolview requires react at render time (browser host)");
1317
1500
  return mod;
1318
1501
  }
1319
- function makeToggle(setValue) {
1320
- return () => setValue((prev) => !prev);
1321
- }
1322
- function useToggle(initial = false) {
1323
- const [value, setValue] = react().useState(initial);
1324
- return [value, makeToggle(setValue)];
1502
+ /** Cache user-expanded tool cards across TanStack Virtualizer remount cycles */
1503
+ const expandedToolBlockKeys = /* @__PURE__ */ new Set();
1504
+ function useToggle(initial = false, key) {
1505
+ const R2 = react();
1506
+ const init = key ? expandedToolBlockKeys.has(key) : initial;
1507
+ const [value, setValue] = R2.useState(init);
1508
+ const toggle = () => {
1509
+ setValue((prev) => {
1510
+ const next = !prev;
1511
+ if (key) {
1512
+ if (next) expandedToolBlockKeys.add(key);
1513
+ else expandedToolBlockKeys.delete(key);
1514
+ }
1515
+ return next;
1516
+ });
1517
+ };
1518
+ return [value, toggle];
1325
1519
  }
1326
1520
  function hx(type, props, ...children) {
1327
1521
  return react().createElement(type, props, ...children);
@@ -2096,12 +2290,12 @@ window.__ModuleLoader__.load({
2096
2290
  }
2097
2291
  }
2098
2292
  function AgyMirrorToolView(props) {
2099
- const [open, setOpen] = useToggle(false);
2100
2293
  const h = hx;
2101
- const { block, cwd, home, openFile, inspect, t } = props;
2294
+ const { block, cwd, home, openFile, inspect, t, callId } = props;
2102
2295
  const settle = isSettled(block);
2103
2296
  const err = isError(block);
2104
2297
  const model = mirrorCardModel(block, cwd, home);
2298
+ const [open, setOpen] = useToggle(false, callId ? `call:${callId}` : `${model.kind}:${model.tool ?? ""}:${model.title}:${model.path ?? model.command ?? ""}`);
2105
2299
  const state = !settle ? "run" : err ? "err" : "ok";
2106
2300
  const title = displayTitle(model, t);
2107
2301
  const summary = displaySummary(model, cwd, home);
@@ -2217,10 +2411,10 @@ window.__ModuleLoader__.load({
2217
2411
  * friends) so an ordinary Code Mode call reads exactly like a native tool row.
2218
2412
  */
2219
2413
  function AgyCodeToolView(props) {
2220
- const [open, setOpen] = useToggle(false);
2221
2414
  const h = hx;
2222
- const { block, inspect, t } = props ?? {};
2415
+ const { block, inspect, t, callId } = props ?? {};
2223
2416
  const model = runCodeModel(block);
2417
+ const [open, setOpen] = useToggle(false, callId ? `call:${callId}` : `code:${model.title}`);
2224
2418
  const settle = block !== void 0 && isSettled(block);
2225
2419
  const err = block !== void 0 && isError(block);
2226
2420
  const state = !settle ? "run" : err ? "err" : "ok";
@@ -2878,6 +3072,12 @@ div[data-agy-mermaid-svg] svg {
2878
3072
  min-width: 280px;
2879
3073
  height: auto;
2880
3074
  }
3075
+ .agy-mermaid-body {
3076
+ display: flex;
3077
+ justify-content: center;
3078
+ width: 100%;
3079
+ overflow-x: auto;
3080
+ }
2881
3081
  .agy-mermaid-toolbar {
2882
3082
  display: flex;
2883
3083
  justify-content: flex-end;
@@ -3133,6 +3333,12 @@ body > [id^="iagy-mmd"] .error-icon {
3133
3333
  return l;
3134
3334
  }).join("\n");
3135
3335
  }
3336
+ function isInvalidOrCollapsedSvg(svg) {
3337
+ if (!svg || svg.trim() === "") return true;
3338
+ if (svg.includes("class=\"error-icon\"") || svg.includes("class=\"error-text\"") || svg.includes("aria-roledescription=\"error\"")) return true;
3339
+ if (svg.includes("viewBox=\"0 0 0 0\"") || svg.includes("viewBox=\"-8 -8 16 16\"") || svg.includes("max-width: 16px") || svg.includes("max-width: 0px")) return true;
3340
+ return false;
3341
+ }
3136
3342
  async function renderInto(block, source, lang = "") {
3137
3343
  block.querySelector("[data-agy-mermaid-svg]")?.remove();
3138
3344
  const mount = document.createElement("div");
@@ -3165,8 +3371,8 @@ body > [id^="iagy-mmd"] .error-icon {
3165
3371
  else throw parseErr;
3166
3372
  }
3167
3373
  if (!parseValidated && typeof api.parse !== "function") targetSource = repairMermaidSource(targetSource);
3168
- const { svg } = await api.render(id, targetSource, mount);
3169
- if (!svg || svg.trim() === "" || svg.includes("class=\"error-icon\"") || svg.includes("class=\"error-text\"") || svg.includes("aria-roledescription=\"error\"")) throw new Error("Mermaid returned an empty or error diagram");
3374
+ const { svg } = await api.render(id, targetSource);
3375
+ if (isInvalidOrCollapsedSvg(svg)) throw new Error("Mermaid returned an empty or invalid diagram");
3170
3376
  mount.innerHTML = "";
3171
3377
  const toolbar = document.createElement("div");
3172
3378
  toolbar.className = "agy-mermaid-toolbar";
@@ -3207,14 +3413,17 @@ body > [id^="iagy-mmd"] .error-icon {
3207
3413
  }
3208
3414
  let scanTimer$1 = null;
3209
3415
  function scheduleScan$1() {
3416
+ if (isInteractionLocked()) return;
3210
3417
  if (scanTimer$1 !== null) return;
3211
3418
  scanTimer$1 = setTimeout(() => {
3212
3419
  scanTimer$1 = null;
3420
+ if (isInteractionLocked()) return;
3213
3421
  scan();
3214
3422
  }, 150);
3215
3423
  }
3216
3424
  function scan() {
3217
3425
  if (typeof document === "undefined") return;
3426
+ if (isInteractionLocked()) return;
3218
3427
  cleanupTempElements();
3219
3428
  const blocks = document.querySelectorAll(".md-code-block");
3220
3429
  for (let i = 0; i < blocks.length; i++) {
@@ -3232,6 +3441,7 @@ body > [id^="iagy-mmd"] .error-icon {
3232
3441
  }
3233
3442
  let activeObserver$1 = null;
3234
3443
  let domContentLoadedHandler$1 = null;
3444
+ let unbindMermaidSettle = null;
3235
3445
  /**
3236
3446
  * Install the mermaid overlay. Returns a disposer that disconnects the
3237
3447
  * MutationObserver and cancels pending setup — wrap it in ctx.effect so a
@@ -3246,6 +3456,9 @@ body > [id^="iagy-mmd"] .error-icon {
3246
3456
  scheduleScan$1();
3247
3457
  });
3248
3458
  activeObserver$1 = observer;
3459
+ unbindMermaidSettle = onScrollSettle(() => {
3460
+ scheduleScan$1();
3461
+ });
3249
3462
  const setup = () => {
3250
3463
  if (document.body) {
3251
3464
  cleanupTempElements();
@@ -3268,6 +3481,10 @@ body > [id^="iagy-mmd"] .error-icon {
3268
3481
  activeObserver$1.disconnect();
3269
3482
  activeObserver$1 = null;
3270
3483
  }
3484
+ if (unbindMermaidSettle !== null) {
3485
+ unbindMermaidSettle();
3486
+ unbindMermaidSettle = null;
3487
+ }
3271
3488
  if (domContentLoadedHandler$1 !== null) {
3272
3489
  window.removeEventListener("DOMContentLoaded", domContentLoadedHandler$1);
3273
3490
  domContentLoadedHandler$1 = null;
@@ -3334,42 +3551,57 @@ div[data-variant="think"] [class*="thinkBody"] {
3334
3551
  }
3335
3552
  }
3336
3553
  /**
3554
+ * Persistent cache of handled thinking turns (keyed by turn fingerprint).
3555
+ * Survived across TanStack Virtualizer unmount/remount cycles.
3556
+ */
3557
+ const handledTurns = /* @__PURE__ */ new Set();
3558
+ function getTurnKey(root, text) {
3559
+ const parent = root.closest("[data-message-id], [data-item-index], [data-index], [data-role=\"message-content\"]");
3560
+ return `${parent?.getAttribute("data-message-id") ?? parent?.getAttribute("data-index") ?? parent?.getAttribute("data-item-index") ?? ""}:${text.slice(0, 100)}`;
3561
+ }
3562
+ /**
3337
3563
  * Try to expand a thinking row once when its body contains real prose
3338
3564
  * (not a bare [agy thinking turn · N tokens] chip). Uses the host's own
3339
3565
  * disclosure control when present — never mutates thinkBody children.
3340
- *
3341
- * A row the user manually COLLAPSED is recorded in a WeakSet keyed by the
3342
- * row element; React keeps the same element identity across its own
3343
- * re-renders, so the scanner stops re-expanding it (the old data attribute
3344
- * survived only until React rebuilt the node).
3345
3566
  */
3346
- const userCollapsed = typeof WeakSet !== "undefined" ? /* @__PURE__ */ new WeakSet() : null;
3347
3567
  function tryExpandThinkRow(root) {
3348
3568
  if (root.dataset.agyExpanded === "true") return;
3349
- if (userCollapsed?.has(root) === true) return;
3350
3569
  const body = root.querySelector("[class*=\"thinkBody\"]");
3351
3570
  if (!body) return;
3352
3571
  const text = (body.textContent ?? "").trim();
3353
3572
  if (text === "") return;
3354
3573
  if (/^\[agy(?: thinking turn)?(?: · \d+ (?:thinking )?tokens)?\]\s*$/.test(text)) return;
3355
- const style = typeof getComputedStyle === "function" ? getComputedStyle(body) : null;
3356
- if (style && style.display !== "none" && body.clientHeight > 8) {
3574
+ const turnKey = getTurnKey(root, text);
3575
+ if (handledTurns.has(turnKey)) {
3357
3576
  root.dataset.agyExpanded = "true";
3358
3577
  return;
3359
3578
  }
3360
3579
  const toggle = root.querySelector("[data-disclosure-row]") ?? root.querySelector("[class*=\"disclosure\"]") ?? root.querySelector("button");
3361
- if (toggle) {
3362
- userCollapsed?.add(root);
3363
- try {
3364
- toggle.click();
3365
- root.dataset.agyExpanded = "true";
3366
- } catch {
3367
- userCollapsed?.delete(root);
3368
- }
3580
+ if (!toggle) return;
3581
+ if (!toggle.dataset.agyClickBound) {
3582
+ toggle.dataset.agyClickBound = "true";
3583
+ toggle.addEventListener("click", () => {
3584
+ handledTurns.add(turnKey);
3585
+ }, {
3586
+ capture: true,
3587
+ passive: true
3588
+ });
3369
3589
  }
3590
+ if (isInteractionLocked()) return;
3591
+ if (body.clientHeight > 8) {
3592
+ handledTurns.add(turnKey);
3593
+ root.dataset.agyExpanded = "true";
3594
+ return;
3595
+ }
3596
+ handledTurns.add(turnKey);
3597
+ root.dataset.agyExpanded = "true";
3598
+ try {
3599
+ toggle.click();
3600
+ } catch {}
3370
3601
  }
3371
3602
  function scanThinkingRows() {
3372
3603
  if (typeof document === "undefined") return;
3604
+ if (isInteractionLocked()) return;
3373
3605
  const rows = document.querySelectorAll("div[data-variant=\"think\"]");
3374
3606
  for (let i = 0; i < rows.length; i++) {
3375
3607
  const row = rows[i];
@@ -3378,14 +3610,17 @@ div[data-variant="think"] [class*="thinkBody"] {
3378
3610
  }
3379
3611
  let scanTimer = null;
3380
3612
  function scheduleScan() {
3613
+ if (isInteractionLocked()) return;
3381
3614
  if (scanTimer !== null) return;
3382
3615
  scanTimer = setTimeout(() => {
3383
3616
  scanTimer = null;
3617
+ if (isInteractionLocked()) return;
3384
3618
  scanThinkingRows();
3385
- }, 200);
3619
+ }, 150);
3386
3620
  }
3387
3621
  let activeObserver = null;
3388
3622
  let domContentLoadedHandler = null;
3623
+ let unbindSettle = null;
3389
3624
  /**
3390
3625
  * Install the auto-expand reasoning overlay. Returns a disposer that
3391
3626
  * disconnects the MutationObserver — wrap it in ctx.effect so a plugin
@@ -3399,6 +3634,9 @@ div[data-variant="think"] [class*="thinkBody"] {
3399
3634
  scheduleScan();
3400
3635
  });
3401
3636
  activeObserver = observer;
3637
+ unbindSettle = onScrollSettle(() => {
3638
+ scheduleScan();
3639
+ });
3402
3640
  const setupObserver = () => {
3403
3641
  if (document.body) {
3404
3642
  observer.observe(document.body, {
@@ -3420,6 +3658,10 @@ div[data-variant="think"] [class*="thinkBody"] {
3420
3658
  activeObserver.disconnect();
3421
3659
  activeObserver = null;
3422
3660
  }
3661
+ if (unbindSettle !== null) {
3662
+ unbindSettle();
3663
+ unbindSettle = null;
3664
+ }
3423
3665
  if (domContentLoadedHandler !== null) {
3424
3666
  window.removeEventListener("DOMContentLoaded", domContentLoadedHandler);
3425
3667
  domContentLoadedHandler = null;
@@ -3489,6 +3731,7 @@ div[data-variant="think"] [class*="thinkBody"] {
3489
3731
  }
3490
3732
  const statusListeners = /* @__PURE__ */ new Set();
3491
3733
  let statusPollTimer = null;
3734
+ let statusPollInterval = 3e3;
3492
3735
  let statusFetchSeq = 0;
3493
3736
  function isStatusEqual(a, b) {
3494
3737
  if (a === b) return true;
@@ -3516,16 +3759,25 @@ div[data-variant="think"] [class*="thinkBody"] {
3516
3759
  }
3517
3760
  }
3518
3761
  function ensureStatusPolling() {
3519
- if (statusPollTimer !== null) return;
3762
+ if (statusPollTimer !== null) {
3763
+ if (statusPollInterval !== 3e3) {
3764
+ clearInterval(statusPollTimer);
3765
+ statusPollTimer = null;
3766
+ } else return;
3767
+ }
3768
+ statusPollInterval = 3e3;
3520
3769
  statusPollTick();
3521
3770
  statusPollTimer = setInterval(() => {
3522
3771
  statusPollTick();
3523
3772
  }, 3e3);
3524
3773
  }
3525
3774
  function stopStatusPollingIfIdle() {
3526
- if (statusListeners.size === 0 && statusPollTimer !== null) {
3775
+ if (statusListeners.size === 0 && statusPollTimer !== null && statusPollInterval === 3e3) {
3527
3776
  clearInterval(statusPollTimer);
3528
- statusPollTimer = null;
3777
+ statusPollInterval = 15e3;
3778
+ statusPollTimer = setInterval(() => {
3779
+ statusPollTick();
3780
+ }, 15e3);
3529
3781
  }
3530
3782
  }
3531
3783
  /**
@@ -3546,7 +3798,7 @@ div[data-variant="think"] [class*="thinkBody"] {
3546
3798
  }, []);
3547
3799
  const refresh = async () => {
3548
3800
  const st = await getStatus();
3549
- if (st !== null) notifyStatus(st);
3801
+ if (st !== null && !isStatusEqual(statusCache, st)) notifyStatus(st);
3550
3802
  };
3551
3803
  return {
3552
3804
  status,
@@ -3558,7 +3810,10 @@ div[data-variant="think"] [class*="thinkBody"] {
3558
3810
  open: false,
3559
3811
  listeners: /* @__PURE__ */ new Set(),
3560
3812
  setOpen(v) {
3813
+ if (agyModalStore.open === v) return;
3561
3814
  agyModalStore.open = v;
3815
+ setOverlaySuspended(v);
3816
+ if (typeof document !== "undefined" && document.documentElement) document.documentElement.classList.toggle("agy-modal-open", v);
3562
3817
  agyModalStore.listeners.forEach((fn) => fn());
3563
3818
  },
3564
3819
  subscribe(fn) {
@@ -3570,8 +3825,9 @@ div[data-variant="think"] [class*="thinkBody"] {
3570
3825
  };
3571
3826
  /**
3572
3827
  * Global modal host manager: ensures exactly one mounted session header
3573
- * hosts the modal dialog portal reactively. If the primary header unmounts,
3574
- * the next available header is immediately notified and seamlessly takes over.
3828
+ * hosts the modal dialog portal reactively. The most recently focused or mounted
3829
+ * header is prioritized immediately to prevent host handoff flicker when opened.
3830
+ * If the primary header unmounts, the next available header is seamlessly notified.
3575
3831
  */
3576
3832
  const modalHostManager = {
3577
3833
  listeners: /* @__PURE__ */ new Map(),
@@ -3585,7 +3841,9 @@ div[data-variant="think"] [class*="thinkBody"] {
3585
3841
  if (modalHostManager.activeId === id) {
3586
3842
  const next = modalHostManager.listeners.keys().next().value ?? null;
3587
3843
  modalHostManager.activeId = next;
3588
- if (next) modalHostManager.listeners.get(next)?.(true);
3844
+ if (next) try {
3845
+ modalHostManager.listeners.get(next)?.(true);
3846
+ } catch {}
3589
3847
  }
3590
3848
  };
3591
3849
  },
@@ -3593,8 +3851,12 @@ div[data-variant="think"] [class*="thinkBody"] {
3593
3851
  if (modalHostManager.activeId !== id) {
3594
3852
  const prev = modalHostManager.activeId;
3595
3853
  modalHostManager.activeId = id;
3596
- if (prev) modalHostManager.listeners.get(prev)?.(false);
3597
- modalHostManager.listeners.get(id)?.(true);
3854
+ if (prev && prev !== id) try {
3855
+ modalHostManager.listeners.get(prev)?.(false);
3856
+ } catch {}
3857
+ try {
3858
+ modalHostManager.listeners.get(id)?.(true);
3859
+ } catch {}
3598
3860
  }
3599
3861
  }
3600
3862
  };
@@ -3859,8 +4121,12 @@ div[data-variant="think"] [class*="thinkBody"] {
3859
4121
  scheduled = false;
3860
4122
  if (disposed) return;
3861
4123
  const wanted = resolveLabel();
3862
- for (const row of Array.from(document.querySelectorAll(NAV_ROW_SELECTOR))) if (isOwnNavRow(row.textContent, wanted)) row.setAttribute(NAV_ICON_MARKER, "");
3863
- else row.removeAttribute(NAV_ICON_MARKER);
4124
+ for (const row of Array.from(document.querySelectorAll(NAV_ROW_SELECTOR))) {
4125
+ const shouldMark = isOwnNavRow(row.textContent, wanted);
4126
+ if (shouldMark === row.hasAttribute(NAV_ICON_MARKER)) continue;
4127
+ if (shouldMark) row.setAttribute(NAV_ICON_MARKER, "");
4128
+ else row.removeAttribute(NAV_ICON_MARKER);
4129
+ }
3864
4130
  };
3865
4131
  const schedule = () => {
3866
4132
  if (scheduled || disposed) return;
@@ -3872,7 +4138,7 @@ div[data-variant="think"] [class*="thinkBody"] {
3872
4138
  observer.observe(document.body, {
3873
4139
  childList: true,
3874
4140
  subtree: true,
3875
- characterData: true
4141
+ attributeFilter: [NAV_ICON_MARKER]
3876
4142
  });
3877
4143
  return () => {
3878
4144
  disposed = true;
@@ -4267,7 +4533,7 @@ body.dark,
4267
4533
  }
4268
4534
 
4269
4535
  .agy-btn {
4270
- transition: all 0.15s ease;
4536
+ transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, filter 0.15s ease, transform 0.15s ease;
4271
4537
  user-select: none;
4272
4538
  }
4273
4539
  .agy-btn:hover:not(:disabled) {
@@ -4287,12 +4553,17 @@ body.dark,
4287
4553
  .agy-shimmer-track {
4288
4554
  position: relative;
4289
4555
  overflow: hidden;
4556
+ contain: paint;
4557
+ transform: translateZ(0);
4558
+ isolation: isolate;
4290
4559
  }
4291
4560
  .agy-shimmer-track::after {
4292
4561
  content: '';
4293
4562
  position: absolute;
4294
4563
  top: 0; left: 0; right: 0; bottom: 0;
4295
4564
  background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.22) 50%, transparent 100%);
4565
+ transform: translateX(-100%) translateZ(0);
4566
+ will-change: transform;
4296
4567
  animation: agy-shimmer 1.8s infinite ease-in-out;
4297
4568
  pointer-events: none;
4298
4569
  }
@@ -4301,13 +4572,24 @@ body.dark,
4301
4572
  inset: 0;
4302
4573
  z-index: 9999;
4303
4574
  background: var(--agy-modal-backdrop);
4304
- backdrop-filter: blur(6px);
4575
+ /* NO full-screen backdrop blur here. Filtering the whole viewport forces
4576
+ Chromium/Electron to re-sample the page every frame while anything behind
4577
+ it moves (scroll, streaming, animations) — the recurring "打开控制台后滚动
4578
+ 闪屏" root cause. The rgba dim below is stable and flicker-free. */
4579
+ overscroll-behavior: contain;
4305
4580
  display: flex;
4306
4581
  align-items: center;
4307
4582
  justify-content: center;
4308
4583
  padding: 16px;
4309
4584
  box-sizing: border-box;
4310
4585
  }
4586
+ /* While the console is open, freeze the conversation scroller behind it so
4587
+ nothing moves/reflows under the fixed overlay. scrollbar-gutter:stable on the
4588
+ host scroller keeps the width from jumping when overflow flips to hidden. */
4589
+ html.agy-modal-open [data-conversation-scroll] {
4590
+ overflow: hidden !important;
4591
+ overscroll-behavior: contain;
4592
+ }
4311
4593
  .agy-modal-panel {
4312
4594
  position: relative;
4313
4595
  width: 100%;
@@ -4322,6 +4604,8 @@ body.dark,
4322
4604
  overflow: hidden;
4323
4605
  color: var(--agy-text-primary);
4324
4606
  font-family: var(--agy-font-family);
4607
+ transform: translateZ(0);
4608
+ contain: content;
4325
4609
  }
4326
4610
  .agy-submodel-row {
4327
4611
  display: flex;
@@ -4579,6 +4863,15 @@ body.dark,
4579
4863
  };
4580
4864
  function apply(ctx) {
4581
4865
  injectGlobalStyles();
4866
+ ctx.effect(() => {
4867
+ ensureStatusPolling();
4868
+ return () => {
4869
+ if (statusPollTimer !== null) {
4870
+ clearInterval(statusPollTimer);
4871
+ statusPollTimer = null;
4872
+ }
4873
+ };
4874
+ }, "agy-link: status polling");
4582
4875
  const locale = ctx.locale;
4583
4876
  ctx.effect(() => {
4584
4877
  const disposePtBR = locale.addLanguage({
@@ -4808,15 +5101,41 @@ body.dark,
4808
5101
  };
4809
5102
  const refreshQuota = async (id) => {
4810
5103
  setLoadingAction(id ? `refresh:${id}` : "refresh:all");
4811
- const res = await postJson("/plugins/agy-link/pool/refresh-quota", { id });
4812
- if (res === null || res?.ok === false) {
5104
+ try {
5105
+ const res = await postJson("/plugins/agy-link/pool/refresh-quota", { id });
5106
+ if (res === null || res?.ok === false) {
5107
+ actionFailed();
5108
+ return;
5109
+ }
5110
+ await refresh();
5111
+ showToast(t("quota.refreshed"), "success");
5112
+ } finally {
5113
+ setLoadingAction(null);
5114
+ }
5115
+ };
5116
+ const warmupAccount = async (id) => {
5117
+ setLoadingAction(id ? `warmup:${id}` : "warmup:all");
5118
+ try {
5119
+ const res = await postJson("/plugins/agy-link/pool/warmup", {
5120
+ id,
5121
+ force: true,
5122
+ ...id ? {} : { all: true }
5123
+ });
5124
+ if (res === null || res?.ok === false) {
5125
+ actionFailed();
5126
+ return;
5127
+ }
5128
+ await refresh();
5129
+ const results = Array.isArray(res.results) ? res.results : [];
5130
+ const skippedCount = results.filter((r) => r.skipped).length;
5131
+ const successCount = results.filter((r) => r.success && !r.skipped).length;
5132
+ const failedCount = results.length - skippedCount - successCount;
5133
+ if (successCount > 0) showToast(id ? t("warmup.success") : t("warmup.allSuccess"), "success");
5134
+ else if (skippedCount > 0 && failedCount === 0) showToast(t("warmup.skipped"), "info");
5135
+ else actionFailed();
5136
+ } finally {
4813
5137
  setLoadingAction(null);
4814
- actionFailed();
4815
- return;
4816
5138
  }
4817
- await refresh();
4818
- setLoadingAction(null);
4819
- showToast(t("quota.refreshed"), "success");
4820
5139
  };
4821
5140
  const saveProxy = async (id) => {
4822
5141
  setLoadingAction(`proxy:${id}`);
@@ -4871,6 +5190,9 @@ body.dark,
4871
5190
  const accounts = pool?.accounts ?? [];
4872
5191
  const isAuthed = authPhase === "ok" || accounts.length > 0;
4873
5192
  const isBusy = loadingAction !== null;
5193
+ /** Busy for a per-account action; also blocks while a global action runs. */
5194
+ const accountActionBusy = (acc, action) => loadingAction === `${action}:${acc.id}`;
5195
+ isBusy && loadingAction?.includes(":");
4874
5196
  const renderSpinner = () => h("span", { className: "agy-spinner" });
4875
5197
  const renderToastBanner = () => {
4876
5198
  if (!toast) return null;
@@ -4920,8 +5242,7 @@ body.dark,
4920
5242
  "aria-label": t("aria.closeToast")
4921
5243
  }, uiIcon("x", 12)));
4922
5244
  };
4923
- const isRefreshingQuota = loadingAction === "refresh-quota";
4924
- const renderQuotaBar = (label, familyKey, acc) => renderFamilyQuotaBar(t, label, familyKey, acc, isRefreshingQuota);
5245
+ const renderQuotaBar = (label, familyKey, acc) => renderFamilyQuotaBar(t, label, familyKey, acc, loadingAction === "refresh:all" || loadingAction === `refresh:${acc.id}`);
4925
5246
  const authoritativeActiveId = pool?.activeAccountId ?? pool?.lastActiveAccountId ?? pool?.activeAccountIds?.google ?? pool?.primaryAccountId;
4926
5247
  const activeAccountId = optimisticActiveId ?? authoritativeActiveId;
4927
5248
  const renderedAccountCards = accounts.map((acc) => {
@@ -5132,7 +5453,7 @@ body.dark,
5132
5453
  padding: "2px 6px"
5133
5454
  },
5134
5455
  title: acc.enabled ? t("account.disableTitle") : t("account.enableTitle"),
5135
- disabled: isBusy,
5456
+ disabled: accountActionBusy(acc, "enable"),
5136
5457
  onClick: () => void toggleAccountEnabled(acc)
5137
5458
  }, loadingAction === `enable:${acc.id}` ? [renderSpinner(), acc.enabled ? t("account.disable") : t("account.enable")] : [uiIcon("power", 10, acc.enabled ? "var(--agy-text-secondary)" : "#ffffff"), ` ${acc.enabled ? t("account.disable") : t("account.enable")}`]), h("button", {
5138
5459
  type: "button",
@@ -5141,12 +5462,28 @@ body.dark,
5141
5462
  ...S.btnSm,
5142
5463
  gap: "3px",
5143
5464
  fontSize: "11px",
5144
- padding: "2px 6px"
5465
+ padding: "2px 6px",
5466
+ minWidth: "58px",
5467
+ justifyContent: "center"
5145
5468
  },
5146
5469
  title: t("account.syncTitle"),
5147
- disabled: isBusy,
5470
+ disabled: accountActionBusy(acc, "refresh"),
5148
5471
  onClick: () => void refreshQuota(acc.id)
5149
- }, loadingAction === `refresh:${acc.id}` ? [renderSpinner(), t("account.syncing")] : [uiIcon("refresh", 10), ` ${t("account.sync")}`]), acc.enabled && !isInUse ? h("button", {
5472
+ }, loadingAction === `refresh:${acc.id}` ? [renderSpinner(), t("account.syncing")] : [uiIcon("refresh", 10), ` ${t("account.sync")}`]), acc.enabled && !acc.authRequired ? h("button", {
5473
+ type: "button",
5474
+ className: "agy-btn",
5475
+ style: {
5476
+ ...S.btnSm,
5477
+ gap: "3px",
5478
+ fontSize: "11px",
5479
+ padding: "2px 6px",
5480
+ minWidth: "58px",
5481
+ justifyContent: "center"
5482
+ },
5483
+ title: t("account.warmupTitle"),
5484
+ disabled: accountActionBusy(acc, "warmup"),
5485
+ onClick: () => void warmupAccount(acc.id)
5486
+ }, loadingAction === `warmup:${acc.id}` ? [renderSpinner(), t("account.warmingUp")] : [uiIcon("flame", 10, "#f97316"), ` ${t("account.warmup")}`]) : null, acc.enabled && !isInUse ? h("button", {
5150
5487
  type: "button",
5151
5488
  className: "agy-btn",
5152
5489
  style: {
@@ -5156,7 +5493,7 @@ body.dark,
5156
5493
  padding: "2px 6px"
5157
5494
  },
5158
5495
  title: t("account.setActive"),
5159
- disabled: isBusy,
5496
+ disabled: accountActionBusy(acc, "active"),
5160
5497
  onClick: () => void setActiveAccount(acc.id)
5161
5498
  }, loadingAction === `active:${acc.id}` ? [renderSpinner(), t("account.settingActive")] : [uiIcon("zap", 10), ` ${t("account.setActive")}`]) : null, h("button", {
5162
5499
  type: "button",
@@ -5172,7 +5509,7 @@ body.dark,
5172
5509
  fontSize: "11px",
5173
5510
  padding: "2px 6px"
5174
5511
  },
5175
- disabled: isBusy,
5512
+ disabled: accountActionBusy(acc, "proxy"),
5176
5513
  onClick: () => setEditingProxyId(isEditingProxy ? null : acc.id)
5177
5514
  }, [uiIcon("globe", 10), ` ${t("proxy.button")}`]), accounts.length > 1 ? h("button", {
5178
5515
  type: "button",
@@ -5184,7 +5521,7 @@ body.dark,
5184
5521
  },
5185
5522
  title: t("account.removeTitle"),
5186
5523
  "aria-label": t("account.removeTitle"),
5187
- disabled: isBusy,
5524
+ disabled: accountActionBusy(acc, "remove"),
5188
5525
  onClick: () => void removeAccount(acc.id, displayAlias)
5189
5526
  }, loadingAction === `remove:${acc.id}` ? renderSpinner() : uiIcon("trash", 11, "var(--agy-danger-text)")) : null)), h("div", {
5190
5527
  key: "agy-body",
@@ -5273,7 +5610,7 @@ body.dark,
5273
5610
  background: "var(--agy-warn-btn-bg)",
5274
5611
  gap: "4px"
5275
5612
  },
5276
- disabled: isBusy,
5613
+ disabled: accountActionBusy(acc, "clearCooldown"),
5277
5614
  onClick: () => void clearCooldown(acc.id)
5278
5615
  }, loadingAction === `clearCooldown:${acc.id}` ? [renderSpinner(), ""] : [uiIcon("zap", 11, "var(--agy-warn-btn-text)"), ` ${t("account.clearCooldown")}`])) : null, isEditingProxy ? h("div", {
5279
5616
  key: "proxy-body",
@@ -5300,7 +5637,7 @@ body.dark,
5300
5637
  type: "button",
5301
5638
  className: "agy-btn",
5302
5639
  style: S.btnPrimary,
5303
- disabled: isBusy,
5640
+ disabled: accountActionBusy(acc, "proxy"),
5304
5641
  onClick: () => void saveProxy(acc.id)
5305
5642
  }, loadingAction === `proxy:${acc.id}` ? [renderSpinner(), t("proxy.save")] : t("proxy.save")), h("button", {
5306
5643
  type: "button",
@@ -5448,6 +5785,15 @@ body.dark,
5448
5785
  },
5449
5786
  onClick: () => setAddingAccount(true)
5450
5787
  }, [uiIcon("plus", 12, "#ffffff"), ` ${t("account.add")}`]) : null, h("button", {
5788
+ type: "button",
5789
+ className: "agy-btn",
5790
+ style: {
5791
+ ...S.btn,
5792
+ gap: "4px"
5793
+ },
5794
+ disabled: isBusy,
5795
+ onClick: () => void warmupAccount()
5796
+ }, loadingAction === "warmup:all" ? [renderSpinner(), t("header.warmingUp")] : [uiIcon("flame", 12, "#f97316"), ` ${t("header.warmup")}`]), h("button", {
5451
5797
  type: "button",
5452
5798
  className: "agy-btn",
5453
5799
  style: {
@@ -5544,6 +5890,22 @@ body.dark,
5544
5890
  style: pool?.mode === "round-robin" ? S.segBtnActive : S.segBtn,
5545
5891
  onClick: () => void setMode("round-robin")
5546
5892
  }, t("pool.roundRobin")))), h("div", { style: {
5893
+ display: "flex",
5894
+ alignItems: "center",
5895
+ justifyContent: "space-between"
5896
+ } }, h("div", { style: {
5897
+ display: "flex",
5898
+ flexDirection: "column"
5899
+ } }, h("span", { style: {
5900
+ color: "var(--agy-text-primary)",
5901
+ fontWeight: 600,
5902
+ fontSize: "12.5px"
5903
+ } }, t("config.autoWarmup")), h("span", { style: S.muted }, t("config.autoWarmupDesc"))), h("button", {
5904
+ type: "button",
5905
+ style: status?.autoWarmup !== false ? S.segBtnActive : S.segBtn,
5906
+ disabled: loadingAction === "config:autoWarmup",
5907
+ onClick: () => void setCfg("autoWarmup", status?.autoWarmup === false)
5908
+ }, status?.autoWarmup !== false ? t("account.disable") : t("account.enable"))), h("div", { style: {
5547
5909
  display: "flex",
5548
5910
  flexDirection: "column",
5549
5911
  gap: "6px"
@@ -5630,6 +5992,7 @@ body.dark,
5630
5992
  onClick: () => agyModalStore.setOpen(false)
5631
5993
  }, uiIcon("x", 14, "var(--agy-text-btn)"))), h("div", { style: {
5632
5994
  overflowY: "auto",
5995
+ overscrollBehavior: "contain",
5633
5996
  padding: "18px",
5634
5997
  flex: "1",
5635
5998
  background: "var(--agy-bg-panel)"
@@ -5643,7 +6006,13 @@ body.dark,
5643
6006
  const hostIdRef = useRef(null);
5644
6007
  if (hostIdRef.current === null) hostIdRef.current = "host_" + Math.random().toString(36).slice(2);
5645
6008
  const hostId = hostIdRef.current;
5646
- const [isPrimaryHost, setIsPrimaryHost] = useState(false);
6009
+ const [isPrimaryHost, setIsPrimaryHost] = useState(() => {
6010
+ if (modalHostManager.activeId === null) {
6011
+ modalHostManager.activeId = hostId;
6012
+ return true;
6013
+ }
6014
+ return modalHostManager.activeId === hostId;
6015
+ });
5647
6016
  useEffect(() => {
5648
6017
  return modalHostManager.register(hostId, setIsPrimaryHost);
5649
6018
  }, [hostId]);
@@ -5696,7 +6065,9 @@ body.dark,
5696
6065
  display: "inline-flex",
5697
6066
  alignItems: "center",
5698
6067
  gap: "6px",
5699
- boxShadow: "var(--agy-shadow-card)"
6068
+ boxShadow: "var(--agy-shadow-card)",
6069
+ flexShrink: 0,
6070
+ whiteSpace: "nowrap"
5700
6071
  }
5701
6072
  }, h("span", { style: {
5702
6073
  display: "inline-block",
@@ -5745,6 +6116,15 @@ body.dark,
5745
6116
  return h(AgySettingsSection, { isModal: false });
5746
6117
  });
5747
6118
  });
6119
+ ctx.effect(() => {
6120
+ ensureScrollListener();
6121
+ return () => {
6122
+ disposeScrollListener();
6123
+ agyModalStore.open = false;
6124
+ setOverlaySuspended(false);
6125
+ if (typeof document !== "undefined" && document.documentElement) document.documentElement.classList.remove("agy-modal-open");
6126
+ };
6127
+ });
5748
6128
  installAgyToolView(ctx);
5749
6129
  ctx.effect(() => installAutoExpandReasoning());
5750
6130
  ctx.effect(() => installMermaidRendering());