dsh-connect-trae 4.6.12 → 4.8.0

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/lib/client.js CHANGED
@@ -502,6 +502,123 @@ window.__ModuleLoader__.load({
502
502
  return options.sort((left, right) => left - right);
503
503
  }
504
504
  //#endregion
505
+ //#region src/reasoning.ts
506
+ /**
507
+ * The DSH thinking levels this plugin can carry, in ascending order.
508
+ *
509
+ * `max` is the fifth level Trae's own client advertises — its bundle ships the
510
+ * i18n keys `trae-chat-core.model.reasoning_effort.{low,medium,high,extra_high,max}`
511
+ * and `ai.model.reasoning_effort.{low,medium,high,xhigh,max}`, and publishes the
512
+ * per-model list from a remote config (`AI.agent.model.reasoning_effort_level_by_agent_model_v2`).
513
+ * Leaving it out of this list meant a level the vendor could start serving was
514
+ * filtered away at PARSE time, before anything downstream could decide (issue
515
+ * #60). Listing it does not fabricate the level: `supported` is still built
516
+ * only from what the upstream response actually contains, so a model that does
517
+ * not advertise `max` never offers it.
518
+ *
519
+ * `minimal` is kept for the same reason in reverse — it is a DSH level, so the
520
+ * type must be able to represent it even though no CN model has advertised it.
521
+ */
522
+ const TRAE_REASONING_EFFORTS = [
523
+ "minimal",
524
+ "low",
525
+ "medium",
526
+ "high",
527
+ "xhigh",
528
+ "max"
529
+ ];
530
+ //#endregion
531
+ //#region src/default-effort.ts
532
+ /**
533
+ * Which thinking level a model runs at when the caller names none.
534
+ *
535
+ * ## Why this module exists
536
+ *
537
+ * Trae's directory already tells us each model's own default level
538
+ * (`reasoning_effort_config.default_level`, parsed into `reasoning.defaultEffort`
539
+ * by `model-metadata.ts`) — measured 2026-11-03 on a live CN credential:
540
+ * `deepseek-v4.1-flash` says `high`, `glm-5.3` says `xhigh`. The card's meta row
541
+ * names the tiers it accepts, so the fact is known.
542
+ *
543
+ * DSH's model picker nevertheless reads **Default**, because the adapter never
544
+ * reports that level upward (`reasoning.defaultEffort` is absent from what
545
+ * {@link toPiModel} hands the kernel). The picker therefore cannot say which
546
+ * tier a request will run at, and the request itself carries no
547
+ * `reasoning_effort` — the upstream falls back to the same level it already
548
+ * published, so nothing is broken, but nothing is knowable either, and the user
549
+ * has no way to say "always use high on this model, regardless of what Trae
550
+ * ships as its default".
551
+ *
552
+ * ## The three seams that must agree (any one missing and the picker still says Default)
553
+ *
554
+ * 1. **The adapter** must report the level as `LlmResolvedModelInfo.reasoning.defaultEffort`.
555
+ * The kernel materialises that into every request that names no effort
556
+ * (`dsh-llm` `resolveCallWithInfo`: `const effective = requested ?? reasoning.defaultEffort`),
557
+ * and DSH's model picker takes its "current level" from the same metadata.
558
+ * 2. **A per-model saved choice** must survive a settings save. The settings
559
+ * channel rebuilds the whole region document through its schema, so a field
560
+ * absent from that schema is dropped on every save — the failure that once
561
+ * reset `wireConfigName` (see the `modelConfig` schema).
562
+ * 3. **The bridge** must translate the level it receives into Trae's wire name.
563
+ * `solo-bridge.ts` already does exactly that through the model's own
564
+ * `reasoningEfforts` table, and deletes the field when the model does not
565
+ * advertise it — so a level is never sent under a name the vendor never said.
566
+ *
567
+ * ## Precedence
568
+ *
569
+ * The user's explicit choice > the level upstream declares > this model's own
570
+ * middle tier.
571
+ *
572
+ * @module dsh-connect-trae/default-effort
573
+ */
574
+ /**
575
+ * The tier to fall back to when a model publishes a tier list but names no
576
+ * default of its own and the user has never chosen one.
577
+ *
578
+ * `medium` is tried first because it is the only middle tier shared by the
579
+ * families seen so far. It is deliberately not the only fallback: a model that
580
+ * publishes `low / high` (measured on `Doubao-Seed-2.1-Pro` and
581
+ * `step-5-preview`) has no `medium` at all, and picking its weakest tier instead
582
+ * would silently mean "thinking off" — that is not a fallback, that is the worst
583
+ * possible default.
584
+ */
585
+ const TRAE_FALLBACK_EFFORT = "medium";
586
+ /**
587
+ * The tiers a model actually declares, filtered to ones DSH can name.
588
+ *
589
+ * Upstream's vocabulary is not DSH's: Trae publishes `light / high / extra_high`
590
+ * and the parser maps them to `low / high / xhigh` (`EFFORT_MAP`). Anything that
591
+ * still fails to land in DSH's six names is dropped rather than passed through —
592
+ * a level the kernel cannot name would be rejected with
593
+ * `UNSUPPORTED_REASONING_EFFORT` and take the whole request with it.
594
+ *
595
+ * The result keeps `TRAE_REASONING_EFFORTS` order, so it reads weakest-first.
596
+ */
597
+ function declaredEfforts(model) {
598
+ const supported = model.supported ?? [];
599
+ return TRAE_REASONING_EFFORTS.filter((level) => supported.includes(level));
600
+ }
601
+ /**
602
+ * The tier this model should run at when a request names none.
603
+ *
604
+ * @param model - the model, with the tiers it declares and its upstream default.
605
+ * @param saved - the user's per-model choices (model id -> tier). A missing key
606
+ * means the user never chose, which is different from choosing the tier that
607
+ * happens to equal the upstream default.
608
+ * @returns a tier **this model declares**, or `undefined` when it declares none.
609
+ */
610
+ function defaultEffortFor(model, saved = {}) {
611
+ const declared = declaredEfforts(model);
612
+ if (declared.length === 0) return void 0;
613
+ const asked = saved[model.id];
614
+ const chosen = declared.find((level) => level === asked);
615
+ if (chosen !== void 0) return chosen;
616
+ const upstream = declared.find((level) => level === model.defaultEffort);
617
+ if (upstream !== void 0) return upstream;
618
+ if (declared.includes("medium")) return TRAE_FALLBACK_EFFORT;
619
+ return declared[Math.floor((declared.length - 1) / 2)];
620
+ }
621
+ //#endregion
505
622
  //#region src/model-order.ts
506
623
  /**
507
624
  * Wire name of the default SOLO chat function.
@@ -1270,6 +1387,16 @@ window.__ModuleLoader__.load({
1270
1387
  .dsm-trae-model-promo-offPeak{color:var(--dsw-alias-brand-primary,#5686fe);border-color:color-mix(in srgb,var(--dsw-alias-brand-primary,#5686fe) 40%,transparent);background:color-mix(in srgb,var(--dsw-alias-brand-primary,#5686fe) 10%,transparent)}
1271
1388
  .dsm-trae-model-meta{display:flex;align-items:center;gap:7px 12px;flex-wrap:wrap;color:var(--dsw-alias-label-tertiary,#999);font-size:11px;line-height:16px}
1272
1389
  .dsm-trae-model-options{display:flex;align-items:center;justify-content:flex-end;gap:12px;flex:none}
1390
+ /* The thinking-level picker, on the meta row. Deliberately shaped like the
1391
+ quiet meta text beside it (11px, secondary label) rather than like the
1392
+ options row: it edits a standing preference, and a control that shouts
1393
+ would read as a per-request input the way the image checkbox is. */
1394
+ .dsm-trae-model-effort{display:inline-flex;align-items:center;min-width:0}
1395
+ .dsm-trae-model-effort label{display:inline-flex;align-items:center;gap:4px;cursor:pointer}
1396
+ .dsm-trae-model-effort-select{max-width:112px;padding:0 2px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--dsw-alias-label-secondary,#c6c9d0);font:inherit;cursor:pointer}
1397
+ .dsm-trae-model-effort-select:hover{border-color:var(--dsw-alias-border-l2,#36373b)}
1398
+ .dsm-trae-model-effort-select:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
1399
+ .dsm-trae-model-effort-select:disabled{opacity:.6;cursor:not-allowed}
1273
1400
  .dsm-trae-model-image{display:inline-flex;align-items:center;gap:4px;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:11px;line-height:16px;cursor:pointer}
1274
1401
  .dsm-trae-model-image input{margin:0;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
1275
1402
  .dsm-trae-context-budget{display:flex;align-items:center;justify-content:flex-end;gap:12px;flex:none;margin:0;padding:0;border:0;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:11px;line-height:16px}
@@ -1534,6 +1661,29 @@ window.__ModuleLoader__.load({
1534
1661
  .dsm-trae-pool-log-warn{border-left-color:var(--dsw-alias-state-warn-primary,#b45309)}
1535
1662
  .dsm-trae-pool-log-error{border-left-color:var(--dsw-alias-state-error-primary,#d92d20)}
1536
1663
  .dsm-trae-pool-log-text{overflow-wrap:anywhere}
1664
+ /* THE OPTIONAL SIDEBAR PAGE (Config.sidebarPanel).
1665
+ *
1666
+ * The same card body, drawn as a full page in the centre column: no card border
1667
+ * or header, its own heading, and a scrolling column so a long pool log cannot
1668
+ * push the header off screen. Widths are capped at the host's own reading width
1669
+ * rather than the viewport, which is what keeps a wide monitor from stretching
1670
+ * the account table into unreadable rows.
1671
+ *
1672
+ * The page's back control reuses the card's own button shape but not its
1673
+ * dsm-btn class: this one is navigation, not an action. Its caret is drawn in
1674
+ * CSS, for the same reason the card's chevron is — the host primitives' icon
1675
+ * names are not a stable contract across DSH releases. */
1676
+ .dsm-trae-page{box-sizing:border-box;height:100%;display:flex;flex-direction:column;align-items:center;gap:20px;padding:28px clamp(24px,4vw,48px) 48px;overflow:auto;color:var(--dsw-alias-label-primary,#e6e6e6)}
1677
+ .dsm-trae-page>*{width:100%;max-width:860px}
1678
+ .dsm-trae-page-head{display:flex;flex-direction:column;gap:6px}
1679
+ .dsm-trae-page-back{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2,#36373b);background:var(--dsw-alias-bg-layer-1,#17181b);color:var(--dsw-alias-label-secondary,#c7c7c7);border-radius:8px;padding:5px 12px;display:inline-flex;align-items:center;gap:6px;align-self:flex-start;font-size:13px;line-height:20px}
1680
+ .dsm-trae-page-back:hover{color:var(--dsw-alias-label-primary,#e6e6e6);border-color:var(--dsw-alias-label-dimmed,#777)}
1681
+ .dsm-trae-page-back-caret{display:inline-block;width:7px;height:7px;border-left:1.6px solid currentColor;border-bottom:1.6px solid currentColor;transform:rotate(45deg);margin-right:2px}
1682
+ .dsm-trae-page-icon{width:40px;height:40px;border-radius:9px;align-self:flex-start;margin-top:14px}
1683
+ .dsm-trae-page-title{margin:0;font-size:20px;font-weight:600;line-height:1.4}
1684
+ .dsm-trae-page-desc{margin:0;font-size:13px;line-height:1.5;color:var(--dsw-alias-label-tertiary,#999)}
1685
+ .dsm-trae-page-body .dsm-trae-usage{padding:20px 20px 8px;border:1px solid var(--dsw-alias-border-l2,#36373b);background:var(--dsw-alias-bg-layer-3,#202126);border-radius:12px}
1686
+ .dsm-trae-panel-icon{display:block;border-radius:4px;flex:none}
1537
1687
  `;
1538
1688
  /**
1539
1689
  * Composer-row readout styles, injected by the readout itself.
@@ -1570,9 +1720,15 @@ window.__ModuleLoader__.load({
1570
1720
  /* The panel is portaled to document.body and positioned from the trigger, so it
1571
1721
  MUST be fixed 鈥?otherwise the left/top it measures are ignored and it lands in
1572
1722
  document flow. All material comes from shell tokens so it follows the theme. */
1723
+ /* 264px, not the 248px this started as (m02477). The header holds a title plus
1724
+ two icon+word controls now, and measured in the browser the English pair
1725
+ ("Refresh" + "Settings") needed 233px of content where 248px of panel leaves
1726
+ 222 — the settings pill overflowed its own header by 11px. Widening the panel
1727
+ is the honest fix: the alternative is renaming the owner's words, and
1728
+ "Refresh"/「刷新」 are what they asked for. */
1573
1729
  .dsm-trae-composer-panel{
1574
1730
  position:fixed;z-index:1100;box-sizing:border-box;
1575
- width:min(248px,calc(100vw - 24px));padding:12px;
1731
+ width:min(264px,calc(100vw - 24px));padding:12px;
1576
1732
  border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.28));
1577
1733
  border-radius:var(--dsw-radius-lg,14px);
1578
1734
  background:var(--dsw-specific-menu,Canvas);
@@ -1581,19 +1737,128 @@ window.__ModuleLoader__.load({
1581
1737
  font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary,#444);
1582
1738
  }
1583
1739
  .dsm-trae-composer-panel-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}
1584
- .dsm-trae-composer-panel-title{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,CanvasText)}
1585
- /* Self-contained: the dsm-btn primitive lives in TRAE_CARD_CSS, which the
1586
- composer never loads, so the refresh button must style itself. */
1587
- .dsm-trae-composer-panel-refresh{
1588
- appearance:none;flex:none;margin-left:auto;padding:3px 10px;
1589
- border:1px solid var(--dsw-alias-border-l2,rgba(127,127,127,.34));
1590
- border-radius:7px;background:transparent;
1740
+ /* nowrap: the header is a title plus an 11px control in a ~250px popover, and
1741
+ without it the TITLE broke across two lines the moment the settings control
1742
+ joined the row — measured on the live Desktop host for the sibling plugin.
1743
+ The title is the one thing that must stay on one line; what follows it is
1744
+ short by construction. */
1745
+ .dsm-trae-composer-panel-title{flex:none;white-space:nowrap;font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,CanvasText)}
1746
+ /* The header's right-hand control: the link to this plugin's settings.
1747
+ A wrapper rather than an auto margin on the button, because the button is NOT
1748
+ always rendered (a host with no route renders none) and a wrapper that is
1749
+ always present keeps the header layout identical either way. */
1750
+ .dsm-trae-composer-panel-actions{flex:none;margin-left:auto;display:flex;align-items:center;gap:6px}
1751
+ /* THE POOL TEST BUTTON (m02091, restyled m02477), sitting to the settings
1752
+ pill's left.
1753
+
1754
+ A GHOST control: no border, no fill, an icon plus a word — and the FILL
1755
+ appears only on hover. That shape is the owner's call (the reference shows a
1756
+ bare circular arrow and 「刷新」 beside the tinted settings pill) and it also
1757
+ reads correctly: the two controls are different kinds of thing. Settings
1758
+ navigates, so it carries the brand fill and behaves like a pill; this one
1759
+ re-runs a batch in place, so it stays quiet until the pointer arrives.
1760
+
1761
+ What the earlier bordered pill got wrong: a 1px outline at rest made it look
1762
+ like the card's dsm-btn-outline (a save/cancel control), and two bordered or
1763
+ two tinted shapes in a 264px header read as one segmented control instead of
1764
+ two different actions. The icon is a circular arrow, not a gear — same family,
1765
+ different verb. */
1766
+ .dsm-trae-composer-panel-test{
1767
+ appearance:none;flex:none;
1768
+ display:inline-flex;align-items:center;gap:4px;
1769
+ padding:3px 6px;
1770
+ border:1px solid transparent;border-radius:999px;background:transparent;
1771
+ color:var(--dsw-alias-label-secondary,#444);
1772
+ font:inherit;font-size:11px;line-height:16px;
1773
+ white-space:nowrap;cursor:pointer;
1774
+ transition:background-color .12s ease,border-color .12s ease,color .12s ease;
1775
+ }
1776
+ .dsm-trae-composer-panel-test:hover:not(:disabled){
1777
+ background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.10));
1778
+ border-color:transparent;
1779
+ color:var(--dsw-alias-label-primary,CanvasText);
1780
+ }
1781
+ .dsm-trae-composer-panel-test:active{background:rgba(127,127,127,.16)}
1782
+ .dsm-trae-composer-panel-test:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
1783
+ .dsm-trae-composer-panel-test:disabled{opacity:.5;cursor:default}
1784
+ /* The arrow does not shrink when the label is a longer word ("Refresh" vs
1785
+ 「刷新」): a flex item defaults to shrinking, which squashes the 12px icon into
1786
+ an oval on a narrow popover — the same trap the gear beside it documents. */
1787
+ .dsm-trae-composer-panel-test-icon{flex:none;display:block}
1788
+ /* While the batch runs the arrow TURNS — 900ms a revolution. A disabled button
1789
+ whose icon sits still reads as broken, and this batch is serial: the slowest
1790
+ account decides when it ends, so "did my press land?" is a real question for
1791
+ as long as the panel is open. Fast enough to read as motion, slow enough not
1792
+ to look like a rendering fault. */
1793
+ .dsm-trae-composer-panel-test-icon-spin{animation:dsm-trae-panel-test-spin .9s linear infinite}
1794
+ @keyframes dsm-trae-panel-test-spin{to{transform:rotate(360deg)}}
1795
+ /* The label never wraps. Two lines in a 16px-tall control would push the header
1796
+ taller and unstick the table below it. */
1797
+ .dsm-trae-composer-panel-test-label{flex:none;white-space:nowrap}
1798
+ /* Reduced motion: the colour transition is decoration, and the hover state is
1799
+ still fully visible without it. */
1800
+ @media (prefers-reduced-motion:reduce){
1801
+ .dsm-trae-composer-panel-test{transition:none}
1802
+ /* The motion is dropped but the STATE SIGNAL must not be: the icon keeps a
1803
+ distinct dimmed look, and the label already switches to 「刷新中…」. Without
1804
+ this the spinning arrow would simply freeze at an arbitrary angle and read
1805
+ as a static icon — i.e. exactly the "did my press land?" silence that the
1806
+ rotation was added to fix. */
1807
+ .dsm-trae-composer-panel-test-icon-spin{animation:none;opacity:.5}
1808
+ }
1809
+ /* The batch's own outcome line: a NOTE (it succeeded but not everything
1810
+ answered), not an error. Sized like the footer so it reads as a report. */
1811
+ .dsm-trae-composer-panel-note{
1812
+ margin:8px 0 0;font-size:11px;line-height:16px;
1591
1813
  color:var(--dsw-alias-label-secondary,#444);
1592
- font:inherit;font-size:11px;line-height:16px;cursor:pointer;
1593
1814
  }
1594
- .dsm-trae-composer-panel-refresh:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed,rgba(127,127,127,.6));color:var(--dsw-alias-label-primary,CanvasText)}
1595
- .dsm-trae-composer-panel-refresh:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
1596
- .dsm-trae-composer-panel-refresh:disabled{opacity:.5;cursor:default}
1815
+ /* PORTED VERBATIM IN SHAPE from the sibling dsh-connect-workbuddy
1816
+ (ComposerCredits), so the two connectors' popovers agree: a quiet tinted
1817
+ PILL, not a bordered button and not an underlined link.
1818
+
1819
+ Why not the bordered button this started as: navigating away is not the
1820
+ header's primary action, and a bordered box beside a 13px title reads as a
1821
+ toolbar. Why not an underlined link: a one-word underline reads as body text
1822
+ that happens to be clickable — in a 13px header the eye does not register it
1823
+ as a control at all. The tinted fill plus the gear give it a SHAPE at rest,
1824
+ which is where the affordance is actually needed (the underline only appeared
1825
+ on hover, i.e. after the user already knew). The fill is the brand colour at
1826
+ 10%, so it reads as "secondary action in this product" rather than as a fourth
1827
+ grey chip beside the title.
1828
+
1829
+ Self-contained: the dsm-btn primitive lives in TRAE_CARD_CSS, which the
1830
+ composer never loads. */
1831
+ .dsm-trae-composer-panel-settings{
1832
+ appearance:none;flex:none;
1833
+ display:inline-flex;align-items:center;gap:4px;
1834
+ padding:3px 8px 3px 6px;
1835
+ border:1px solid transparent;border-radius:999px;
1836
+ background:rgba(86,134,254,.10);
1837
+ color:var(--dsw-alias-brand-primary,#5686fe);
1838
+ font:inherit;font-size:11px;line-height:16px;font-weight:500;
1839
+ cursor:pointer;
1840
+ transition:background-color .12s ease,border-color .12s ease,color .12s ease;
1841
+ }
1842
+ .dsm-trae-composer-panel-settings:hover:not(:disabled){
1843
+ background:rgba(86,134,254,.18);
1844
+ border-color:rgba(86,134,254,.34);
1845
+ color:var(--dsw-alias-brand-primary,#5686fe);
1846
+ }
1847
+ .dsm-trae-composer-panel-settings:active{background:rgba(86,134,254,.26)}
1848
+ .dsm-trae-composer-panel-settings:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
1849
+ .dsm-trae-composer-panel-settings:disabled{opacity:.5;cursor:default}
1850
+ /* The gear does not shrink when the label is a longer word ("Settings" vs
1851
+ "设置"): a flex item defaults to shrinking, which would squash the icon into
1852
+ an oval on a narrow popover. */
1853
+ .dsm-trae-composer-panel-settings-icon{flex:none;display:block}
1854
+ /* The label never wraps. Two lines in a 16px-tall pill would push the header
1855
+ taller and unstick the table below it. */
1856
+ .dsm-trae-composer-panel-settings-label{flex:none;white-space:nowrap}
1857
+ /* Reduced motion: the colour transition is decoration, and the hover state is
1858
+ still fully visible without it. */
1859
+ @media (prefers-reduced-motion:reduce){
1860
+ .dsm-trae-composer-panel-settings{transition:none}
1861
+ }
1597
1862
  /* The panel is a TABLE: one row per account in the region, one figure (the
1598
1863
  general balance) per row, and a row click switches accounts.
1599
1864
 
@@ -1758,6 +2023,94 @@ window.__ModuleLoader__.load({
1758
2023
  });
1759
2024
  }
1760
2025
  //#endregion
2026
+ //#region src/client/pool-test.ts
2027
+ /**
2028
+ * How long one `test` request may take before the caller gives up.
2029
+ *
2030
+ * A probe has its own upstream deadline, but a stalled connection (a restarting
2031
+ * Host, a dropped keep-alive) has none: without this the run button stays
2032
+ * disabled until the page is reloaded, which reads as "测速卡住了". Sized for a
2033
+ * full batch — a serial run with a gap between accounts — rather than for one
2034
+ * account, because aborting a healthy batch half way through would report the
2035
+ * members it never reached as failures.
2036
+ */
2037
+ const POOL_TEST_TIMEOUT_MS = 18e4;
2038
+ /**
2039
+ * Handle ONE NDJSON line of a `test` answer. A non-JSON line is skipped; a
2040
+ * `{reason:'pool-failed'}` line reports the failure and counts as "saw
2041
+ * something". Returns whether the line was usable, so the caller can tell "the
2042
+ * route answered nothing parseable" (a bug to surface) from "not finished yet".
2043
+ */
2044
+ function applyTestLine(line, t, onRow, onFailure) {
2045
+ if (line.trim() === "") return false;
2046
+ let parsed;
2047
+ try {
2048
+ parsed = JSON.parse(line);
2049
+ } catch {
2050
+ return false;
2051
+ }
2052
+ if (parsed.row !== void 0) {
2053
+ onRow(parsed.row);
2054
+ return true;
2055
+ }
2056
+ if (parsed.rows !== void 0) {
2057
+ for (const r of parsed.rows) onRow(r);
2058
+ return true;
2059
+ }
2060
+ if (parsed.reason === "pool-failed") {
2061
+ onFailure(parsed.error ?? t("row.poolLogGeneric"));
2062
+ return true;
2063
+ }
2064
+ return false;
2065
+ }
2066
+ /**
2067
+ * Read the answer line by line AS IT ARRIVES.
2068
+ *
2069
+ * The Host streams one line per account, but `response.text()` waits for the
2070
+ * whole body: with a slow probe the surface sat on "逐个处理账号中…" and looked
2071
+ * frozen (a single member could take half a minute). Reading the body stream
2072
+ * makes each finished account appear immediately, and an empty/failed stream
2073
+ * still ends the loop instead of hanging the busy state forever.
2074
+ */
2075
+ async function* testLinesOf(response) {
2076
+ const body = response.body;
2077
+ if (body === null) {
2078
+ for (const line of (await response.text()).split(/\r?\n/)) yield line;
2079
+ return;
2080
+ }
2081
+ const reader = body.getReader();
2082
+ const decoder = new TextDecoder();
2083
+ let buffer = "";
2084
+ for (;;) {
2085
+ const { done, value } = await reader.read();
2086
+ if (done) break;
2087
+ buffer += decoder.decode(value, { stream: true });
2088
+ let cut = buffer.indexOf("\n");
2089
+ while (cut >= 0) {
2090
+ yield buffer.slice(0, cut);
2091
+ buffer = buffer.slice(cut + 1);
2092
+ cut = buffer.indexOf("\n");
2093
+ }
2094
+ }
2095
+ buffer += decoder.decode();
2096
+ if (buffer.trim() !== "") yield buffer;
2097
+ }
2098
+ /** A human label for every value of {@link TraeWebPoolOutcome}. */
2099
+ function outcomeLabel(outcome, t) {
2100
+ switch (outcome) {
2101
+ case "ok": return t("row.poolProbeOk");
2102
+ case "rate-limited": return t("row.poolProbeRateLimited");
2103
+ case "model-busy": return t("row.poolProbeModelBusy");
2104
+ case "out-of-credit": return t("row.poolProbeOutOfCredit");
2105
+ case "credential-rejected": return t("row.poolProbeCredentialRejected");
2106
+ case "policy-rejected": return t("row.poolProbePolicyRejected");
2107
+ case "plan-gated": return t("row.poolProbePlanGated");
2108
+ case "unavailable": return t("row.poolProbeUnavailable");
2109
+ case "not-found": return t("row.poolProbeNotFound");
2110
+ case "failed": return t("row.poolProbeFailed");
2111
+ }
2112
+ }
2113
+ //#endregion
1761
2114
  //#region src/client/AccountPool.tsx
1762
2115
  /**
1763
2116
  * The account-pool card UI for Trae, ported from the WorkBuddy sibling.
@@ -1828,21 +2181,6 @@ window.__ModuleLoader__.load({
1828
2181
  if (hours < 24) return t("row.poolAgoHour", { n: hours });
1829
2182
  return t("row.poolAgoDay", { n: Math.round(hours / 24) });
1830
2183
  }
1831
- /** A human label for every value of {@link TraeWebPoolOutcome}. */
1832
- function outcomeLabel(outcome, t) {
1833
- switch (outcome) {
1834
- case "ok": return t("row.poolProbeOk");
1835
- case "rate-limited": return t("row.poolProbeRateLimited");
1836
- case "model-busy": return t("row.poolProbeModelBusy");
1837
- case "out-of-credit": return t("row.poolProbeOutOfCredit");
1838
- case "credential-rejected": return t("row.poolProbeCredentialRejected");
1839
- case "policy-rejected": return t("row.poolProbePolicyRejected");
1840
- case "plan-gated": return t("row.poolProbePlanGated");
1841
- case "unavailable": return t("row.poolProbeUnavailable");
1842
- case "not-found": return t("row.poolProbeNotFound");
1843
- case "failed": return t("row.poolProbeFailed");
1844
- }
1845
- }
1846
2184
  /** A human label for every value of {@link TraeWebPoolExclusion}. */
1847
2185
  function exclusionLabel(exclusion, t) {
1848
2186
  switch (exclusion) {
@@ -1893,75 +2231,7 @@ window.__ModuleLoader__.load({
1893
2231
  }
1894
2232
  return t("row.poolErrGeneric", { status: String(status) });
1895
2233
  }
1896
- /**
1897
- * Handle ONE NDJSON line of a `test` answer. A non-JSON line is skipped; a
1898
- * `{reason:'pool-failed'}` line reports the failure and counts as "saw
1899
- * something". Returns whether the line was usable, so the caller can tell "the
1900
- * route answered nothing parseable" (a bug to surface) from "not finished yet".
1901
- */
1902
- function applyTestLine(line, t, onRow, onFailure) {
1903
- if (line.trim() === "") return false;
1904
- let parsed;
1905
- try {
1906
- parsed = JSON.parse(line);
1907
- } catch {
1908
- return false;
1909
- }
1910
- if (parsed.row !== void 0) {
1911
- onRow(parsed.row);
1912
- return true;
1913
- }
1914
- if (parsed.rows !== void 0) {
1915
- for (const r of parsed.rows) onRow(r);
1916
- return true;
1917
- }
1918
- if (parsed.reason === "pool-failed") {
1919
- onFailure(parsed.error ?? t("row.poolLogGeneric"));
1920
- return true;
1921
- }
1922
- return false;
1923
- }
1924
- /**
1925
- * Read the answer line by line AS IT ARRIVES.
1926
- *
1927
- * The Host streams one line per account, but `response.text()` waits for the
1928
- * whole body: with a slow probe the card sat on "逐个处理账号中…" and looked
1929
- * frozen (a single member could take half a minute). Reading the body stream
1930
- * makes each finished account appear immediately, and an empty/failed stream
1931
- * still ends the loop instead of hanging the busy state forever.
1932
- */
1933
- async function* testLinesOf(response) {
1934
- const body = response.body;
1935
- if (body === null) {
1936
- for (const line of (await response.text()).split(/\r?\n/)) yield line;
1937
- return;
1938
- }
1939
- const reader = body.getReader();
1940
- const decoder = new TextDecoder();
1941
- let buffer = "";
1942
- for (;;) {
1943
- const { done, value } = await reader.read();
1944
- if (done) break;
1945
- buffer += decoder.decode(value, { stream: true });
1946
- let cut = buffer.indexOf("\n");
1947
- while (cut >= 0) {
1948
- yield buffer.slice(0, cut);
1949
- buffer = buffer.slice(cut + 1);
1950
- cut = buffer.indexOf("\n");
1951
- }
1952
- }
1953
- buffer += decoder.decode();
1954
- if (buffer.trim() !== "") yield buffer;
1955
- }
1956
- /**
1957
- * How long one `test` request may take before the card gives up.
1958
- *
1959
- * A probe has its own upstream deadline, but a stalled connection (a restarting
1960
- * Host, a dropped keep-alive) has none: without this the run button stays
1961
- * disabled until the page is reloaded, which reads as "测速卡住了". Sized for a
1962
- * batch: a few seconds per account plus room for the slowest upstream.
1963
- */
1964
- const TEST_TIMEOUT_MS = 18e4;
2234
+ /** Whether one row is a saved member (dictates the in-pool tag / ghost badge). */
1965
2235
  function AccountPool({ panel, pool, models, t, settingsScope, onChanged, onRefresh, tools, topActions, details, claims, onClaim, deviceClaimed = false, onBind, bindingBusy }) {
1966
2236
  const [settingsTick, setSettingsTick] = (0, react.useState)(0);
1967
2237
  const saved = (0, react.useMemo)(() => {
@@ -2134,7 +2404,7 @@ window.__ModuleLoader__.load({
2134
2404
  const controller = new AbortController();
2135
2405
  const timer = setTimeout(() => {
2136
2406
  controller.abort();
2137
- }, TEST_TIMEOUT_MS);
2407
+ }, POOL_TEST_TIMEOUT_MS);
2138
2408
  try {
2139
2409
  const response = await fetch(withTraePoolAction(panel, "test"), {
2140
2410
  method: "POST",
@@ -2181,7 +2451,7 @@ window.__ModuleLoader__.load({
2181
2451
  const controller = new AbortController();
2182
2452
  const timer = setTimeout(() => {
2183
2453
  controller.abort();
2184
- }, TEST_TIMEOUT_MS);
2454
+ }, POOL_TEST_TIMEOUT_MS);
2185
2455
  try {
2186
2456
  const response = await fetch(withTraePoolAccount(panel, "test", account.accountId), {
2187
2457
  method: "POST",
@@ -3635,7 +3905,7 @@ window.__ModuleLoader__.load({
3635
3905
  * is precisely why this section has no 「保存」 of its own — the model catalog,
3636
3906
  * which does, lives in its own section.
3637
3907
  */
3638
- function TraeAccountPanel({ t, settingsScope, panel, status, statusLabel, credentialRegionIsAi, regionOn, showPointsInMainUi, onToggleShowPoints, creditPosition, onCreditPositionChange, savingPreferences, creditsLabelId, creditsHintId, rows, busy, rescanNote, onRescan, switchingAccount, onSwitchAccount, onClaim, onTransferChanged, poolDetails, poolClaims, models, deviceClaimed, onPoolChanged, onPoolRefresh, creditsUnavailable, creditsExpiry, alternatives, onSwitchToAlternative }) {
3908
+ function TraeAccountPanel({ t, settingsScope, panel, status, statusLabel, credentialRegionIsAi, regionOn, showPointsInMainUi, onToggleShowPoints, creditPosition, onCreditPositionChange, sidebarPanelOn, sidebarPanelBusy, onToggleSidebarPanel, savingPreferences, creditsLabelId, creditsHintId, rows, busy, rescanNote, onRescan, switchingAccount, onSwitchAccount, onClaim, onTransferChanged, poolDetails, poolClaims, models, deviceClaimed, onPoolChanged, onPoolRefresh, creditsUnavailable, creditsExpiry, alternatives, onSwitchToAlternative }) {
3639
3909
  const writable = settingsScope?.getSnapshot().writable === true;
3640
3910
  const signedIn = status.status === "signed-in";
3641
3911
  const accounts = signedIn ? status.accounts : [];
@@ -3643,62 +3913,87 @@ window.__ModuleLoader__.load({
3643
3913
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3644
3914
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3645
3915
  className: "dsm-trae-prefs",
3646
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3647
- className: "dsm-trae-pref",
3648
- title: t(credentialRegionIsAi ? "row.showPointsInMainUiHintAi" : "row.showPointsInMainUiHint"),
3649
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3650
- type: "checkbox",
3651
- className: "dsm-trae-pref-check",
3652
- checked: showPointsInMainUi,
3653
- disabled: !writable || savingPreferences,
3654
- "aria-describedby": creditsHintId,
3655
- onChange: (event) => {
3656
- onToggleShowPoints(event.currentTarget.checked);
3657
- }
3658
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3659
- className: "dsm-trae-pref-copy",
3660
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3661
- className: "dsm-trae-pref-label",
3662
- id: creditsLabelId,
3663
- children: t("row.showPointsInMainUi")
3664
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3665
- className: "dsm-trae-pref-hint",
3666
- id: creditsHintId,
3667
- children: t(credentialRegionIsAi ? "row.showPointsInMainUiHintAi" : "row.showPointsInMainUiHint")
3668
- })]
3669
- })]
3670
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3671
- className: "dsm-trae-pref dsm-trae-credit-position-pref",
3672
- title: t("row.creditPositionHint"),
3673
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3674
- className: "dsm-trae-pref-copy",
3675
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3676
- className: "dsm-trae-pref-lead",
3916
+ children: [
3917
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3918
+ className: "dsm-trae-pref",
3919
+ title: t(credentialRegionIsAi ? "row.showPointsInMainUiHintAi" : "row.showPointsInMainUiHint"),
3920
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3921
+ type: "checkbox",
3922
+ className: "dsm-trae-pref-check",
3923
+ checked: showPointsInMainUi,
3924
+ disabled: !writable || savingPreferences,
3925
+ "aria-describedby": creditsHintId,
3926
+ onChange: (event) => {
3927
+ onToggleShowPoints(event.currentTarget.checked);
3928
+ }
3929
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3930
+ className: "dsm-trae-pref-copy",
3677
3931
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3678
3932
  className: "dsm-trae-pref-label",
3679
- children: t("row.creditPositionLabel")
3680
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3681
- className: "dsm-trae-credit-position",
3682
- value: regionOn ? creditPosition : "composer",
3683
- disabled: !regionOn || !writable || savingPreferences,
3684
- "aria-label": t("row.creditPositionAria", { region: panel === "cn" ? t("row.tabCn") : panel === "enterprise" ? t("row.tabEnterprise") : t("row.tabAi") }),
3685
- onChange: (event) => {
3686
- onCreditPositionChange(event.currentTarget.value === "stats" ? "stats" : "composer");
3687
- },
3688
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3689
- value: "composer",
3690
- children: t("row.creditPositionComposer")
3691
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3692
- value: "stats",
3693
- children: t("row.creditPositionStats")
3933
+ id: creditsLabelId,
3934
+ children: t("row.showPointsInMainUi")
3935
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3936
+ className: "dsm-trae-pref-hint",
3937
+ id: creditsHintId,
3938
+ children: t(credentialRegionIsAi ? "row.showPointsInMainUiHintAi" : "row.showPointsInMainUiHint")
3939
+ })]
3940
+ })]
3941
+ }),
3942
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3943
+ className: "dsm-trae-pref dsm-trae-credit-position-pref",
3944
+ title: t("row.creditPositionHint"),
3945
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3946
+ className: "dsm-trae-pref-copy",
3947
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3948
+ className: "dsm-trae-pref-lead",
3949
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3950
+ className: "dsm-trae-pref-label",
3951
+ children: t("row.creditPositionLabel")
3952
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3953
+ className: "dsm-trae-credit-position",
3954
+ value: regionOn ? creditPosition : "composer",
3955
+ disabled: !regionOn || !writable || savingPreferences,
3956
+ "aria-label": t("row.creditPositionAria", { region: panel === "cn" ? t("row.tabCn") : panel === "enterprise" ? t("row.tabEnterprise") : t("row.tabAi") }),
3957
+ onChange: (event) => {
3958
+ onCreditPositionChange(event.currentTarget.value === "stats" ? "stats" : "composer");
3959
+ },
3960
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3961
+ value: "composer",
3962
+ children: t("row.creditPositionComposer")
3963
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3964
+ value: "stats",
3965
+ children: t("row.creditPositionStats")
3966
+ })]
3694
3967
  })]
3968
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3969
+ className: "dsm-trae-pref-hint",
3970
+ children: t("row.creditPositionHint")
3971
+ })]
3972
+ })
3973
+ }),
3974
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3975
+ className: "dsm-trae-pref",
3976
+ title: t("row.sidebarPanelHint"),
3977
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3978
+ type: "checkbox",
3979
+ className: "dsm-trae-pref-check",
3980
+ checked: sidebarPanelOn,
3981
+ disabled: sidebarPanelBusy || savingPreferences,
3982
+ onChange: (event) => {
3983
+ onToggleSidebarPanel(event.currentTarget.checked);
3984
+ }
3985
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3986
+ className: "dsm-trae-pref-copy",
3987
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3988
+ className: "dsm-trae-pref-label",
3989
+ children: t("row.sidebarPanelLabel")
3990
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3991
+ className: "dsm-trae-pref-hint",
3992
+ children: t("row.sidebarPanelHint")
3695
3993
  })]
3696
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3697
- className: "dsm-trae-pref-hint",
3698
- children: t("row.creditPositionHint")
3699
3994
  })]
3700
3995
  })
3701
- })]
3996
+ ]
3702
3997
  }),
3703
3998
  pool !== void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3704
3999
  className: "dsm-trae-accounts",
@@ -3959,6 +4254,105 @@ window.__ModuleLoader__.load({
3959
4254
  return verdict.tier === void 0 ? t(verdict.label) : verdict.tier;
3960
4255
  }
3961
4256
  //#endregion
4257
+ //#region src/client/panel-switch.ts
4258
+ /**
4259
+ * Whether the OPTIONAL sidebar panel is switched on, as the browser half sees
4260
+ * it, plus the tiny subscription the registration controller needs.
4261
+ *
4262
+ * Ported from the sibling `dsh-connect-workbuddy` plugin (its PR #188, off by
4263
+ * default since), because the same two facts decide the shape here:
4264
+ *
4265
+ * ## Why this is a module-level store and not component state
4266
+ *
4267
+ * The `sidebar.panellist` row is built from ENTRY METADATA, not from the
4268
+ * component: the host's sidebar maps the slot's entries into a row button whose
4269
+ * label comes from `options.label` and whose glyph is `renderSlot(...)`. A
4270
+ * component that returns `null` therefore does not hide the row — it renders a
4271
+ * BLANK, still-clickable nav button. That is why the panel cannot use the
4272
+ * `ComposerPointsGate` trick the credit readout uses (that seat renders a list
4273
+ * of components, so `null` means absent). The row has to be registered only
4274
+ * while the switch is on, and unregistered when it goes off.
4275
+ *
4276
+ * Registering conditionally in turn means something must OWN the re-register on
4277
+ * a settings change, and that something lives in the entry (`apply`), not in a
4278
+ * component. This module is the seam between the two: the card publishes a
4279
+ * value here after it has verified a write landed, and the entry subscribes to
4280
+ * register or dispose.
4281
+ *
4282
+ * ## Why the Host is the seed
4283
+ *
4284
+ * The value is DELIVERED by the Host on the status document
4285
+ * (`sidebarPanelEnabled`), for the reason every other switch in this plugin is
4286
+ * Host-carried: this namespace's browser mirror is documented as stale on the
4287
+ * affected 0.1.7 deployments. Reading only the settings scope at startup would
4288
+ * let a stale `false` hide a row the Host believes it is serving — with nothing
4289
+ * to re-evaluate it afterwards, the panel would then be gone for the session.
4290
+ *
4291
+ * Turning the panel OFF while the page is open needs no special handling here:
4292
+ * the host's layout service drops an `activePanelId` whose `main` key no longer
4293
+ * exists, so the centre column returns to the conversation by itself.
4294
+ */
4295
+ /** Listeners waiting for a change to the opt-in state. */
4296
+ const listeners = /* @__PURE__ */ new Set();
4297
+ /**
4298
+ * The current answer, defaulting to OFF.
4299
+ *
4300
+ * OFF is the whole point of this switch: an absent or unreadable value must
4301
+ * never turn the row on. See `Config.sidebarPanel` in `src/index.ts`.
4302
+ */
4303
+ let wanted = false;
4304
+ /** Whether the sidebar panel should currently be registered. */
4305
+ function sidebarPanelWanted() {
4306
+ return wanted;
4307
+ }
4308
+ /**
4309
+ * Set the opt-in state and notify the registration controller.
4310
+ *
4311
+ * Idempotent: the controller re-registers on every call, and a settings
4312
+ * subscription can legitimately report the same value twice. Callers that
4313
+ * cannot tell whether the value changed need not check first.
4314
+ *
4315
+ * @param next - the desired state.
4316
+ */
4317
+ function setSidebarPanelWanted(next) {
4318
+ if (wanted === next) return;
4319
+ wanted = next;
4320
+ for (const listener of [...listeners]) try {
4321
+ listener();
4322
+ } catch {}
4323
+ }
4324
+ /**
4325
+ * Observe changes to the opt-in state.
4326
+ *
4327
+ * @param listener - invoked after each change.
4328
+ * @returns the disposer removing this listener.
4329
+ */
4330
+ function subscribeSidebarPanelWanted(listener) {
4331
+ listeners.add(listener);
4332
+ return () => {
4333
+ listeners.delete(listener);
4334
+ };
4335
+ }
4336
+ /**
4337
+ * Read the opt-in switch out of a settings snapshot.
4338
+ *
4339
+ * Only an explicit `true` is ON, matching the schema's `default(false)` and
4340
+ * every other switch this plugin owns: the row claims a GLOBAL sidebar seat
4341
+ * shared by every installed bundle, so an absent or malformed value must read
4342
+ * as "did not ask for this", never as consent.
4343
+ *
4344
+ * The value is deep-unwrapped because DSH 0.1.7 hands volatile-marked fields
4345
+ * back as `{get(): T}` live references, which are truthy non-booleans.
4346
+ *
4347
+ * @param configured - the whole settings snapshot (or any object carrying the
4348
+ * field).
4349
+ * @returns whether the panel row should exist.
4350
+ */
4351
+ function sidebarPanelEnabledOf(configured) {
4352
+ const value = configured?.["sidebarPanel"];
4353
+ return (value !== null && typeof value === "object" && typeof value.get === "function" ? value.get() : value) === true;
4354
+ }
4355
+ //#endregion
3962
4356
  //#region src/client/TraeUsageCard.tsx
3963
4357
  /**
3964
4358
  * Trae usage card contributed to Harness Plugin configuration.
@@ -4141,9 +4535,17 @@ window.__ModuleLoader__.load({
4141
4535
  });
4142
4536
  }
4143
4537
  /** Render Trae sign-in state and the total usage summary as one expandable card. */
4144
- function TraeUsageCard({ t, settingsScope, view }) {
4538
+ function TraeUsageCard({ t, settingsScope, view, surface, backToConversation }) {
4145
4539
  if (t === void 0) throw new Error("Trae usage card requires its translation function");
4146
- const [open, setOpen] = (0, react.useState)(view === "page");
4540
+ /**
4541
+ * A PAGE surface (the optional sidebar row's `main` panel) has no card chrome
4542
+ * to collapse: it is always expanded, and the collapse button is not rendered,
4543
+ * so `setOpen` is simply unused there. Keeping ONE `open` value lets every
4544
+ * polling effect below stay untouched.
4545
+ */
4546
+ const pageSurface = surface === "page";
4547
+ const [cardOpen, setCardOpen] = (0, react.useState)(view === "page");
4548
+ const open = pageSurface || cardOpen;
4147
4549
  /** The region whose tab is on screen; each tab is its own provider stack. */
4148
4550
  const [activeRegion, setActiveRegion] = (0, react.useState)("cn");
4149
4551
  /**
@@ -4305,6 +4707,18 @@ window.__ModuleLoader__.load({
4305
4707
  * the control would flip and then quietly snap back on the next snapshot.
4306
4708
  */
4307
4709
  const [writeError, setWriteError] = (0, react.useState)(void 0);
4710
+ /**
4711
+ * The optional sidebar row's switch (see `Config.sidebarPanel`).
4712
+ *
4713
+ * `undefined` means "no local flip in flight": the box then renders the
4714
+ * committed value from the settings document, or — when that namespace is not
4715
+ * served yet — the Host's own answer on the usage document. Both are read here
4716
+ * rather than echoed from local state for the reason every other switch in
4717
+ * this card reads them: a write made elsewhere (the DSH settings form, another
4718
+ * window) must show up instead of being shadowed by a stale copy.
4719
+ */
4720
+ const [sidebarPanelEdit, setSidebarPanelEdit] = (0, react.useState)(void 0);
4721
+ const [sidebarPanelBusy, setSidebarPanelBusy] = (0, react.useState)(false);
4308
4722
  const mounted = (0, react.useRef)(true);
4309
4723
  (0, react.useEffect)(() => {
4310
4724
  mounted.current = true;
@@ -4380,15 +4794,55 @@ window.__ModuleLoader__.load({
4380
4794
  }
4381
4795
  };
4382
4796
  /**
4383
- * Flip ONE panel's 「允许模型自主生图」 switch (`drawImageEnabled`).
4797
+ * Flip the OPTIONAL sidebar row on or off (`Config.sidebarPanel`).
4384
4798
  *
4385
- * Not a top-level preference like the two above: the flag lives INSIDE
4386
- * `regions.<panel>`, because it is per-panel consent and the Host's tool
4387
- * resolver reads it per panel. `nextRegionDrawImage` carries the rest of that
4388
- * slot through, so flipping this cannot drop the model directory, budgets,
4389
- * pool or credit seat — the failure #44 fixed for the pool.
4799
+ * Two things happen on a verified write, and BOTH are required:
4390
4800
  *
4391
- * Serialized by `savingSettings` with the sibling writers: they all write the
4801
+ * 1. the settings document is updated through `writeSettingsField`, which
4802
+ * read-back checks — a switch that only *looks* on while the Host
4803
+ * disagrees would leave a nav row that vanishes on the next reload;
4804
+ * 2. {@link setSidebarPanelWanted} publishes the value, which is what makes
4805
+ * the registration appear or disappear WITHOUT a reload. The row is built
4806
+ * from slot ENTRY METADATA, so it cannot be hidden from inside the
4807
+ * component — the entry has to register or dispose, and it subscribes to
4808
+ * that store.
4809
+ *
4810
+ * Serialized by its own `sidebarPanelBusy` rather than `savingSettings`: this
4811
+ * write touches one scalar, and making it share the credit switch's veil would
4812
+ * grey the whole card out for a nav-row toggle.
4813
+ *
4814
+ * A NOT-writable scope still flips the store. That is deliberate: the row is a
4815
+ * client-side registration, and a read-only host (no settings form bound yet)
4816
+ * would otherwise leave the user no way to open the page at all. The box reads
4817
+ * committed-or-Host value, so the next snapshot is what the user sees.
4818
+ */
4819
+ const writeSidebarPanel = async (value) => {
4820
+ if (sidebarPanelBusy) return;
4821
+ setSidebarPanelBusy(true);
4822
+ setWriteError(void 0);
4823
+ setSidebarPanelEdit(value);
4824
+ try {
4825
+ if (settingsScope !== void 0 && settingsScope.getSnapshot().writable === true) await writeSettingsField(settingsScope, "sidebarPanel", value, (readBack) => sidebarPanelEnabledOf(readBack) === value);
4826
+ setSidebarPanelWanted(value);
4827
+ } catch (error) {
4828
+ if (mounted.current) setWriteError(error instanceof Error ? error.message : t("row.requestFailed"));
4829
+ } finally {
4830
+ if (mounted.current) {
4831
+ setSidebarPanelEdit(void 0);
4832
+ setSidebarPanelBusy(false);
4833
+ }
4834
+ }
4835
+ };
4836
+ /**
4837
+ * Flip ONE panel's 「允许模型自主生图」 switch (`drawImageEnabled`).
4838
+ *
4839
+ * Not a top-level preference like the two above: the flag lives INSIDE
4840
+ * `regions.<panel>`, because it is per-panel consent and the Host's tool
4841
+ * resolver reads it per panel. `nextRegionDrawImage` carries the rest of that
4842
+ * slot through, so flipping this cannot drop the model directory, budgets,
4843
+ * pool or credit seat — the failure #44 fixed for the pool.
4844
+ *
4845
+ * Serialized by `savingSettings` with the sibling writers: they all write the
4392
4846
  * same settings document. The read-back re-reads the document, so a write that
4393
4847
  * silently reverted cannot leave the box claiming a grant the Host never got —
4394
4848
  * the one failure that matters here, because a box that lies about consent
@@ -5011,6 +5465,16 @@ window.__ModuleLoader__.load({
5011
5465
  const slotContextBudgets = typeof regionSlot?.contextBudgets === "object" && regionSlot.contextBudgets !== null ? regionSlot.contextBudgets : void 0;
5012
5466
  const legacyContextBudgets = legacySlot && typeof settingsValue === "object" && settingsValue !== null && typeof settingsValue.contextBudgets === "object" && settingsValue.contextBudgets !== null ? settingsValue.contextBudgets : void 0;
5013
5467
  const savedContextBudgets = slotContextBudgets ?? legacyContextBudgets ?? {};
5468
+ /**
5469
+ * The saved thinking-level choice per model for this panel.
5470
+ *
5471
+ * A missing key is a real statement — "follow this model's own default" — so
5472
+ * it is preserved everywhere below as an absent entry rather than being
5473
+ * filled in with the level the model would run at anyway. Writing that level
5474
+ * here would freeze today's upstream default into the config, and the model
5475
+ * would then ignore upstream when it changed its mind.
5476
+ */
5477
+ const savedThinkingEfforts = typeof regionSlot?.thinkingEfforts === "object" && regionSlot.thinkingEfforts !== null ? Object.fromEntries(Object.entries(regionSlot.thinkingEfforts).filter((entry) => typeof entry[1] === "string" && entry[1] !== "")) : {};
5014
5478
  const slotImageIds = Array.isArray(regionSlot?.imageModelIds) ? regionSlot.imageModelIds.filter((id) => typeof id === "string") : void 0;
5015
5479
  const legacyImageIds = legacySlot && typeof settingsValue === "object" && settingsValue !== null && Array.isArray(settingsValue.imageModelIds) ? settingsValue.imageModelIds.filter((id) => typeof id === "string") : [];
5016
5480
  const savedImageIds = new Set(slotImageIds ?? legacyImageIds);
@@ -5132,6 +5596,7 @@ window.__ModuleLoader__.load({
5132
5596
  * the single surface.
5133
5597
  */
5134
5598
  const activeContextBudgets = draft?.contextBudgets ?? savedContextBudgets;
5599
+ const activeThinkingEfforts = draft?.thinkingEfforts ?? savedThinkingEfforts;
5135
5600
  const dirty = draft !== void 0;
5136
5601
  /** Apply an edit to the ACTIVE tab's draft, seeding it from the current view. */
5137
5602
  const editDraft = (edit) => {
@@ -5141,7 +5606,8 @@ window.__ModuleLoader__.load({
5141
5606
  models: [...visibleModels],
5142
5607
  enabledIds: new Set(activeEnabledIds),
5143
5608
  imageIds: new Set(activeImageIds),
5144
- contextBudgets: { ...activeContextBudgets }
5609
+ contextBudgets: { ...activeContextBudgets },
5610
+ thinkingEfforts: { ...activeThinkingEfforts }
5145
5611
  })
5146
5612
  }));
5147
5613
  };
@@ -5177,6 +5643,25 @@ window.__ModuleLoader__.load({
5177
5643
  });
5178
5644
  };
5179
5645
  /**
5646
+ * Record one model's thinking level; an empty value means "follow the
5647
+ * upstream default" and therefore DELETES the key.
5648
+ *
5649
+ * Deletion is the point. Keeping the level that happens to equal today's
5650
+ * default would look identical on screen while quietly pinning it — the model
5651
+ * would stop following upstream the moment Trae changed its own default.
5652
+ */
5653
+ const setThinkingEffort = (modelId, effort) => {
5654
+ editDraft((current) => {
5655
+ const next = { ...current.thinkingEfforts };
5656
+ if (effort === "") delete next[modelId];
5657
+ else next[modelId] = effort;
5658
+ return {
5659
+ ...current,
5660
+ thinkingEfforts: next
5661
+ };
5662
+ });
5663
+ };
5664
+ /**
5180
5665
  * Bulk context-budget switch (issue #42 side request). The per-model radios
5181
5666
  * stay exactly as they are; these two act on the whole region draft at once.
5182
5667
  * The reporter counted 18 CN models whose Max window differs from the
@@ -5252,11 +5737,15 @@ window.__ModuleLoader__.load({
5252
5737
  })),
5253
5738
  enabledModelIds: [...activeEnabledIds],
5254
5739
  imageModelIds: [...activeImageIds].filter((id) => activeEnabledIds.has(id)),
5255
- contextBudgets: activeContextBudgets
5740
+ contextBudgets: activeContextBudgets,
5741
+ thinkingEfforts: activeThinkingEfforts
5256
5742
  };
5257
5743
  await writeSettingsField(settingsScope, "regions", nextRegionDirectory(configuredRegions, activeRegion, nextSlot), (readBack) => {
5258
- const ids = regionSlotsOf(readBack)[activeRegion]?.enabledModelIds;
5259
- return Array.isArray(ids) && ids.length === nextSlot.enabledModelIds.length && nextSlot.enabledModelIds.every((id) => ids.includes(id));
5744
+ const slot = regionSlotsOf(readBack)[activeRegion];
5745
+ const ids = slot?.enabledModelIds;
5746
+ if (!Array.isArray(ids) || ids.length !== nextSlot.enabledModelIds.length || !nextSlot.enabledModelIds.every((id) => ids.includes(id))) return false;
5747
+ const savedLevels = slot?.thinkingEfforts;
5748
+ return (typeof savedLevels === "object" && savedLevels !== null ? Object.keys(savedLevels).filter((id) => activeThinkingEfforts[id] !== void 0) : []).length === Object.keys(activeThinkingEfforts).length;
5260
5749
  });
5261
5750
  discardModels();
5262
5751
  await refreshUsage(activeRegion);
@@ -5266,6 +5755,49 @@ window.__ModuleLoader__.load({
5266
5755
  if (mounted.current) setSaving(false);
5267
5756
  }
5268
5757
  };
5758
+ /**
5759
+ * The thinking-level picker for one model row, or nothing when Trae published
5760
+ * no tier list for it.
5761
+ *
5762
+ * The first option is deliberately the interesting one: it names the level
5763
+ * this model will actually run at with no choice made. A picker that only
5764
+ * listed tiers would leave "nothing chosen" unlabelled, so the reader cannot
5765
+ * tell what they are overriding — and the previous UI could not state it at
5766
+ * all, which is why DSH's own picker said `Default`.
5767
+ *
5768
+ * Options are the model's OWN tiers, unlocalised: `low / high / xhigh` need no
5769
+ * translation, and a shared ladder across models would list levels that cannot
5770
+ * be selected.
5771
+ */
5772
+ const effortPicker = (model) => {
5773
+ const efforts = model.reasoning?.supported ?? [];
5774
+ if (efforts.length === 0) return null;
5775
+ const chosen = activeThinkingEfforts[model.id] ?? "";
5776
+ const upstream = model.reasoning?.defaultEffort;
5777
+ const effective = defaultEffortFor({
5778
+ id: model.id,
5779
+ supported: efforts,
5780
+ defaultEffort: upstream
5781
+ }, activeThinkingEfforts);
5782
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5783
+ className: "dsm-trae-model-effort",
5784
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelReasoningLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
5785
+ className: "dsm-trae-model-effort-select",
5786
+ disabled: settingsScope?.getSnapshot().writable !== true || saving,
5787
+ value: chosen,
5788
+ onChange: (event) => {
5789
+ setThinkingEffort(model.id, event.target.value);
5790
+ },
5791
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5792
+ value: "",
5793
+ children: effective === void 0 ? t("row.modelReasoningFollowUpstream") : t("row.modelReasoningFollowUpstreamNamed", { effort: effective })
5794
+ }), efforts.map((effort) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5795
+ value: effort,
5796
+ children: effort
5797
+ }, effort))]
5798
+ })] })
5799
+ });
5800
+ };
5269
5801
  const title = t("row.title");
5270
5802
  /**
5271
5803
  * Row labels for the account table.
@@ -5431,787 +5963,862 @@ window.__ModuleLoader__.load({
5431
5963
  }];
5432
5964
  }));
5433
5965
  const label = loading ? t("row.accountsLoading") : status.status === "signed-in" ? t("row.signedIn", { accountName: status.accountName }) : status.status === "error" ? t("row.requestFailed") : t("row.signedOut");
5434
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
5435
- className: `dsm-plugin-card${open ? " dsm-plugin-card-open" : ""}`,
5966
+ /**
5967
+ * Whether the sidebar row's box is ticked.
5968
+ *
5969
+ * Precedence: an in-flight flip, then the committed settings value when this
5970
+ * namespace is actually served, then the HOST's answer on the usage document.
5971
+ * The Host is the fallback rather than the first source because the card can
5972
+ * be bound before the mirror settles; it is consulted at all because that
5973
+ * mirror is documented as stale on the affected 0.1.7 deployments, so a stale
5974
+ * `false` alone would make a served row look switched off.
5975
+ */
5976
+ const sidebarPanelOn = sidebarPanelEdit ?? (settingsScope === void 0 ? void 0 : sidebarPanelEnabledOf(unwrapVolatileDeep(settingsScope.getSnapshot().value))) ?? loaded?.sidebarPanelEnabled === true;
5977
+ /**
5978
+ * The body BOTH surfaces draw: the region tabs, the section tabs and every
5979
+ * section below them.
5980
+ *
5981
+ * Kept as one value because a page duplicate would be a copy that drifts —
5982
+ * the SURFACE changes the chrome around this tree, nothing inside it.
5983
+ */
5984
+ const cardBody = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5985
+ className: "dsm-trae-usage",
5436
5986
  children: [
5437
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5438
- type: "button",
5439
- className: "dsm-plugin-card-header",
5440
- "aria-expanded": open,
5441
- "aria-label": `${t(open ? "row.collapse" : "row.expand")}: ${title}`,
5442
- onClick: () => {
5443
- setOpen(!open);
5444
- },
5987
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5988
+ className: "dsm-trae-tabs",
5989
+ role: "tablist",
5990
+ "aria-label": title,
5991
+ children: TRAE_PANELS.map((item) => {
5992
+ const regionStatus = statusByRegion[item];
5993
+ const on = regionOn(item);
5994
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5995
+ className: "dsm-trae-tab-cell",
5996
+ role: "presentation",
5997
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5998
+ type: "button",
5999
+ role: "tab",
6000
+ "aria-selected": item === activeRegion,
6001
+ className: `dsm-trae-tab${item === activeRegion ? " dsm-trae-tab-active" : ""}${on ? "" : " dsm-trae-tab-off"}`,
6002
+ onClick: () => {
6003
+ setActiveRegion(item);
6004
+ },
6005
+ children: [regionStatus === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6006
+ "aria-hidden": "true",
6007
+ className: "dsm-trae-tab-dot",
6008
+ style: dotStyle(regionStatus.status)
6009
+ }), item === "cn" ? t("row.tabCn") : item === "enterprise" ? t("row.tabEnterprise") : t("row.tabAi")]
6010
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
6011
+ className: "dsm-trae-switch dsm-trae-tab-switch",
6012
+ title: t("row.tabSwitchHint"),
6013
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6014
+ type: "checkbox",
6015
+ checked: on,
6016
+ disabled: settingsScope?.getSnapshot().writable !== true || togglingRegion !== void 0,
6017
+ "aria-label": t("row.tabSwitchAria", { region: item === "cn" ? t("row.tabCn") : item === "enterprise" ? t("row.tabEnterprise") : t("row.tabAi") }),
6018
+ onChange: (event) => {
6019
+ toggleRegion(item, event.currentTarget.checked);
6020
+ }
6021
+ })
6022
+ })]
6023
+ }, item);
6024
+ })
6025
+ }),
6026
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6027
+ className: "dsm-trae-tab-hint",
6028
+ children: t("row.tabHint")
6029
+ }),
6030
+ writeError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6031
+ className: "dsm-trae-usage-error",
6032
+ role: "alert",
6033
+ children: t("row.settingsWriteFailed", { message: writeError })
6034
+ }),
6035
+ regionOn(activeRegion) ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6036
+ className: "dsm-trae-tab-off-notice",
6037
+ role: "status",
6038
+ children: t("row.tabOffNotice")
6039
+ }),
6040
+ signedIn ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6041
+ className: "dsm-trae-panels",
6042
+ role: "tablist",
6043
+ "aria-label": t("row.panelAria"),
5445
6044
  children: [
5446
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
5447
- className: "dsm-plugin-card-icon",
5448
- src: TRAE_PLUGIN_ICON,
5449
- alt: ""
6045
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6046
+ type: "button",
6047
+ role: "tab",
6048
+ id: "dsm-trae-tab-accounts",
6049
+ "aria-controls": "dsm-trae-panel-accounts",
6050
+ "aria-selected": panel === "accounts",
6051
+ className: `dsm-trae-panel-tab${panel === "accounts" ? " dsm-trae-panel-tab-active" : ""}`,
6052
+ onClick: () => {
6053
+ setPanel("accounts");
6054
+ },
6055
+ children: t("row.panelAccounts")
5450
6056
  }),
5451
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5452
- className: "dsm-plugin-card-head",
5453
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5454
- className: "dsm-plugin-card-title",
5455
- children: title
5456
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5457
- className: "dsm-plugin-card-description",
5458
- children: t("row.desc")
5459
- })]
6057
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6058
+ type: "button",
6059
+ role: "tab",
6060
+ id: "dsm-trae-tab-models",
6061
+ "aria-controls": "dsm-trae-panel-models",
6062
+ "aria-selected": panel === "models",
6063
+ className: `dsm-trae-panel-tab${panel === "models" ? " dsm-trae-panel-tab-active" : ""}`,
6064
+ onClick: () => {
6065
+ setPanel("models");
6066
+ },
6067
+ children: t("row.panelModels")
5460
6068
  }),
5461
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5462
- "aria-hidden": "true",
5463
- className: `dsm-plugin-card-chevron${open ? " dsm-plugin-card-chevron-open" : ""}`
6069
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6070
+ type: "button",
6071
+ role: "tab",
6072
+ id: "dsm-trae-tab-diag",
6073
+ "aria-controls": "dsm-trae-panel-diag",
6074
+ "aria-selected": panel === "diag",
6075
+ className: `dsm-trae-panel-tab${panel === "diag" ? " dsm-trae-panel-tab-active" : ""}`,
6076
+ onClick: () => {
6077
+ setPanel("diag");
6078
+ },
6079
+ children: t("row.panelDiag")
5464
6080
  })
5465
6081
  ]
6082
+ }) : null,
6083
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6084
+ className: "dsm-trae-panel",
6085
+ role: "tabpanel",
6086
+ id: "dsm-trae-panel-accounts",
6087
+ "aria-labelledby": "dsm-trae-tab-accounts",
6088
+ hidden: signedIn && panel !== "accounts",
6089
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TraeAccountPanel, {
6090
+ t,
6091
+ settingsScope,
6092
+ panel: activeRegion,
6093
+ status,
6094
+ statusLabel: label,
6095
+ credentialRegionIsAi,
6096
+ regionOn: activeRegionOn,
6097
+ showPointsInMainUi: settingsEdits.showPointsInMainUi,
6098
+ onToggleShowPoints: (value) => {
6099
+ writePreference("showPointsInMainUi", value);
6100
+ },
6101
+ creditPosition: creditPositionOn(activeRegion),
6102
+ onCreditPositionChange: (position) => {
6103
+ changeCreditPosition(activeRegion, position);
6104
+ },
6105
+ sidebarPanelOn,
6106
+ sidebarPanelBusy,
6107
+ onToggleSidebarPanel: (value) => {
6108
+ writeSidebarPanel(value);
6109
+ },
6110
+ savingPreferences: savingSettings,
6111
+ creditsLabelId,
6112
+ creditsHintId,
6113
+ rows: accountRows,
6114
+ busy,
6115
+ rescanNote,
6116
+ onRescan: () => {
6117
+ rescanAccounts();
6118
+ },
6119
+ switchingAccount,
6120
+ onSwitchAccount: (accountId) => {
6121
+ switchAccount(accountId);
6122
+ },
6123
+ onClaim: (accountId) => {
6124
+ claimFor(accountId);
6125
+ },
6126
+ onTransferChanged: async () => {
6127
+ const fresh = await refreshUsage(activeRegion);
6128
+ if (fresh !== void 0) setStatusByRegion((current) => ({
6129
+ ...current,
6130
+ [activeRegion]: fresh
6131
+ }));
6132
+ },
6133
+ poolDetails,
6134
+ poolClaims: status.status === "signed-in" && status.pool?.checkinSupported === true ? Object.fromEntries(status.accounts.map((account) => [account.id, claimViewOf(account.id, accountStatusByRegion[activeRegion][account.id])])) : void 0,
6135
+ models: visibleModels,
6136
+ deviceClaimed: deviceClaimedToday,
6137
+ onPoolChanged: async () => {
6138
+ const fresh = await refreshUsage(activeRegion);
6139
+ if (fresh !== void 0) setStatusByRegion((current) => ({
6140
+ ...current,
6141
+ [activeRegion]: fresh
6142
+ }));
6143
+ },
6144
+ onPoolRefresh: async () => {
6145
+ const fresh = await refreshUsage(activeRegion);
6146
+ if (fresh !== void 0) setStatusByRegion((current) => ({
6147
+ ...current,
6148
+ [activeRegion]: fresh
6149
+ }));
6150
+ },
6151
+ creditsUnavailable: status.status === "signed-in" ? status.creditsUnavailable : void 0,
6152
+ creditsExpiry: status.status === "signed-in" ? status.credits?.expiry : void 0,
6153
+ alternatives: status.status === "signed-in" ? status.alternatives : void 0,
6154
+ onSwitchToAlternative: (accountId) => {
6155
+ switchAccount(accountId);
6156
+ }
6157
+ })
5466
6158
  }),
5467
6159
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5468
- className: "dsm-plugin-card-body",
5469
- hidden: !open,
5470
- children: open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5471
- className: "dsm-trae-usage",
6160
+ className: "dsm-trae-panel",
6161
+ role: "tabpanel",
6162
+ id: "dsm-trae-panel-models",
6163
+ "aria-labelledby": "dsm-trae-tab-models",
6164
+ hidden: !signedIn || panel !== "models",
6165
+ children: signedIn ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
6166
+ className: "dsm-trae-models",
6167
+ "aria-label": t("row.modelsTitle"),
5472
6168
  children: [
5473
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5474
- className: "dsm-trae-tabs",
5475
- role: "tablist",
5476
- "aria-label": title,
5477
- children: TRAE_PANELS.map((item) => {
5478
- const regionStatus = statusByRegion[item];
5479
- const on = regionOn(item);
5480
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5481
- className: "dsm-trae-tab-cell",
5482
- role: "presentation",
5483
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6169
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6170
+ className: "dsm-trae-models-head",
6171
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
6172
+ className: "dsm-trae-models-title",
6173
+ children: t("row.modelsTitle")
6174
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6175
+ className: "dsm-trae-models-summary",
6176
+ children: t("row.modelsSummary", { count: activeEnabledIds.size })
6177
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6178
+ className: "dsm-trae-models-actions",
6179
+ children: [
6180
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6181
+ type: "button",
6182
+ className: "dsm-btn dsm-btn-outline",
6183
+ disabled: busy,
6184
+ onClick: () => {
6185
+ refreshModels();
6186
+ },
6187
+ children: busy ? t("row.modelsRefreshing") : t("row.modelsRefresh")
6188
+ }),
6189
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5484
6190
  type: "button",
5485
- role: "tab",
5486
- "aria-selected": item === activeRegion,
5487
- className: `dsm-trae-tab${item === activeRegion ? " dsm-trae-tab-active" : ""}${on ? "" : " dsm-trae-tab-off"}`,
6191
+ className: "dsm-btn dsm-btn-outline",
6192
+ disabled: busy || testing !== void 0 || activeEnabledIds.size === 0,
6193
+ title: t("row.modelsTestHint"),
5488
6194
  onClick: () => {
5489
- setActiveRegion(item);
6195
+ testEnabledModels();
5490
6196
  },
5491
- children: [regionStatus === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5492
- "aria-hidden": "true",
5493
- className: "dsm-trae-tab-dot",
5494
- style: dotStyle(regionStatus.status)
5495
- }), item === "cn" ? t("row.tabCn") : item === "enterprise" ? t("row.tabEnterprise") : t("row.tabAi")]
5496
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
5497
- className: "dsm-trae-switch dsm-trae-tab-switch",
5498
- title: t("row.tabSwitchHint"),
5499
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5500
- type: "checkbox",
5501
- checked: on,
5502
- disabled: settingsScope?.getSnapshot().writable !== true || togglingRegion !== void 0,
5503
- "aria-label": t("row.tabSwitchAria", { region: item === "cn" ? t("row.tabCn") : item === "enterprise" ? t("row.tabEnterprise") : t("row.tabAi") }),
5504
- onChange: (event) => {
5505
- toggleRegion(item, event.currentTarget.checked);
5506
- }
6197
+ children: testing === void 0 ? t("row.modelsTestEnabled") : t("row.modelsTesting", {
6198
+ done: testing.done,
6199
+ total: testing.total
5507
6200
  })
5508
- })]
5509
- }, item);
5510
- })
6201
+ }),
6202
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6203
+ type: "button",
6204
+ className: "dsm-btn dsm-btn-outline",
6205
+ disabled: busy || testing !== void 0 || visibleModels.length === 0,
6206
+ title: t("row.modelsTestAllHint"),
6207
+ onClick: () => {
6208
+ testAllModels();
6209
+ },
6210
+ children: testing === void 0 ? t("row.modelsTestAll") : t("row.modelsTesting", {
6211
+ done: testing.done,
6212
+ total: testing.total
6213
+ })
6214
+ })
6215
+ ]
6216
+ })]
5511
6217
  }),
5512
6218
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5513
- className: "dsm-trae-tab-hint",
5514
- children: t("row.tabHint")
6219
+ className: "dsm-trae-models-hint",
6220
+ children: t("row.modelsTestHint")
6221
+ }),
6222
+ testSummary === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6223
+ className: "dsm-trae-models-summary",
6224
+ children: testSummary
5515
6225
  }),
5516
- writeError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6226
+ testError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5517
6227
  className: "dsm-trae-usage-error",
5518
6228
  role: "alert",
5519
- children: t("row.settingsWriteFailed", { message: writeError })
6229
+ children: testError
5520
6230
  }),
5521
- regionOn(activeRegion) ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5522
- className: "dsm-trae-tab-off-notice",
5523
- role: "status",
5524
- children: t("row.tabOffNotice")
6231
+ orderedModels.length > commonModelCount ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6232
+ className: "dsm-trae-models-view",
6233
+ role: "group",
6234
+ "aria-label": t("row.modelsViewLabel"),
6235
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6236
+ type: "button",
6237
+ className: "dsm-trae-models-view-opt",
6238
+ "aria-pressed": modelView === "common",
6239
+ title: t("row.modelsViewCommonHint"),
6240
+ onClick: () => {
6241
+ setModelView("common");
6242
+ },
6243
+ children: t("row.modelsViewCommon", { count: commonModelCount })
6244
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6245
+ type: "button",
6246
+ className: "dsm-trae-models-view-opt",
6247
+ "aria-pressed": modelView === "all",
6248
+ title: t("row.modelsViewAllHint"),
6249
+ onClick: () => {
6250
+ setModelView("all");
6251
+ },
6252
+ children: t("row.modelsViewAll", { count: orderedModels.length })
6253
+ })]
6254
+ }) : null,
6255
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6256
+ className: "dsm-trae-models-search",
6257
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6258
+ type: "search",
6259
+ className: "dsm-trae-models-search-input",
6260
+ value: modelQuery,
6261
+ placeholder: t("row.modelsSearchPlaceholder"),
6262
+ "aria-label": t("row.modelsSearch"),
6263
+ onChange: (event) => {
6264
+ setModelQuery(event.target.value);
6265
+ setClosedModelGroups(/* @__PURE__ */ new Set());
6266
+ }
6267
+ }), modelQueryActive ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6268
+ className: "dsm-trae-models-search-count",
6269
+ children: t("row.modelsSearchCount", {
6270
+ shown: shownModels.length,
6271
+ total: scopedModels.length
6272
+ })
6273
+ }) : null]
5525
6274
  }),
5526
- signedIn ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5527
- className: "dsm-trae-panels",
5528
- role: "tablist",
5529
- "aria-label": t("row.panelAria"),
5530
- children: [
5531
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6275
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6276
+ className: "dsm-trae-model-list",
6277
+ children: [modelGroups.length === 0 ? matchesOutsideView > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
6278
+ className: "dsm-trae-models-empty",
6279
+ children: [t("row.modelsSearchOutsideView", {
6280
+ query: modelQuery.trim(),
6281
+ count: matchesOutsideView
6282
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5532
6283
  type: "button",
5533
- role: "tab",
5534
- id: "dsm-trae-tab-accounts",
5535
- "aria-controls": "dsm-trae-panel-accounts",
5536
- "aria-selected": panel === "accounts",
5537
- className: `dsm-trae-panel-tab${panel === "accounts" ? " dsm-trae-panel-tab-active" : ""}`,
6284
+ className: "dsm-trae-models-widen",
5538
6285
  onClick: () => {
5539
- setPanel("accounts");
6286
+ setModelView("all");
6287
+ },
6288
+ children: t("row.modelsViewShowAll")
6289
+ })]
6290
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6291
+ className: "dsm-trae-models-empty",
6292
+ children: t("row.modelsSearchEmpty", { query: modelQuery.trim() })
6293
+ }) : null, modelGroups.map((group) => {
6294
+ const groupKey = `${activeRegion}|${group.channel ?? ""}`;
6295
+ const groupOpen = modelQueryActive || !closedModelGroups.has(groupKey);
6296
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
6297
+ className: "dsm-trae-model-group",
6298
+ open: groupOpen,
6299
+ onToggle: (event) => {
6300
+ const open = event.currentTarget.open;
6301
+ setClosedModelGroups((current) => {
6302
+ if (open === !current.has(groupKey)) return current;
6303
+ const next = new Set(current);
6304
+ if (open) next.delete(groupKey);
6305
+ else next.add(groupKey);
6306
+ return next;
6307
+ });
5540
6308
  },
5541
- children: t("row.panelAccounts")
6309
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
6310
+ className: "dsm-trae-model-group-head",
6311
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6312
+ className: "dsm-trae-model-group-name",
6313
+ children: modelChannelLabel(group.channel, t)
6314
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6315
+ className: "dsm-trae-model-group-count",
6316
+ children: t("row.modelsGroupCount", { count: group.rows.length })
6317
+ })]
6318
+ }), group.rows.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6319
+ className: `dsm-trae-model${activeEnabledIds.has(model.id) ? "" : " dsm-trae-model-disabled"}`,
6320
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6321
+ className: "dsm-trae-model-head",
6322
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6323
+ className: "dsm-trae-model-enabled",
6324
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6325
+ type: "checkbox",
6326
+ checked: activeEnabledIds.has(model.id),
6327
+ disabled: settingsScope?.getSnapshot().writable !== true || saving,
6328
+ onChange: () => {
6329
+ toggleModel(model.id);
6330
+ }
6331
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6332
+ className: "dsm-trae-model-copy",
6333
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6334
+ className: "dsm-trae-model-name",
6335
+ children: [
6336
+ model.name,
6337
+ activeRegion === "enterprise" || model.creditMultiplier === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6338
+ className: "dsm-trae-model-name-rate",
6339
+ children: ["· x", model.creditMultiplier.toFixed(2)]
6340
+ }),
6341
+ probeBadge(probeByRegion[activeRegion][model.id], t)
6342
+ ]
6343
+ }), promoBadge(model, t, nowForPromo)]
6344
+ })]
6345
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6346
+ className: "dsm-trae-model-options",
6347
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6348
+ className: "dsm-trae-model-image",
6349
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6350
+ type: "checkbox",
6351
+ checked: rowAcceptsImage(model),
6352
+ disabled: rowImageIsAutomatic(model) || settingsScope?.getSnapshot().writable !== true || saving,
6353
+ title: rowImageIsAutomatic(model) ? t("row.modelImageAuto") : void 0,
6354
+ onChange: () => {
6355
+ toggleImage(model.id);
6356
+ }
6357
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelImage") })]
6358
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("fieldset", {
6359
+ className: "dsm-trae-context-budget",
6360
+ "aria-label": t("row.contextBudget"),
6361
+ title: t("row.contextBudgetHint"),
6362
+ children: contextBudgetOptions(model).map((option) => {
6363
+ const selectable = model.maxContextWindow !== void 0;
6364
+ const checked = (activeContextBudgets[model.id] ?? model.contextWindow) === option;
6365
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6366
+ type: "radio",
6367
+ name: `context-${model.id}`,
6368
+ checked,
6369
+ disabled: !selectable || settingsScope?.getSnapshot().writable !== true || saving,
6370
+ onChange: () => {
6371
+ setContextBudget(model.id, option);
6372
+ }
6373
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatCapacity(option, t("row.modelUnknown")) })] }, option);
6374
+ })
6375
+ })]
6376
+ })]
6377
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6378
+ className: "dsm-trae-model-meta",
6379
+ children: [
6380
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelContext", { context: formatCapacity(model.maxContextWindow ?? model.contextWindow, t("row.modelUnknown")) }) }),
6381
+ model.maxTokens === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelOutput", { output: formatCapacity(model.maxTokens, t("row.modelUnknown")) }) }),
6382
+ effortPicker(model)
6383
+ ]
6384
+ })]
6385
+ }, model.id))]
6386
+ }, groupKey);
6387
+ })]
6388
+ }),
6389
+ enabledOutsideView.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
6390
+ className: "dsm-trae-models-note",
6391
+ children: [t("row.modelsEnabledOutsideView", { count: enabledOutsideView.length }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6392
+ type: "button",
6393
+ className: "dsm-trae-models-widen",
6394
+ onClick: () => {
6395
+ setModelView("all");
6396
+ },
6397
+ children: t("row.modelsViewShowAll")
6398
+ })]
6399
+ }),
6400
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6401
+ className: "dsm-trae-context-bulk",
6402
+ children: [
6403
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6404
+ type: "button",
6405
+ className: "dsm-btn dsm-btn-outline",
6406
+ disabled: busy || saving || settingsScope?.getSnapshot().writable !== true || !visibleModels.some((model) => contextBudgetOptions(model).includes(5e5)),
6407
+ title: t("row.contextAllMidHint"),
6408
+ onClick: setAllContextMid,
6409
+ children: t("row.contextAllMid")
5542
6410
  }),
5543
6411
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5544
6412
  type: "button",
5545
- role: "tab",
5546
- id: "dsm-trae-tab-models",
5547
- "aria-controls": "dsm-trae-panel-models",
5548
- "aria-selected": panel === "models",
5549
- className: `dsm-trae-panel-tab${panel === "models" ? " dsm-trae-panel-tab-active" : ""}`,
5550
- onClick: () => {
5551
- setPanel("models");
5552
- },
5553
- children: t("row.panelModels")
6413
+ className: "dsm-btn dsm-btn-outline",
6414
+ disabled: busy || saving || settingsScope?.getSnapshot().writable !== true || !visibleModels.some((model) => model.maxContextWindow !== void 0),
6415
+ title: t("row.contextAllMaxHint"),
6416
+ onClick: setAllContextMax,
6417
+ children: t("row.contextAllMax")
5554
6418
  }),
5555
6419
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5556
6420
  type: "button",
5557
- role: "tab",
5558
- id: "dsm-trae-tab-diag",
5559
- "aria-controls": "dsm-trae-panel-diag",
5560
- "aria-selected": panel === "diag",
5561
- className: `dsm-trae-panel-tab${panel === "diag" ? " dsm-trae-panel-tab-active" : ""}`,
5562
- onClick: () => {
5563
- setPanel("diag");
5564
- },
5565
- children: t("row.panelDiag")
6421
+ className: "dsm-btn dsm-btn-outline",
6422
+ disabled: busy || saving || settingsScope?.getSnapshot().writable !== true || Object.keys(activeContextBudgets).length === 0,
6423
+ title: t("row.contextAllDefaultHint"),
6424
+ onClick: resetAllContextBudgets,
6425
+ children: t("row.contextAllDefault")
5566
6426
  })
5567
6427
  ]
5568
- }) : null,
5569
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5570
- className: "dsm-trae-panel",
5571
- role: "tabpanel",
5572
- id: "dsm-trae-panel-accounts",
5573
- "aria-labelledby": "dsm-trae-tab-accounts",
5574
- hidden: signedIn && panel !== "accounts",
5575
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TraeAccountPanel, {
5576
- t,
5577
- settingsScope,
5578
- panel: activeRegion,
5579
- status,
5580
- statusLabel: label,
5581
- credentialRegionIsAi,
5582
- regionOn: activeRegionOn,
5583
- showPointsInMainUi: settingsEdits.showPointsInMainUi,
5584
- onToggleShowPoints: (value) => {
5585
- writePreference("showPointsInMainUi", value);
5586
- },
5587
- creditPosition: creditPositionOn(activeRegion),
5588
- onCreditPositionChange: (position) => {
5589
- changeCreditPosition(activeRegion, position);
5590
- },
5591
- savingPreferences: savingSettings,
5592
- creditsLabelId,
5593
- creditsHintId,
5594
- rows: accountRows,
5595
- busy,
5596
- rescanNote,
5597
- onRescan: () => {
5598
- rescanAccounts();
5599
- },
5600
- switchingAccount,
5601
- onSwitchAccount: (accountId) => {
5602
- switchAccount(accountId);
5603
- },
5604
- onClaim: (accountId) => {
5605
- claimFor(accountId);
5606
- },
5607
- onTransferChanged: async () => {
5608
- const fresh = await refreshUsage(activeRegion);
5609
- if (fresh !== void 0) setStatusByRegion((current) => ({
5610
- ...current,
5611
- [activeRegion]: fresh
5612
- }));
5613
- },
5614
- poolDetails,
5615
- poolClaims: status.status === "signed-in" && status.pool?.checkinSupported === true ? Object.fromEntries(status.accounts.map((account) => [account.id, claimViewOf(account.id, accountStatusByRegion[activeRegion][account.id])])) : void 0,
5616
- models: visibleModels,
5617
- deviceClaimed: deviceClaimedToday,
5618
- onPoolChanged: async () => {
5619
- const fresh = await refreshUsage(activeRegion);
5620
- if (fresh !== void 0) setStatusByRegion((current) => ({
5621
- ...current,
5622
- [activeRegion]: fresh
5623
- }));
5624
- },
5625
- onPoolRefresh: async () => {
5626
- const fresh = await refreshUsage(activeRegion);
5627
- if (fresh !== void 0) setStatusByRegion((current) => ({
5628
- ...current,
5629
- [activeRegion]: fresh
5630
- }));
5631
- },
5632
- creditsUnavailable: status.status === "signed-in" ? status.creditsUnavailable : void 0,
5633
- creditsExpiry: status.status === "signed-in" ? status.credits?.expiry : void 0,
5634
- alternatives: status.status === "signed-in" ? status.alternatives : void 0,
5635
- onSwitchToAlternative: (accountId) => {
5636
- switchAccount(accountId);
6428
+ }),
6429
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6430
+ className: "dsm-trae-model-capability-note",
6431
+ children: t("row.contextBudgetHint")
6432
+ }),
6433
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6434
+ className: "dsm-trae-draw-image",
6435
+ "data-disabled": drawImageServable ? void 0 : "true",
6436
+ title: drawImageServable ? t("row.drawImageHint") : drawImageUnavailableHint,
6437
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6438
+ type: "checkbox",
6439
+ checked: drawImageOn,
6440
+ disabled: !drawImageServable || busy || saving || savingSettings || settingsScope?.getSnapshot().writable !== true,
6441
+ onChange: (event) => {
6442
+ writeDrawImage(event.target.checked);
5637
6443
  }
5638
- })
6444
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [t("row.drawImage"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6445
+ className: "dsm-trae-draw-image-hint",
6446
+ children: [" ", drawImageServable ? t("row.drawImageHint") : drawImageUnavailableHint]
6447
+ })] })]
5639
6448
  }),
5640
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5641
- className: "dsm-trae-panel",
5642
- role: "tabpanel",
5643
- id: "dsm-trae-panel-models",
5644
- "aria-labelledby": "dsm-trae-tab-models",
5645
- hidden: !signedIn || panel !== "models",
5646
- children: signedIn ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5647
- className: "dsm-trae-models",
5648
- "aria-label": t("row.modelsTitle"),
5649
- children: [
5650
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5651
- className: "dsm-trae-models-head",
5652
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
5653
- className: "dsm-trae-models-title",
5654
- children: t("row.modelsTitle")
5655
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5656
- className: "dsm-trae-models-summary",
5657
- children: t("row.modelsSummary", { count: activeEnabledIds.size })
5658
- })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5659
- className: "dsm-trae-models-actions",
5660
- children: [
5661
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5662
- type: "button",
5663
- className: "dsm-btn dsm-btn-outline",
5664
- disabled: busy,
5665
- onClick: () => {
5666
- refreshModels();
5667
- },
5668
- children: busy ? t("row.modelsRefreshing") : t("row.modelsRefresh")
5669
- }),
5670
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5671
- type: "button",
5672
- className: "dsm-btn dsm-btn-outline",
5673
- disabled: busy || testing !== void 0 || activeEnabledIds.size === 0,
5674
- title: t("row.modelsTestHint"),
5675
- onClick: () => {
5676
- testEnabledModels();
5677
- },
5678
- children: testing === void 0 ? t("row.modelsTestEnabled") : t("row.modelsTesting", {
5679
- done: testing.done,
5680
- total: testing.total
5681
- })
5682
- }),
5683
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5684
- type: "button",
5685
- className: "dsm-btn dsm-btn-outline",
5686
- disabled: busy || testing !== void 0 || visibleModels.length === 0,
5687
- title: t("row.modelsTestAllHint"),
5688
- onClick: () => {
5689
- testAllModels();
5690
- },
5691
- children: testing === void 0 ? t("row.modelsTestAll") : t("row.modelsTesting", {
5692
- done: testing.done,
5693
- total: testing.total
5694
- })
5695
- })
5696
- ]
5697
- })]
5698
- }),
5699
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5700
- className: "dsm-trae-models-hint",
5701
- children: t("row.modelsTestHint")
5702
- }),
5703
- testSummary === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5704
- className: "dsm-trae-models-summary",
5705
- children: testSummary
5706
- }),
5707
- testError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5708
- className: "dsm-trae-usage-error",
5709
- role: "alert",
5710
- children: testError
5711
- }),
5712
- orderedModels.length > commonModelCount ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5713
- className: "dsm-trae-models-view",
5714
- role: "group",
5715
- "aria-label": t("row.modelsViewLabel"),
5716
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5717
- type: "button",
5718
- className: "dsm-trae-models-view-opt",
5719
- "aria-pressed": modelView === "common",
5720
- title: t("row.modelsViewCommonHint"),
5721
- onClick: () => {
5722
- setModelView("common");
5723
- },
5724
- children: t("row.modelsViewCommon", { count: commonModelCount })
5725
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5726
- type: "button",
5727
- className: "dsm-trae-models-view-opt",
5728
- "aria-pressed": modelView === "all",
5729
- title: t("row.modelsViewAllHint"),
5730
- onClick: () => {
5731
- setModelView("all");
5732
- },
5733
- children: t("row.modelsViewAll", { count: orderedModels.length })
5734
- })]
5735
- }) : null,
5736
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5737
- className: "dsm-trae-models-search",
5738
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5739
- type: "search",
5740
- className: "dsm-trae-models-search-input",
5741
- value: modelQuery,
5742
- placeholder: t("row.modelsSearchPlaceholder"),
5743
- "aria-label": t("row.modelsSearch"),
5744
- onChange: (event) => {
5745
- setModelQuery(event.target.value);
5746
- setClosedModelGroups(/* @__PURE__ */ new Set());
5747
- }
5748
- }), modelQueryActive ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5749
- className: "dsm-trae-models-search-count",
5750
- children: t("row.modelsSearchCount", {
5751
- shown: shownModels.length,
5752
- total: scopedModels.length
5753
- })
5754
- }) : null]
5755
- }),
5756
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5757
- className: "dsm-trae-model-list",
5758
- children: [modelGroups.length === 0 ? matchesOutsideView > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
5759
- className: "dsm-trae-models-empty",
5760
- children: [t("row.modelsSearchOutsideView", {
5761
- query: modelQuery.trim(),
5762
- count: matchesOutsideView
5763
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5764
- type: "button",
5765
- className: "dsm-trae-models-widen",
5766
- onClick: () => {
5767
- setModelView("all");
5768
- },
5769
- children: t("row.modelsViewShowAll")
5770
- })]
5771
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5772
- className: "dsm-trae-models-empty",
5773
- children: t("row.modelsSearchEmpty", { query: modelQuery.trim() })
5774
- }) : null, modelGroups.map((group) => {
5775
- const groupKey = `${activeRegion}|${group.channel ?? ""}`;
5776
- const groupOpen = modelQueryActive || !closedModelGroups.has(groupKey);
5777
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
5778
- className: "dsm-trae-model-group",
5779
- open: groupOpen,
5780
- onToggle: (event) => {
5781
- const open = event.currentTarget.open;
5782
- setClosedModelGroups((current) => {
5783
- if (open === !current.has(groupKey)) return current;
5784
- const next = new Set(current);
5785
- if (open) next.delete(groupKey);
5786
- else next.add(groupKey);
5787
- return next;
5788
- });
5789
- },
5790
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
5791
- className: "dsm-trae-model-group-head",
5792
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5793
- className: "dsm-trae-model-group-name",
5794
- children: modelChannelLabel(group.channel, t)
5795
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5796
- className: "dsm-trae-model-group-count",
5797
- children: t("row.modelsGroupCount", { count: group.rows.length })
5798
- })]
5799
- }), group.rows.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5800
- className: `dsm-trae-model${activeEnabledIds.has(model.id) ? "" : " dsm-trae-model-disabled"}`,
5801
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5802
- className: "dsm-trae-model-head",
5803
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5804
- className: "dsm-trae-model-enabled",
5805
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5806
- type: "checkbox",
5807
- checked: activeEnabledIds.has(model.id),
5808
- disabled: settingsScope?.getSnapshot().writable !== true || saving,
5809
- onChange: () => {
5810
- toggleModel(model.id);
5811
- }
5812
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5813
- className: "dsm-trae-model-copy",
5814
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5815
- className: "dsm-trae-model-name",
5816
- children: [
5817
- model.name,
5818
- activeRegion === "enterprise" || model.creditMultiplier === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5819
- className: "dsm-trae-model-name-rate",
5820
- children: ["· x", model.creditMultiplier.toFixed(2)]
5821
- }),
5822
- probeBadge(probeByRegion[activeRegion][model.id], t)
5823
- ]
5824
- }), promoBadge(model, t, nowForPromo)]
5825
- })]
5826
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5827
- className: "dsm-trae-model-options",
5828
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5829
- className: "dsm-trae-model-image",
5830
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5831
- type: "checkbox",
5832
- checked: rowAcceptsImage(model),
5833
- disabled: rowImageIsAutomatic(model) || settingsScope?.getSnapshot().writable !== true || saving,
5834
- title: rowImageIsAutomatic(model) ? t("row.modelImageAuto") : void 0,
5835
- onChange: () => {
5836
- toggleImage(model.id);
5837
- }
5838
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelImage") })]
5839
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("fieldset", {
5840
- className: "dsm-trae-context-budget",
5841
- "aria-label": t("row.contextBudget"),
5842
- title: t("row.contextBudgetHint"),
5843
- children: contextBudgetOptions(model).map((option) => {
5844
- const selectable = model.maxContextWindow !== void 0;
5845
- const checked = (activeContextBudgets[model.id] ?? model.contextWindow) === option;
5846
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5847
- type: "radio",
5848
- name: `context-${model.id}`,
5849
- checked,
5850
- disabled: !selectable || settingsScope?.getSnapshot().writable !== true || saving,
5851
- onChange: () => {
5852
- setContextBudget(model.id, option);
5853
- }
5854
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatCapacity(option, t("row.modelUnknown")) })] }, option);
5855
- })
5856
- })]
5857
- })]
5858
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5859
- className: "dsm-trae-model-meta",
5860
- children: [
5861
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelContext", { context: formatCapacity(model.maxContextWindow ?? model.contextWindow, t("row.modelUnknown")) }) }),
5862
- model.maxTokens === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelOutput", { output: formatCapacity(model.maxTokens, t("row.modelUnknown")) }) }),
5863
- model.reasoning === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelReasoning", { efforts: model.reasoning.supported.join(" / ") }) })
5864
- ]
5865
- })]
5866
- }, model.id))]
5867
- }, groupKey);
5868
- })]
5869
- }),
5870
- enabledOutsideView.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
5871
- className: "dsm-trae-models-note",
5872
- children: [t("row.modelsEnabledOutsideView", { count: enabledOutsideView.length }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5873
- type: "button",
5874
- className: "dsm-trae-models-widen",
5875
- onClick: () => {
5876
- setModelView("all");
5877
- },
5878
- children: t("row.modelsViewShowAll")
5879
- })]
5880
- }),
5881
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5882
- className: "dsm-trae-context-bulk",
5883
- children: [
5884
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5885
- type: "button",
5886
- className: "dsm-btn dsm-btn-outline",
5887
- disabled: busy || saving || settingsScope?.getSnapshot().writable !== true || !visibleModels.some((model) => contextBudgetOptions(model).includes(5e5)),
5888
- title: t("row.contextAllMidHint"),
5889
- onClick: setAllContextMid,
5890
- children: t("row.contextAllMid")
5891
- }),
5892
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5893
- type: "button",
5894
- className: "dsm-btn dsm-btn-outline",
5895
- disabled: busy || saving || settingsScope?.getSnapshot().writable !== true || !visibleModels.some((model) => model.maxContextWindow !== void 0),
5896
- title: t("row.contextAllMaxHint"),
5897
- onClick: setAllContextMax,
5898
- children: t("row.contextAllMax")
5899
- }),
5900
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5901
- type: "button",
5902
- className: "dsm-btn dsm-btn-outline",
5903
- disabled: busy || saving || settingsScope?.getSnapshot().writable !== true || Object.keys(activeContextBudgets).length === 0,
5904
- title: t("row.contextAllDefaultHint"),
5905
- onClick: resetAllContextBudgets,
5906
- children: t("row.contextAllDefault")
5907
- })
5908
- ]
5909
- }),
5910
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5911
- className: "dsm-trae-model-capability-note",
5912
- children: t("row.contextBudgetHint")
5913
- }),
5914
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5915
- className: "dsm-trae-draw-image",
5916
- "data-disabled": drawImageServable ? void 0 : "true",
5917
- title: drawImageServable ? t("row.drawImageHint") : drawImageUnavailableHint,
5918
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5919
- type: "checkbox",
5920
- checked: drawImageOn,
5921
- disabled: !drawImageServable || busy || saving || savingSettings || settingsScope?.getSnapshot().writable !== true,
5922
- onChange: (event) => {
5923
- writeDrawImage(event.target.checked);
5924
- }
5925
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [t("row.drawImage"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5926
- className: "dsm-trae-draw-image-hint",
5927
- children: [" ", drawImageServable ? t("row.drawImageHint") : drawImageUnavailableHint]
5928
- })] })]
5929
- }),
5930
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5931
- className: "dsm-trae-model-capability-note",
5932
- children: t("row.modelCapabilityPending")
5933
- }),
5934
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5935
- className: "dsm-trae-model-actions",
5936
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
5937
- className: "dsm-trae-usage-cheer",
5938
- href: TRAE_GITHUB_URL,
5939
- target: "_blank",
5940
- rel: "noopener noreferrer",
5941
- children: [t("row.cheer"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5942
- className: "dsm-trae-usage-cheer-star",
5943
- "aria-hidden": "true",
5944
- children: "★"
5945
- })]
5946
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5947
- className: "dsm-trae-model-actions-buttons",
5948
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5949
- type: "button",
5950
- className: "dsm-btn dsm-btn-outline",
5951
- disabled: !dirty || saving,
5952
- onClick: discardModels,
5953
- children: t("row.discard")
5954
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5955
- type: "button",
5956
- className: "dsm-btn dsm-btn-primary",
5957
- disabled: !dirty || saving || activeEnabledIds.size === 0,
5958
- onClick: () => {
5959
- saveModels();
5960
- },
5961
- children: saving ? t("row.saving") : t("row.save")
5962
- })]
5963
- })]
5964
- })
5965
- ]
5966
- }) }) : null
6449
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6450
+ className: "dsm-trae-model-capability-note",
6451
+ children: t("row.modelCapabilityPending")
6452
+ }),
6453
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6454
+ className: "dsm-trae-model-actions",
6455
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
6456
+ className: "dsm-trae-usage-cheer",
6457
+ href: TRAE_GITHUB_URL,
6458
+ target: "_blank",
6459
+ rel: "noopener noreferrer",
6460
+ children: [t("row.cheer"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6461
+ className: "dsm-trae-usage-cheer-star",
6462
+ "aria-hidden": "true",
6463
+ children: "★"
6464
+ })]
6465
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6466
+ className: "dsm-trae-model-actions-buttons",
6467
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6468
+ type: "button",
6469
+ className: "dsm-btn dsm-btn-outline",
6470
+ disabled: !dirty || saving,
6471
+ onClick: discardModels,
6472
+ children: t("row.discard")
6473
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6474
+ type: "button",
6475
+ className: "dsm-btn dsm-btn-primary",
6476
+ disabled: !dirty || saving || activeEnabledIds.size === 0,
6477
+ onClick: () => {
6478
+ saveModels();
6479
+ },
6480
+ children: saving ? t("row.saving") : t("row.save")
6481
+ })]
6482
+ })]
6483
+ })
6484
+ ]
6485
+ }) }) : null
6486
+ }),
6487
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6488
+ className: "dsm-trae-panel",
6489
+ role: "tabpanel",
6490
+ id: "dsm-trae-panel-diag",
6491
+ "aria-labelledby": "dsm-trae-tab-diag",
6492
+ hidden: !signedIn || panel !== "diag",
6493
+ children: status.status !== "signed-in" || panel !== "diag" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6494
+ className: "dsm-trae-diag",
6495
+ children: [
6496
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6497
+ className: "dsm-trae-diag-row",
6498
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6499
+ className: "dsm-trae-diag-label",
6500
+ children: t("row.diagRegion")
6501
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6502
+ className: "dsm-trae-diag-value",
6503
+ children: activeRegion === "cn" ? t("row.tabCn") : activeRegion === "enterprise" ? t("row.tabEnterprise") : t("row.tabAi")
6504
+ })]
5967
6505
  }),
5968
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5969
- className: "dsm-trae-panel",
5970
- role: "tabpanel",
5971
- id: "dsm-trae-panel-diag",
5972
- "aria-labelledby": "dsm-trae-tab-diag",
5973
- hidden: !signedIn || panel !== "diag",
5974
- children: status.status !== "signed-in" || panel !== "diag" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5975
- className: "dsm-trae-diag",
6506
+ status.unsupportedRegion === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6507
+ className: "dsm-trae-diag-row",
6508
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6509
+ className: "dsm-trae-diag-label",
6510
+ children: t("row.diagUnsupportedRegion")
6511
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6512
+ className: "dsm-trae-diag-value dsm-trae-diag-bad",
5976
6513
  children: [
5977
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5978
- className: "dsm-trae-diag-row",
5979
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5980
- className: "dsm-trae-diag-label",
5981
- children: t("row.diagRegion")
5982
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5983
- className: "dsm-trae-diag-value",
5984
- children: activeRegion === "cn" ? t("row.tabCn") : activeRegion === "enterprise" ? t("row.tabEnterprise") : t("row.tabAi")
5985
- })]
5986
- }),
5987
- status.unsupportedRegion === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5988
- className: "dsm-trae-diag-row",
5989
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5990
- className: "dsm-trae-diag-label",
5991
- children: t("row.diagUnsupportedRegion")
5992
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5993
- className: "dsm-trae-diag-value dsm-trae-diag-bad",
5994
- children: [
5995
- status.unsupportedRegion.region.toUpperCase(),
5996
- " · ",
5997
- status.unsupportedRegion.accounts.join(", ")
5998
- ]
5999
- })]
6000
- }),
6001
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6002
- className: "dsm-trae-diag-row",
6003
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6004
- className: "dsm-trae-diag-label",
6005
- children: t("row.diagProvider")
6006
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6007
- className: "dsm-trae-diag-value dsm-trae-diag-mono",
6008
- children: providerOfPanel(activeRegion)
6009
- })]
6010
- }),
6011
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6012
- className: "dsm-trae-diag-row",
6013
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6014
- className: "dsm-trae-diag-label",
6015
- children: t("row.diagAccount")
6016
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6017
- className: "dsm-trae-diag-value",
6018
- children: status.accountName
6019
- })]
6020
- }),
6021
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6022
- className: "dsm-trae-diag-row",
6023
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6024
- className: "dsm-trae-diag-label",
6025
- children: t("row.diagToken")
6026
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6027
- className: "dsm-trae-diag-value dsm-trae-diag-mono",
6028
- children: formatDateTime(status.tokenExpiresAtMs)
6029
- })]
6030
- }),
6031
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6032
- className: "dsm-trae-diag-row",
6033
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6034
- className: "dsm-trae-diag-label",
6035
- children: t("row.diagRefresh")
6036
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6037
- className: "dsm-trae-diag-value dsm-trae-diag-mono",
6038
- children: status.refreshExpiresAtMs === void 0 ? t("row.diagRefreshNone") : formatDateTime(status.refreshExpiresAtMs)
6039
- })]
6040
- }),
6041
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6042
- className: "dsm-trae-diag-row",
6043
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6044
- className: "dsm-trae-diag-label",
6045
- children: t("row.diagCatalog")
6046
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6047
- className: "dsm-trae-diag-value",
6048
- children: formatNumber(status.models.length)
6049
- })]
6050
- }),
6051
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6052
- className: "dsm-trae-diag-row",
6053
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6054
- className: "dsm-trae-diag-label",
6055
- children: t("row.diagEnabled")
6056
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6057
- className: "dsm-trae-diag-value",
6058
- children: formatNumber(status.enabledModelIds.length)
6059
- })]
6060
- }),
6061
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6062
- className: "dsm-trae-diag-row",
6063
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6064
- className: "dsm-trae-diag-label",
6065
- children: t("row.diagPool")
6066
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6067
- className: "dsm-trae-diag-value",
6068
- children: status.pool === void 0 ? t("row.diagPoolOff") : status.pool.enabled ? t("row.diagPoolOn", { count: status.pool.effectiveMemberAccountIds?.length ?? status.pool.accounts.length }) : t("row.diagPoolOff")
6069
- })]
6070
- }),
6071
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6072
- className: "dsm-trae-diag-row",
6073
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6074
- className: "dsm-trae-diag-label",
6075
- children: t("row.diagCheckin")
6076
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6077
- className: "dsm-trae-diag-value",
6078
- children: status.checkin !== void 0 || status.pool?.checkinSupported === true ? t("row.diagCheckinOn") : t("row.diagCheckinOff")
6079
- })]
6080
- }),
6081
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6082
- className: "dsm-trae-diag-row",
6083
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6084
- className: "dsm-trae-diag-label",
6085
- children: t("row.diagRaw")
6086
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6087
- className: "dsm-trae-diag-value dsm-trae-diag-mono",
6088
- children: status.rawChat === void 0 ? t("row.diagRawOff") : status.rawChat.state
6089
- })]
6090
- }),
6091
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6092
- className: "dsm-trae-diag-row",
6093
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6094
- className: "dsm-trae-diag-label",
6095
- children: t("row.diagFailures")
6096
- }), (() => {
6097
- const failures = [];
6098
- if (status.creditsError !== void 0) failures.push(status.creditsError);
6099
- if (status.payStatusError !== void 0) failures.push(status.payStatusError);
6100
- if (status.checkinError !== void 0) failures.push(status.checkinError);
6101
- if (writeError !== void 0) failures.push(writeError);
6102
- return failures.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6103
- className: "dsm-trae-diag-value",
6104
- children: t("row.diagNoFailures")
6105
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6106
- className: "dsm-trae-diag-value dsm-trae-diag-bad dsm-trae-diag-mono",
6107
- children: failures.join(" · ")
6108
- });
6109
- })()]
6110
- }),
6111
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6112
- className: "dsm-trae-diag-row",
6113
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6114
- className: "dsm-trae-diag-label",
6115
- children: t("row.diagQuarantine")
6116
- }), status.quarantined === void 0 || status.quarantined.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6117
- className: "dsm-trae-diag-value",
6118
- children: t("row.diagQuarantineNone")
6119
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6120
- className: "dsm-trae-diag-value dsm-trae-diag-bad dsm-trae-diag-mono",
6121
- children: status.quarantined.map((item) => item.quarantinedAs).join(" · ")
6122
- })]
6123
- })
6514
+ status.unsupportedRegion.region.toUpperCase(),
6515
+ " · ",
6516
+ status.unsupportedRegion.accounts.join(", ")
6124
6517
  ]
6125
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6126
- className: "dsm-trae-diag-hint",
6127
- children: t("row.diagHint")
6128
- })] })
6518
+ })]
6519
+ }),
6520
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6521
+ className: "dsm-trae-diag-row",
6522
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6523
+ className: "dsm-trae-diag-label",
6524
+ children: t("row.diagProvider")
6525
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6526
+ className: "dsm-trae-diag-value dsm-trae-diag-mono",
6527
+ children: providerOfPanel(activeRegion)
6528
+ })]
6529
+ }),
6530
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6531
+ className: "dsm-trae-diag-row",
6532
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6533
+ className: "dsm-trae-diag-label",
6534
+ children: t("row.diagAccount")
6535
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6536
+ className: "dsm-trae-diag-value",
6537
+ children: status.accountName
6538
+ })]
6539
+ }),
6540
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6541
+ className: "dsm-trae-diag-row",
6542
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6543
+ className: "dsm-trae-diag-label",
6544
+ children: t("row.diagToken")
6545
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6546
+ className: "dsm-trae-diag-value dsm-trae-diag-mono",
6547
+ children: formatDateTime(status.tokenExpiresAtMs)
6548
+ })]
6549
+ }),
6550
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6551
+ className: "dsm-trae-diag-row",
6552
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6553
+ className: "dsm-trae-diag-label",
6554
+ children: t("row.diagRefresh")
6555
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6556
+ className: "dsm-trae-diag-value dsm-trae-diag-mono",
6557
+ children: status.refreshExpiresAtMs === void 0 ? t("row.diagRefreshNone") : formatDateTime(status.refreshExpiresAtMs)
6558
+ })]
6559
+ }),
6560
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6561
+ className: "dsm-trae-diag-row",
6562
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6563
+ className: "dsm-trae-diag-label",
6564
+ children: t("row.diagCatalog")
6565
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6566
+ className: "dsm-trae-diag-value",
6567
+ children: formatNumber(status.models.length)
6568
+ })]
6569
+ }),
6570
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6571
+ className: "dsm-trae-diag-row",
6572
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6573
+ className: "dsm-trae-diag-label",
6574
+ children: t("row.diagEnabled")
6575
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6576
+ className: "dsm-trae-diag-value",
6577
+ children: formatNumber(status.enabledModelIds.length)
6578
+ })]
6579
+ }),
6580
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6581
+ className: "dsm-trae-diag-row",
6582
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6583
+ className: "dsm-trae-diag-label",
6584
+ children: t("row.diagPool")
6585
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6586
+ className: "dsm-trae-diag-value",
6587
+ children: status.pool === void 0 ? t("row.diagPoolOff") : status.pool.enabled ? t("row.diagPoolOn", { count: status.pool.effectiveMemberAccountIds?.length ?? status.pool.accounts.length }) : t("row.diagPoolOff")
6588
+ })]
6589
+ }),
6590
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6591
+ className: "dsm-trae-diag-row",
6592
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6593
+ className: "dsm-trae-diag-label",
6594
+ children: t("row.diagCheckin")
6595
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6596
+ className: "dsm-trae-diag-value",
6597
+ children: status.checkin !== void 0 || status.pool?.checkinSupported === true ? t("row.diagCheckinOn") : t("row.diagCheckinOff")
6598
+ })]
6599
+ }),
6600
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6601
+ className: "dsm-trae-diag-row",
6602
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6603
+ className: "dsm-trae-diag-label",
6604
+ children: t("row.diagRaw")
6605
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6606
+ className: "dsm-trae-diag-value dsm-trae-diag-mono",
6607
+ children: status.rawChat === void 0 ? t("row.diagRawOff") : status.rawChat.state
6608
+ })]
6609
+ }),
6610
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6611
+ className: "dsm-trae-diag-row",
6612
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6613
+ className: "dsm-trae-diag-label",
6614
+ children: t("row.diagFailures")
6615
+ }), (() => {
6616
+ const failures = [];
6617
+ if (status.creditsError !== void 0) failures.push(status.creditsError);
6618
+ if (status.payStatusError !== void 0) failures.push(status.payStatusError);
6619
+ if (status.checkinError !== void 0) failures.push(status.checkinError);
6620
+ if (writeError !== void 0) failures.push(writeError);
6621
+ return failures.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6622
+ className: "dsm-trae-diag-value",
6623
+ children: t("row.diagNoFailures")
6624
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6625
+ className: "dsm-trae-diag-value dsm-trae-diag-bad dsm-trae-diag-mono",
6626
+ children: failures.join(" · ")
6627
+ });
6628
+ })()]
6629
+ }),
6630
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6631
+ className: "dsm-trae-diag-row",
6632
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6633
+ className: "dsm-trae-diag-label",
6634
+ children: t("row.diagQuarantine")
6635
+ }), status.quarantined === void 0 || status.quarantined.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6636
+ className: "dsm-trae-diag-value",
6637
+ children: t("row.diagQuarantineNone")
6638
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6639
+ className: "dsm-trae-diag-value dsm-trae-diag-bad dsm-trae-diag-mono",
6640
+ children: status.quarantined.map((item) => item.quarantinedAs).join(" · ")
6641
+ })]
6642
+ })
6643
+ ]
6644
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6645
+ className: "dsm-trae-diag-hint",
6646
+ children: t("row.diagHint")
6647
+ })] })
6648
+ }),
6649
+ status.status === "signed-out" && loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6650
+ className: "dsm-trae-usage-text",
6651
+ role: "status",
6652
+ children: t("row.accountsLoading")
6653
+ }) }) : null,
6654
+ status.status === "signed-out" && !loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
6655
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6656
+ className: "dsm-trae-usage-text",
6657
+ children: status.message ?? t("row.signedOutHint")
6658
+ }),
6659
+ status.unsupportedRegion === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
6660
+ className: "dsm-trae-quarantine",
6661
+ open: true,
6662
+ children: [
6663
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("row.unsupportedRegionTitle") }),
6664
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6665
+ className: "dsm-trae-quarantine-hint",
6666
+ children: t("row.unsupportedRegionHint")
6129
6667
  }),
6130
- status.status === "signed-out" && loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6131
- className: "dsm-trae-usage-text",
6132
- role: "status",
6133
- children: t("row.accountsLoading")
6134
- }) }) : null,
6135
- status.status === "signed-out" && !loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
6136
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6137
- className: "dsm-trae-usage-text",
6138
- children: status.message ?? t("row.signedOutHint")
6139
- }),
6140
- status.unsupportedRegion === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
6141
- className: "dsm-trae-quarantine",
6142
- open: true,
6143
- children: [
6144
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("row.unsupportedRegionTitle") }),
6145
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6146
- className: "dsm-trae-quarantine-hint",
6147
- children: t("row.unsupportedRegionHint")
6148
- }),
6149
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
6150
- className: "dsm-trae-quarantine-list",
6151
- children: status.unsupportedRegion.accounts.map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: name }) }, name))
6152
- })
6153
- ]
6154
- }),
6155
- status.quarantined && status.quarantined.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
6156
- className: "dsm-trae-quarantine",
6157
- open: true,
6668
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
6669
+ className: "dsm-trae-quarantine-list",
6670
+ children: status.unsupportedRegion.accounts.map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: name }) }, name))
6671
+ })
6672
+ ]
6673
+ }),
6674
+ status.quarantined && status.quarantined.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
6675
+ className: "dsm-trae-quarantine",
6676
+ open: true,
6677
+ children: [
6678
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", { children: [
6679
+ t("row.quarantineTitle"),
6680
+ " (",
6681
+ status.quarantined.length,
6682
+ ")"
6683
+ ] }),
6684
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6685
+ className: "dsm-trae-quarantine-hint",
6686
+ children: t("row.quarantineHint")
6687
+ }),
6688
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
6689
+ className: "dsm-trae-quarantine-list",
6690
+ children: status.quarantined.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: item.quarantinedAs }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6691
+ className: "dsm-trae-quarantine-reason",
6158
6692
  children: [
6159
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", { children: [
6160
- t("row.quarantineTitle"),
6161
- " (",
6162
- status.quarantined.length,
6163
- ")"
6164
- ] }),
6165
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6166
- className: "dsm-trae-quarantine-hint",
6167
- children: t("row.quarantineHint")
6168
- }),
6169
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
6170
- className: "dsm-trae-quarantine-list",
6171
- children: status.quarantined.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: item.quarantinedAs }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6172
- className: "dsm-trae-quarantine-reason",
6173
- children: [
6174
- item.accounts === void 0 ? t("row.quarantineAccountsUnknown") : t("row.quarantineAccounts", { count: item.accounts }),
6175
- " · ",
6176
- t("row.quarantineBytes", { bytes: item.bytes })
6177
- ]
6178
- })] }, item.quarantinedAs))
6179
- })
6693
+ item.accounts === void 0 ? t("row.quarantineAccountsUnknown") : t("row.quarantineAccounts", { count: item.accounts }),
6694
+ " · ",
6695
+ t("row.quarantineBytes", { bytes: item.bytes })
6180
6696
  ]
6181
- }) : null,
6182
- status.searched && status.searched.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
6183
- className: "dsm-trae-searched",
6697
+ })] }, item.quarantinedAs))
6698
+ })
6699
+ ]
6700
+ }) : null,
6701
+ status.searched && status.searched.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
6702
+ className: "dsm-trae-searched",
6703
+ children: [
6704
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", { children: [
6705
+ t("row.searchedTitle"),
6706
+ " (",
6707
+ status.searched.length,
6708
+ ")"
6709
+ ] }),
6710
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6711
+ className: "dsm-trae-searched-hint",
6712
+ children: t("row.searchedHint")
6713
+ }),
6714
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
6715
+ className: "dsm-trae-searched-list",
6716
+ children: status.searched.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: item.path }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6717
+ className: "dsm-trae-searched-reason",
6184
6718
  children: [
6185
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", { children: [
6186
- t("row.searchedTitle"),
6187
- " (",
6188
- status.searched.length,
6189
- ")"
6190
- ] }),
6191
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6192
- className: "dsm-trae-searched-hint",
6193
- children: t("row.searchedHint")
6194
- }),
6195
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
6196
- className: "dsm-trae-searched-list",
6197
- children: status.searched.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: item.path }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6198
- className: "dsm-trae-searched-reason",
6199
- children: [
6200
- t(item.source === "cli" ? "row.sourceCli" : "row.sourceDesktop"),
6201
- " · ",
6202
- t(item.reason === "missing" ? "row.reasonMissing" : item.reason === "unreadable" ? "row.reasonUnreadable" : "row.reasonInvalid")
6203
- ]
6204
- })] }, `${item.source}:${item.path}`))
6205
- })
6719
+ t(item.source === "cli" ? "row.sourceCli" : "row.sourceDesktop"),
6720
+ " · ",
6721
+ t(item.reason === "missing" ? "row.reasonMissing" : item.reason === "unreadable" ? "row.reasonUnreadable" : "row.reasonInvalid")
6206
6722
  ]
6207
- }) : null
6208
- ] }) : null,
6209
- status.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6210
- className: "dsm-trae-usage-error",
6211
- children: status.message
6212
- }) : null
6723
+ })] }, `${item.source}:${item.path}`))
6724
+ })
6213
6725
  ]
6214
6726
  }) : null
6727
+ ] }) : null,
6728
+ status.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6729
+ className: "dsm-trae-usage-error",
6730
+ children: status.message
6731
+ }) : null
6732
+ ]
6733
+ });
6734
+ /**
6735
+ * The page header, drawn only by the sidebar row's `main` panel.
6736
+ *
6737
+ * A settings card has the Plugins page's own chrome around it; a full page has
6738
+ * nothing, so it has to say what it is and how to leave. The back control
6739
+ * reuses the host's `layout.selectPanel(null)` (handed down as
6740
+ * `backToConversation`), which is the only public contract for returning the
6741
+ * centre column to the conversation — and it is omitted, not drawn dead, on a
6742
+ * host that does not declare `layout`.
6743
+ */
6744
+ const pageHead = pageSurface ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6745
+ className: "dsm-trae-page-head",
6746
+ children: [
6747
+ backToConversation === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6748
+ type: "button",
6749
+ className: "dsm-trae-page-back",
6750
+ onClick: backToConversation,
6751
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6752
+ "aria-hidden": "true",
6753
+ className: "dsm-trae-page-back-caret"
6754
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.pageBack") })]
6755
+ }),
6756
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
6757
+ className: "dsm-trae-page-icon",
6758
+ src: TRAE_PLUGIN_ICON,
6759
+ alt: ""
6760
+ }),
6761
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6762
+ className: "dsm-trae-page-title",
6763
+ children: title
6764
+ }),
6765
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6766
+ className: "dsm-trae-page-desc",
6767
+ children: t("row.desc")
6768
+ })
6769
+ ]
6770
+ }) : null;
6771
+ if (pageSurface) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6772
+ className: "dsm-trae-page",
6773
+ children: [
6774
+ pageHead,
6775
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6776
+ className: "dsm-trae-page-body",
6777
+ children: cardBody
6778
+ }),
6779
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BusyOverlay, {
6780
+ visible: saving || savingSettings,
6781
+ message: t("row.veilSaving")
6782
+ })
6783
+ ]
6784
+ });
6785
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
6786
+ className: `dsm-plugin-card${open ? " dsm-plugin-card-open" : ""}`,
6787
+ children: [
6788
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6789
+ type: "button",
6790
+ className: "dsm-plugin-card-header",
6791
+ "aria-expanded": open,
6792
+ "aria-label": `${t(open ? "row.collapse" : "row.expand")}: ${title}`,
6793
+ onClick: () => {
6794
+ setCardOpen(!open);
6795
+ },
6796
+ children: [
6797
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
6798
+ className: "dsm-plugin-card-icon",
6799
+ src: TRAE_PLUGIN_ICON,
6800
+ alt: ""
6801
+ }),
6802
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6803
+ className: "dsm-plugin-card-head",
6804
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6805
+ className: "dsm-plugin-card-title",
6806
+ children: title
6807
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6808
+ className: "dsm-plugin-card-description",
6809
+ children: t("row.desc")
6810
+ })]
6811
+ }),
6812
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6813
+ "aria-hidden": "true",
6814
+ className: `dsm-plugin-card-chevron${open ? " dsm-plugin-card-chevron-open" : ""}`
6815
+ })
6816
+ ]
6817
+ }),
6818
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6819
+ className: "dsm-plugin-card-body",
6820
+ hidden: !open,
6821
+ children: open ? cardBody : null
6215
6822
  }),
6216
6823
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BusyOverlay, {
6217
6824
  visible: saving || savingSettings,
@@ -6350,9 +6957,16 @@ window.__ModuleLoader__.load({
6350
6957
  * at a time and nothing overlaps.
6351
6958
  *
6352
6959
  * The row itself is a bare clickable readout — `Trae CN · 657` — with NO
6353
- * refresh control. Clicking it opens an anchored panel: a TABLE with one row
6960
+ * controls of its own. Clicking it opens an anchored panel: a TABLE with one row
6354
6961
  * per account in the region and that account's general balance, where a row
6355
- * click switches to it. The manual refresh button lives in the panel too.
6962
+ * click switches to it.
6963
+ *
6964
+ * The panel's header carries a SETTINGS button, not a refresh button: the
6965
+ * question a reader actually has at this panel is "where do I change this?", and
6966
+ * the answer used to require closing the popover, finding the Plugins row and
6967
+ * clicking the bundle. Refreshing is not lost — the readout polls every five
6968
+ * minutes and every account switch re-reads — but it no longer needs a button of
6969
+ * its own. One press now lands on this plugin's own page (`./settings-route.ts`).
6356
6970
  *
6357
6971
  * Two deliberate choices in that panel:
6358
6972
  *
@@ -6479,7 +7093,7 @@ window.__ModuleLoader__.load({
6479
7093
  });
6480
7094
  }
6481
7095
  function ComposerPoints(props) {
6482
- const { t, provider, region, settingsScope } = props;
7096
+ const { t, provider, region, settingsScope, onOpenSettings } = props;
6483
7097
  if (t === void 0) throw new Error("Composer points readout requires its translation function");
6484
7098
  const owned = provider === void 0 ? void 0 : TRAE_COMPOSER_PROVIDERS[provider];
6485
7099
  const activeRegion = region ?? owned;
@@ -6495,9 +7109,54 @@ window.__ModuleLoader__.load({
6495
7109
  * open, for the same reason the credit route does.
6496
7110
  */
6497
7111
  const [accountPlans, setAccountPlans] = (0, react.useState)(void 0);
6498
- const [busy, setBusy] = (0, react.useState)(false);
6499
7112
  const [switchingId, setSwitchingId] = (0, react.useState)(void 0);
6500
7113
  const [failed, setFailed] = (0, react.useState)(false);
7114
+ /**
7115
+ * The settings button was pressed on a host with no route to the page.
7116
+ *
7117
+ * Its own state rather than reusing `failed`: the two say different things
7118
+ * ("the upstream read failed" vs "this host cannot open the settings page"),
7119
+ * and a shared flag would let one press erase the other's explanation.
7120
+ */
7121
+ const [settingsFailed, setSettingsFailed] = (0, react.useState)(false);
7122
+ /**
7123
+ * The account-pool test batch, driven from this panel's header.
7124
+ *
7125
+ * WHY IT LIVES HERE AT ALL: the rows below ARE the pool's members, and a row's
7126
+ * mark is a MEASUREMENT (`被限流 · 约 6 小时后` is a probe result, not a
7127
+ * setting). Re-reading the usage document cannot improve a stale measurement —
7128
+ * only running it again can — so the question "which of these can serve right
7129
+ * now?" had no answer from the place that asks it. The pool card can do this,
7130
+ * but it is behind the settings page; the reader is already looking at these
7131
+ * accounts.
7132
+ *
7133
+ * `testing` is separate from the panel's other busy states on purpose: this is
7134
+ * a SERIAL batch against one account at a time with a gap (hitting the whole
7135
+ * pool at once is how the upstream's volume limit gets tripped), so it can run
7136
+ * for a minute while a credits read would be merely slow.
7137
+ *
7138
+ * `testFailed` is its own line for the same reason `settingsFailed` is: three
7139
+ * different failures rendered into one slot would take turns overwriting each
7140
+ * other. `testNote` carries the last outcome summary, which is what makes the
7141
+ * press feel answered even when the table's marks are subtle.
7142
+ */
7143
+ const [testing, setTesting] = (0, react.useState)(false);
7144
+ const [testFailed, setTestFailed] = (0, react.useState)(void 0);
7145
+ const [testNote, setTestNote] = (0, react.useState)(void 0);
7146
+ const testingRef = (0, react.useRef)(false);
7147
+ /**
7148
+ * The usage document, mirrored into a ref.
7149
+ *
7150
+ * `fetchAccountPlans` has to read the roster out of it, and reading it as a
7151
+ * `useCallback` DEPENDENCY made that callback a new function on every poll
7152
+ * (every `fetchUsage` stores a fresh object). The open/region effect calls
7153
+ * the callback by identity, so a new identity re-ran it — which meant every
7154
+ * five-minute poll silently re-read the per-account status of every account,
7155
+ * the exact "read on OPEN, not on a cycle" cost the effect's own comment says
7156
+ * it is avoiding. The ref keeps the callback stable while still reading the
7157
+ * newest document.
7158
+ */
7159
+ const usageRef = (0, react.useRef)(void 0);
6501
7160
  const [lastRefresh, setLastRefresh] = (0, react.useState)(void 0);
6502
7161
  const [open, setOpen] = (0, react.useState)(false);
6503
7162
  const inFlight = (0, react.useRef)(false);
@@ -6522,7 +7181,6 @@ window.__ModuleLoader__.load({
6522
7181
  const fetchUsage = (0, react.useCallback)(async (signal) => {
6523
7182
  if (inFlight.current || activeRegion === void 0) return;
6524
7183
  inFlight.current = true;
6525
- if (mounted.current) setBusy(true);
6526
7184
  try {
6527
7185
  const response = await fetch(withTraeRegion(TRAE_USAGE_PATH, activeRegion), {
6528
7186
  headers: { accept: "application/json" },
@@ -6532,6 +7190,7 @@ window.__ModuleLoader__.load({
6532
7190
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
6533
7191
  const value = await response.json();
6534
7192
  if (!mounted.current || signal?.aborted === true) return;
7193
+ usageRef.current = value;
6535
7194
  setUsage(value);
6536
7195
  setLastRefresh(Date.now());
6537
7196
  setFailed(false);
@@ -6539,7 +7198,6 @@ window.__ModuleLoader__.load({
6539
7198
  if (mounted.current && signal?.aborted !== true) setFailed(true);
6540
7199
  } finally {
6541
7200
  inFlight.current = false;
6542
- if (mounted.current) setBusy(false);
6543
7201
  }
6544
7202
  }, [activeRegion]);
6545
7203
  /**
@@ -6590,7 +7248,8 @@ window.__ModuleLoader__.load({
6590
7248
  */
6591
7249
  const fetchAccountPlans = (0, react.useCallback)(async (signal) => {
6592
7250
  if (activeRegion !== "ai") return;
6593
- const roster = usage?.status === "signed-in" ? usage.accounts.filter((account) => account.region === "ai") : [];
7251
+ const document = usageRef.current;
7252
+ const roster = document?.status === "signed-in" ? document.accounts.filter((account) => account.region === "ai") : [];
6594
7253
  if (roster.length === 0) {
6595
7254
  setAccountPlans({});
6596
7255
  return;
@@ -6611,7 +7270,7 @@ window.__ModuleLoader__.load({
6611
7270
  }));
6612
7271
  if (!mounted.current || signal?.aborted === true) return;
6613
7272
  setAccountPlans(Object.fromEntries(entries));
6614
- }, [activeRegion, usage]);
7273
+ }, [activeRegion]);
6615
7274
  (0, react.useEffect)(() => {
6616
7275
  if (!open || activeRegion === void 0) return void 0;
6617
7276
  const controller = new AbortController();
@@ -6669,6 +7328,100 @@ window.__ModuleLoader__.load({
6669
7328
  if (mounted.current) setSwitchingId(void 0);
6670
7329
  }
6671
7330
  };
7331
+ /**
7332
+ * The header's settings control: open this plugin's page, then close.
7333
+ *
7334
+ * CLOSING IS PART OF THE JOB. The panel is portaled to `document.body` and
7335
+ * floats over the composer; navigating the centre column does not remove it,
7336
+ * so leaving it open would park a credits table on top of the page the user
7337
+ * just asked for. It is not reopened on return — a popover that comes back by
7338
+ * itself after navigation is a popover the user cannot get rid of.
7339
+ *
7340
+ * `false` (no route on this host) sets the panel's own error line rather than
7341
+ * doing nothing: a press that changes nothing on screen is indistinguishable
7342
+ * from a broken button, and the honest answer here is "this host has no way
7343
+ * there".
7344
+ */
7345
+ const openSettings = () => {
7346
+ if (!(onOpenSettings === void 0 ? false : onOpenSettings())) {
7347
+ setSettingsFailed(true);
7348
+ return;
7349
+ }
7350
+ setSettingsFailed(false);
7351
+ setOpen(false);
7352
+ };
7353
+ /**
7354
+ * Run the pool's test batch, then re-read what it changed.
7355
+ *
7356
+ * The route is the SAME one the card's "测试勾选的账号" drives — it tests the
7357
+ * pool's checked-in members, not every account listed in this panel — and the
7358
+ * model comes from the Host's saved target rather than from here, so a press
7359
+ * cannot spend credits on a model the user did not choose. Same for the
7360
+ * outcome vocabulary and the NDJSON fold: both live in `./pool-test.ts` so the
7361
+ * card and this header cannot answer one batch two ways.
7362
+ *
7363
+ * The re-read afterwards is not a courtesy. The probes live in the USAGE
7364
+ * document (`usage.pool.accounts[].probe`), so the marks only move once that
7365
+ * document is fetched again; without it the press would leave the table
7366
+ * showing the state it just measured away.
7367
+ *
7368
+ * A failure keeps the previous measurements: nothing was learned about them,
7369
+ * and blanking the marks would look like the addresses had become unusable.
7370
+ */
7371
+ const testAccounts = async () => {
7372
+ if (activeRegion === void 0 || testingRef.current) return;
7373
+ testingRef.current = true;
7374
+ setTesting(true);
7375
+ setTestFailed(void 0);
7376
+ setTestNote(void 0);
7377
+ const controller = new AbortController();
7378
+ const timer = setTimeout(() => {
7379
+ controller.abort();
7380
+ }, POOL_TEST_TIMEOUT_MS);
7381
+ try {
7382
+ const response = await fetch(withTraePoolAction(activeRegion, "test"), {
7383
+ method: "POST",
7384
+ headers: { accept: "application/json" },
7385
+ credentials: "same-origin",
7386
+ signal: controller.signal
7387
+ });
7388
+ let failure;
7389
+ let sawAny = false;
7390
+ /**
7391
+ * Counted by ACCOUNT ID, not by line.
7392
+ *
7393
+ * The route streams one `{row}` per account and then a `{rows}` summary
7394
+ * that repeats every one of them, so counting lines would report "4 / 2".
7395
+ * The summary is what guarantees a client that only reads the last line
7396
+ * still gets the whole batch, so it cannot simply be ignored.
7397
+ */
7398
+ const seen = /* @__PURE__ */ new Map();
7399
+ for await (const line of testLinesOf(response)) if (applyTestLine(line, t, (row) => {
7400
+ seen.set(row.accountId, row.outcome);
7401
+ }, (message) => {
7402
+ failure = message;
7403
+ })) sawAny = true;
7404
+ if (!mounted.current) return;
7405
+ if (failure !== void 0) {
7406
+ setTestFailed(failure);
7407
+ return;
7408
+ }
7409
+ if (!sawAny) throw new Error(t("row.poolLogGeneric"));
7410
+ const outcomes = [...seen.values()];
7411
+ setTestNote(t("composer.testDone", {
7412
+ ok: String(outcomes.filter((outcome) => outcome === "ok").length),
7413
+ total: String(outcomes.length)
7414
+ }));
7415
+ } catch {
7416
+ if (mounted.current && !controller.signal.aborted) setTestFailed(t("composer.testFailed"));
7417
+ else if (mounted.current) setTestFailed(t("composer.testTimedOut"));
7418
+ } finally {
7419
+ clearTimeout(timer);
7420
+ testingRef.current = false;
7421
+ if (mounted.current) setTesting(false);
7422
+ await Promise.all([fetchUsage(), fetchAccountCredits()]);
7423
+ }
7424
+ };
6672
7425
  if (owned === void 0) return null;
6673
7426
  const signedIn = usage?.status === "signed-in" ? usage : void 0;
6674
7427
  const signedOut = usage !== void 0 && usage.status === "signed-out";
@@ -6711,6 +7464,15 @@ window.__ModuleLoader__.load({
6711
7464
  };
6712
7465
  });
6713
7466
  const canSwitch = settingsScope !== void 0 && settingsScope.getSnapshot().writable === true;
7467
+ /**
7468
+ * This panel's account pool, when the Host reports one.
7469
+ *
7470
+ * A `pool` carrying no members is still a pool: the test button is drawn (the
7471
+ * Host answers 409 `no-members` and the panel says so) rather than hidden, so
7472
+ * the control's absence always means "this panel has no pool feature", never
7473
+ * "you have not checked anybody in yet".
7474
+ */
7475
+ const pool = signedIn?.pool;
6714
7476
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6715
7477
  ref: rootRef,
6716
7478
  className: "dsm-trae-composer-points",
@@ -6743,14 +7505,72 @@ window.__ModuleLoader__.load({
6743
7505
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6744
7506
  className: "dsm-trae-composer-panel-title",
6745
7507
  children: activeRegion === "ai" ? t("composer.panelTitleGlobal") : t("composer.panelTitle")
6746
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6747
- type: "button",
6748
- className: "dsm-trae-composer-panel-refresh",
6749
- disabled: busy || switchingId !== void 0,
6750
- onClick: () => {
6751
- fetchUsage();
6752
- },
6753
- children: busy ? t("composer.refreshing") : t("composer.refresh")
7508
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7509
+ className: "dsm-trae-composer-panel-actions",
7510
+ children: [pool === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7511
+ type: "button",
7512
+ className: "dsm-trae-composer-panel-test",
7513
+ title: t("composer.testHint"),
7514
+ "aria-label": t("composer.testHint"),
7515
+ disabled: testing || switchingId !== void 0,
7516
+ onClick: () => {
7517
+ testAccounts();
7518
+ },
7519
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
7520
+ className: testing ? "dsm-trae-composer-panel-test-icon dsm-trae-composer-panel-test-icon-spin" : "dsm-trae-composer-panel-test-icon",
7521
+ viewBox: "0 0 16 16",
7522
+ width: "12",
7523
+ height: "12",
7524
+ fill: "none",
7525
+ "aria-hidden": "true",
7526
+ focusable: "false",
7527
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
7528
+ d: "M13.4 8a5.4 5.4 0 1 1-5.4-5.4c1.5 0 2.95.62 3.96 1.63L13.4 5.6",
7529
+ stroke: "currentColor",
7530
+ strokeWidth: "1.5",
7531
+ strokeLinecap: "round",
7532
+ strokeLinejoin: "round"
7533
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
7534
+ d: "M13.4 2.6v3h-3",
7535
+ stroke: "currentColor",
7536
+ strokeWidth: "1.5",
7537
+ strokeLinecap: "round",
7538
+ strokeLinejoin: "round"
7539
+ })]
7540
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7541
+ className: "dsm-trae-composer-panel-test-label",
7542
+ children: testing ? t("composer.testing") : t("composer.test")
7543
+ })]
7544
+ }), onOpenSettings === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7545
+ type: "button",
7546
+ className: "dsm-trae-composer-panel-settings",
7547
+ title: t("composer.openSettingsHint"),
7548
+ disabled: switchingId !== void 0,
7549
+ onClick: openSettings,
7550
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
7551
+ className: "dsm-trae-composer-panel-settings-icon",
7552
+ viewBox: "0 0 16 16",
7553
+ width: "12",
7554
+ height: "12",
7555
+ fill: "none",
7556
+ "aria-hidden": "true",
7557
+ focusable: "false",
7558
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
7559
+ d: "M8 10.2a2.2 2.2 0 1 0 0-4.4 2.2 2.2 0 0 0 0 4.4Z",
7560
+ stroke: "currentColor",
7561
+ strokeWidth: "1.5"
7562
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
7563
+ d: "M12.9 9.6a1.2 1.2 0 0 0 .24 1.32l.04.04a1.4 1.4 0 1 1-1.98 1.98l-.04-.04a1.2 1.2 0 0 0-1.32-.24 1.2 1.2 0 0 0-.72 1.1v.12a1.4 1.4 0 1 1-2.8 0v-.07a1.2 1.2 0 0 0-.78-1.1 1.2 1.2 0 0 0-1.32.24l-.04.04a1.4 1.4 0 1 1-1.98-1.98l.04-.04a1.2 1.2 0 0 0 .24-1.32 1.2 1.2 0 0 0-1.1-.72h-.12a1.4 1.4 0 1 1 0-2.8h.07a1.2 1.2 0 0 0 1.1-.78 1.2 1.2 0 0 0-.24-1.32l-.04-.04a1.4 1.4 0 1 1 1.98-1.98l.04.04a1.2 1.2 0 0 0 1.32.24h.05a1.2 1.2 0 0 0 .72-1.1v-.12a1.4 1.4 0 1 1 2.8 0v.07a1.2 1.2 0 0 0 .72 1.1 1.2 1.2 0 0 0 1.32-.24l.04-.04a1.4 1.4 0 1 1 1.98 1.98l-.04.04a1.2 1.2 0 0 0-.24 1.32v.05a1.2 1.2 0 0 0 1.1.72h.12a1.4 1.4 0 1 1 0 2.8h-.07a1.2 1.2 0 0 0-1.1.72Z",
7564
+ stroke: "currentColor",
7565
+ strokeWidth: "1.5",
7566
+ strokeLinecap: "round",
7567
+ strokeLinejoin: "round"
7568
+ })]
7569
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7570
+ className: "dsm-trae-composer-panel-settings-label",
7571
+ children: t("composer.openSettings")
7572
+ })]
7573
+ })]
6754
7574
  })]
6755
7575
  }),
6756
7576
  signedOut ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -6817,7 +7637,22 @@ window.__ModuleLoader__.load({
6817
7637
  className: "dsm-trae-composer-panel-error",
6818
7638
  role: "status",
6819
7639
  children: t("composer.refreshFailed")
6820
- }) : null
7640
+ }) : null,
7641
+ settingsFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7642
+ className: "dsm-trae-composer-panel-error",
7643
+ role: "status",
7644
+ children: t("composer.openSettingsFailed")
7645
+ }) : null,
7646
+ testNote === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7647
+ className: "dsm-trae-composer-panel-note",
7648
+ role: "status",
7649
+ children: testNote
7650
+ }),
7651
+ testFailed === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7652
+ className: "dsm-trae-composer-panel-error",
7653
+ role: "status",
7654
+ children: testFailed
7655
+ })
6821
7656
  ]
6822
7657
  }), document.body)]
6823
7658
  });
@@ -6911,7 +7746,8 @@ window.__ModuleLoader__.load({
6911
7746
  t,
6912
7747
  provider,
6913
7748
  region,
6914
- ...settingsScope === void 0 ? {} : { settingsScope }
7749
+ ...settingsScope === void 0 ? {} : { settingsScope },
7750
+ ...props.onOpenSettings === void 0 ? {} : { onOpenSettings: props.onOpenSettings }
6915
7751
  });
6916
7752
  }
6917
7753
  //#endregion
@@ -7100,8 +7936,15 @@ window.__ModuleLoader__.load({
7100
7936
  "composer.signedOut": "Not signed in",
7101
7937
  "composer.points": "Trae CN",
7102
7938
  "composer.pointsGlobal": "Trae Global",
7103
- "composer.refresh": "Refresh credits",
7104
- "composer.refreshing": "Refreshing credits…",
7939
+ "composer.openSettings": "Settings",
7940
+ "composer.openSettingsHint": "Open this plugin's settings page",
7941
+ "composer.openSettingsFailed": "This host has no route to the plugin settings page",
7942
+ "composer.test": "Refresh",
7943
+ "composer.testHint": "Refresh every account checked into this panel's pool — probes each one in turn (spends a small amount of credits, can take a minute)",
7944
+ "composer.testing": "Refreshing…",
7945
+ "composer.testDone": "Pool tested: {ok} of {total} answered",
7946
+ "composer.testFailed": "Could not test the pool",
7947
+ "composer.testTimedOut": "Testing took too long and was given up on",
7105
7948
  "composer.refreshFailed": "Could not refresh credits",
7106
7949
  "row.accountsRefreshed": "Accounts refreshed: {count} found{selected}",
7107
7950
  "row.accountsSaved": "bound to",
@@ -7154,8 +7997,10 @@ window.__ModuleLoader__.load({
7154
7997
  "row.modelImage": "Image",
7155
7998
  "row.modelImageAuto": "Enabled automatically: Trae marks this model multimodal.",
7156
7999
  "row.modelRate": "{rate}x credits",
7157
- "row.modelReasoning": "Reasoning: {efforts}",
7158
8000
  "row.modelReasoningSupported": "Reasoning: supported",
8001
+ "row.modelReasoningLabel": "Reasoning",
8002
+ "row.modelReasoningFollowUpstream": "Default",
8003
+ "row.modelReasoningFollowUpstreamNamed": "Default ({effort})",
7159
8004
  "row.modelUnknown": "Unknown",
7160
8005
  "row.modelCapabilityPending": "Image input follows Trae's own multimodal flag, plus anything you tick yourself.",
7161
8006
  "row.poolTitle": "Account pool",
@@ -7307,7 +8152,11 @@ window.__ModuleLoader__.load({
7307
8152
  "row.poolRowDeleteStillSigned": "removed this copy, but {accountName} is still signed in to Trae",
7308
8153
  "row.poolRowDeleteNoCopy": "{accountName} had no plugin copy — only its Trae app login",
7309
8154
  "row.poolDeleteHint": "The plugin keeps its own copy of every local Trae login (a copy is created on refresh unless one already exists) and only ever shows and bills those copies. Delete removes the copy AND signs that account out of Trae itself, leaving a backup of every file it rewrites; a deleted account is not re-imported while it stays signed in Trae. A running Trae would flush its own state back over the file, so quit it first — the card names the app it needs closed.",
7310
- "row.cheer": "Star on GitHub"
8155
+ "row.cheer": "Star on GitHub",
8156
+ "nav.panel": "Trae",
8157
+ "row.pageBack": "Back to conversation",
8158
+ "row.sidebarPanelLabel": "Show Trae in the sidebar",
8159
+ "row.sidebarPanelHint": "Adds a Trae row to the DSH sidebar that opens this card as a full page. Off by default: the row takes a global sidebar seat shared by every installed plugin, and the Plugins page already reaches the same content. Takes effect on the click — no Save needed — and switching it off while the page is open returns you to the conversation."
7311
8160
  };
7312
8161
  const zh = {
7313
8162
  "row.title": "接入使用Trae积分与模型(dsh-connect-trae)",
@@ -7488,8 +8337,15 @@ window.__ModuleLoader__.load({
7488
8337
  "composer.signedOut": "未登录",
7489
8338
  "composer.points": "Trae CN",
7490
8339
  "composer.pointsGlobal": "Trae 国际版",
7491
- "composer.refresh": "刷新积分",
7492
- "composer.refreshing": "正在刷新积分…",
8340
+ "composer.openSettings": "设置",
8341
+ "composer.openSettingsHint": "打开本插件的设置页",
8342
+ "composer.openSettingsFailed": "这个宿主没有通向插件设置页的入口",
8343
+ "composer.test": "刷新",
8344
+ "composer.testHint": "刷新本面板账号池里勾选账号的状态——逐个探测(会消耗少量积分,最长约一分钟)",
8345
+ "composer.testing": "刷新中…",
8346
+ "composer.testDone": "账号池已测:{total} 个里 {ok} 个可用",
8347
+ "composer.testFailed": "账号池测速失败",
8348
+ "composer.testTimedOut": "测速用时过长,已放弃",
7493
8349
  "composer.refreshFailed": "积分刷新失败",
7494
8350
  "row.accountsRefreshed": "已刷新账号:找到 {count} 个账号{selected}",
7495
8351
  "row.accountsSaved": "已绑定",
@@ -7542,8 +8398,10 @@ window.__ModuleLoader__.load({
7542
8398
  "row.modelImage": "图片",
7543
8399
  "row.modelImageAuto": "已自动开启:Trae 把该模型标为多模态。",
7544
8400
  "row.modelRate": "积分 {rate}x",
7545
- "row.modelReasoning": "推理强度:{efforts}",
7546
8401
  "row.modelReasoningSupported": "推理:支持",
8402
+ "row.modelReasoningLabel": "思考强度",
8403
+ "row.modelReasoningFollowUpstream": "默认",
8404
+ "row.modelReasoningFollowUpstreamNamed": "默认({effort})",
7547
8405
  "row.modelUnknown": "未知",
7548
8406
  "row.modelCapabilityPending": "图片输入跟随 Trae 自身的多模态标注,并叠加你手动勾选的模型。",
7549
8407
  "row.poolTitle": "账号池",
@@ -7695,7 +8553,11 @@ window.__ModuleLoader__.load({
7695
8553
  "row.poolRowDeleteStillSigned": "已删除副本,但 {accountName} 仍登录在 Trae 中",
7696
8554
  "row.poolRowDeleteNoCopy": "{accountName} 没有插件副本——它只存在于 Trae 应用登录里",
7697
8555
  "row.poolDeleteHint": "本插件会为本机每个 Trae 登录各创建一份自己的副本(刷新时自动创建,已有副本则不动),卡片只显示、也只使用这些副本。「删除」会同时删掉副本并把该账号从 Trae 应用中退出登录,改写的每个文件都留一份备份;只要该账号还登录在 Trae 里,就不会被重新收录。Trae 正在运行时会把自己的状态回写到文件,所以要先完全退出——卡片会点名是哪个应用。",
7698
- "row.cheer": "鼓励一下"
8556
+ "row.cheer": "鼓励一下",
8557
+ "nav.panel": "Trae",
8558
+ "row.pageBack": "返回会话",
8559
+ "row.sidebarPanelLabel": "在侧边栏显示 Trae",
8560
+ "row.sidebarPanelHint": "在 DSH 侧边栏加一个 Trae 行,点开就是这张卡片的整页版。默认关闭:这一行占的是所有插件共用的全局侧边栏座位,而「插件」页本来就能看到同样的内容。点一下即生效,不用保存;页面开着时关掉它,会直接回到会话。"
7699
8561
  };
7700
8562
  //#endregion
7701
8563
  //#region src/client/TraeModelLabel.tsx
@@ -7980,6 +8842,47 @@ window.__ModuleLoader__.load({
7980
8842
  * @param props - The host's model identity and the placement of this label.
7981
8843
  * @returns The catalog name, followed by the promotion chip when one holds.
7982
8844
  */
8845
+ /**
8846
+ * This plugin's seat in the HOST's model-label cell (issue #74).
8847
+ *
8848
+ * `conversation.input.model.label` is a `single` slot: `SlotCore.register()`
8849
+ * THROWS when a second registration claims the same priority, with
8850
+ * "single slot ... already has a registration at priority 0 ... register at a
8851
+ * different priority to shadow it (lowest renders)". Priority 0 is the DEFAULT,
8852
+ * so it is the one value that is guaranteed to be contested.
8853
+ *
8854
+ * WHY `-0.25` AND NOT 0. Measured on the live desktop host (0.2.1-rc.5) the
8855
+ * cell is already shared:
8856
+ *
8857
+ * dsh-connect-qoder-client priority -1 (active — lowest renders)
8858
+ * dsh-connect-workbuddy-client priority -0.5
8859
+ * dsh-connect-trae-client priority 0 (this plugin)
8860
+ *
8861
+ * -1 and -0.5 are the two sibling connectors' seats and must NOT be reused —
8862
+ * two plugins on one priority collide for exactly the reason 0 did. `-0.25`
8863
+ * sits between them, legal, and collides with neither. The value only has to be
8864
+ * DISTINCT from whatever else claims the cell; it deliberately does not read the
8865
+ * slot ledger or negotiate with other plugins (this repository's §零 invariant,
8866
+ * and the host's own shipped README reaches the same conclusion — its documented
8867
+ * remedy for a same-priority conflict is a configuration decision, not a runtime
8868
+ * handshake).
8869
+ *
8870
+ * WHAT WINNING/HOLDING COSTS. This cell is `single`, so the other registrants'
8871
+ * decorations on the rows this plugin claims are not composed. That is the
8872
+ * documented, accepted trade-off, and it is why the component returns the plain
8873
+ * catalog name for any provider that is not ours — the slot's own minimum.
8874
+ *
8875
+ * WHY A DISTINCT PRIORITY IS NOT ENOUGH ON ITS OWN. It removes the cross-plugin
8876
+ * collision, but issue #74's error was a SECOND registration of THIS plugin
8877
+ * against its own first one (the report's stack shows
8878
+ * "registered by dsh-connect-trae-client" colliding with priority 0). That is
8879
+ * the re-entrancy path `inject` allows: the callback runs again whenever the
8880
+ * slot's declaration lifetime collapses and re-declares, so a callback that
8881
+ * registers unconditionally double-registers. `index.tsx` therefore also
8882
+ * disposes its previous entry before every attempt. Priority alone would have
8883
+ * made the throw rarer and harder to read, not absent.
8884
+ */
8885
+ const MODEL_LABEL_PRIORITY = -.25;
7983
8886
  function TraeModelLabel(props) {
7984
8887
  const { provider, model } = props;
7985
8888
  const panel = panelOfProvider(provider.id);
@@ -8033,6 +8936,92 @@ window.__ModuleLoader__.load({
8033
8936
  });
8034
8937
  }
8035
8938
  //#endregion
8939
+ //#region src/client/settings-route.ts
8940
+ /**
8941
+ * The route from the composer credit readout to THIS plugin's settings page.
8942
+ *
8943
+ * ## Why a module and not three lines inside `apply`
8944
+ *
8945
+ * The entry (`src/client/index.tsx`) imports browser-only DSH packages the Node
8946
+ * test environment cannot load, so anything worth asserting on has to live
8947
+ * outside it — the same seam `panel-switch.ts` provides for the sidebar opt-in.
8948
+ * The spec then exercises the REAL decision instead of a mirror of it.
8949
+ *
8950
+ * ## Why two routes
8951
+ *
8952
+ * On this host line a plugin's settings are not a "page in Settings": the
8953
+ * Plugins page owns them, and each bundle's page (`plugins.bundle.config` /
8954
+ * `plugins.row.config`) IS the configuration surface — Settings keeps only the
8955
+ * read-only built-in inventory. So "open the plugin settings" means "open the
8956
+ * Plugins page on this bundle", and the supported way to ask for that is the
8957
+ * `pluginNavigation` service the Plugins page provides: `openBundle(packageName)`
8958
+ * selects that page without touching the Session.
8959
+ *
8960
+ * `pluginNavigation` is provided by `@deepseek-ai/dsh-client-ui-plugin-manager`,
8961
+ * NOT by the shell. A profile can be composed without it, and plugin load order
8962
+ * is not something a contributor may assume — so the probe happens at CALL time
8963
+ * rather than at registration time. Probing once in `apply` would capture
8964
+ * `undefined` on a page whose Plugins bundle simply applied after ours, and the
8965
+ * button would be dead for the rest of the session with nothing to re-evaluate it.
8966
+ *
8967
+ * The fallback reaches the SAME page through the lower-level `layout` service.
8968
+ * `selectPanel('plugins')` is the shipped main key of the Plugins page (the
8969
+ * plugin-manager bundle registers `sidebar.panellist` id / `main` key
8970
+ * `'plugins'`), so a host without `pluginNavigation` still lands the user where
8971
+ * the settings are, one click from the row. It is a fallback and not the primary
8972
+ * route deliberately: `'plugins'` is another package's private key, while
8973
+ * `openBundle` is that package's published seam. `selectPanel` THROWS when the
8974
+ * key is not registered (Plugins page disabled, trimmed profile), so the call is
8975
+ * guarded rather than trusted.
8976
+ *
8977
+ * ## Why `false` rather than an exception
8978
+ *
8979
+ * The caller is a popover button. There is no supported host line lacking both
8980
+ * routes, but "cannot" must not become a thrown error inside a click handler on
8981
+ * a user's machine; it becomes a `false` the caller reports, and a button that
8982
+ * stays honest about it.
8983
+ */
8984
+ /**
8985
+ * The package name the Plugins page knows this plugin by.
8986
+ *
8987
+ * `openBundle` matches the inventory's `pkg.name` by EXACT equality, and that
8988
+ * name is the profile's package name — the npm package name — not the Loader
8989
+ * entry id (which on a live Desktop profile can be `include:dsh-connect-trae`).
8990
+ * This is the same reason the card registers under every spelling the host has
8991
+ * been observed to dispatch: the comparison is exact and the deployment picks
8992
+ * the spelling. The inventory side of that comparison is the package name in
8993
+ * every composition observed, so that is what `openBundle` is asked for.
8994
+ *
8995
+ * Deliberately ONE name, not a list: `openBundle` selects a target and an
8996
+ * unknown target renders the LIST, so a second call with a spelling the
8997
+ * inventory does not hold would undo the first one's navigation.
8998
+ */
8999
+ const TRAE_PACKAGE_NAME = "dsh-connect-trae";
9000
+ /** The shipped `main` / `sidebar.panellist` key of the Plugins page. */
9001
+ const PLUGINS_PANEL_ID = "plugins";
9002
+ /**
9003
+ * Open this plugin's settings page.
9004
+ *
9005
+ * @param get - the entry's soft service probe (`ctx.get`), which returns
9006
+ * `undefined` for an absent service instead of throwing. Called HERE, on every
9007
+ * invocation, so a service that appears after `apply` is still found.
9008
+ * @returns whether a route was taken. `false` means the caller's control should
9009
+ * report that this host has no way there — see the module note.
9010
+ */
9011
+ function openPluginSettingsPage(get) {
9012
+ const navigation = get("pluginNavigation");
9013
+ if (navigation !== void 0 && typeof navigation.openBundle === "function") try {
9014
+ navigation.openBundle(TRAE_PACKAGE_NAME);
9015
+ return true;
9016
+ } catch {}
9017
+ const layout = get("layout");
9018
+ if (layout !== void 0 && typeof layout.selectPanel === "function") try {
9019
+ layout.selectPanel(PLUGINS_PANEL_ID);
9020
+ return true;
9021
+ } catch {}
9022
+ return false;
9023
+ }
9024
+ //#endregion
8036
9025
  //#region src/client/index.tsx
8037
9026
  /** Stable browser-plugin name. */
8038
9027
  const name = "dsh-connect-trae-client";
@@ -8046,21 +9035,87 @@ window.__ModuleLoader__.load({
8046
9035
  * configForms is not served yet.
8047
9036
  */
8048
9037
  const inject = ["slots", "locale"];
9038
+ const liveSlotEntries = /* @__PURE__ */ new Map();
9039
+ /**
9040
+ * Release this client's hold on `identity`, if it has one.
9041
+ *
9042
+ * `entryOnly` exists because of a subtle self-collision: the release is called
9043
+ * from INSIDE the `inject` callback as well as from `apply`, and disposing the
9044
+ * inject controller from within its own callback would tear down the effect
9045
+ * that is currently executing. Inside the callback only the ENTRY is released;
9046
+ * from `apply`, both.
9047
+ */
9048
+ function releaseSlotEntry(identity, entryOnly = false) {
9049
+ const held = liveSlotEntries.get(identity);
9050
+ if (held === void 0) return;
9051
+ liveSlotEntries.delete(identity);
9052
+ if (held.disposeEntry !== void 0) try {
9053
+ held.disposeEntry();
9054
+ } catch {}
9055
+ if (!entryOnly && held.disposeInject !== void 0) try {
9056
+ held.disposeInject();
9057
+ } catch {}
9058
+ }
8049
9059
  /**
8050
- * Register card copy and the Trae card under Plugin configuration.
8051
- *
8052
- * The entire body is wrapped so that a DSH slot-API breaking change (for
8053
- * example the rc.6→rc.7 `id`→`key` / `order`→`priority` rename) degrades
8054
- * to a `console.error` instead of throwing into the DSH loader and raising
8055
- * the red "Failed to load plugins" banner. The host provider keeps working:
8056
- * the `trae` model channel is unaffected.
8057
- *
8058
- * NOTE: the try/catch boundary of this function is mirrored (duplicated) in
8059
- * `tests/client-fallback.spec.ts`, because the real client entry imports
8060
- * browser-only DSH packages that cannot load in the Node test environment.
8061
- * If you change the guarded body, the soft probe, or the per-slot
8062
- * registration here, update that mirror too.
9060
+ * Register one slot entry, surviving EVERY way the registry can refuse it.
9061
+ *
9062
+ * WHY THE CATCH IS INSIDE THE CALLBACK, which is the whole point of issue #74.
9063
+ * `slots.inject()` does not necessarily run its callback now: when the slot is
9064
+ * not declared yet it subscribes and runs the callback LATER. A later throw is
9065
+ * re-thrown by the renderer's own `changed()` handler out of a `queueMicrotask`,
9066
+ * where no call-site try/catch can reach it — the user gets a renderer error
9067
+ * banner on the first paint after a restart, and a reload (which re-declares in
9068
+ * a different order) clears it. Moving the catch inside the callback turns every
9069
+ * refusal into a log line, matching this file's "degrade, never take the page
9070
+ * down" contract.
9071
+ *
9072
+ * WHY THE RELEASE COMES FIRST. A `single` slot rejects a second live entry at
9073
+ * the same priority. That refusal is OUR fault and we can fix it rather than
9074
+ * report it, so each run releases whatever this identity already holds.
9075
+ *
9076
+ * @param ctx - The applying plugin's context (module scope has none).
9077
+ * @param identity - Stable key for this seat.
9078
+ * @param slotKey - The host slot to register into.
9079
+ * @param register - Performs the registration, returning its disposer.
9080
+ * @param describe - Names the seat in the failure log.
8063
9081
  */
9082
+ function registerSlotEntry(ctx, identity, slotKey, register, describe) {
9083
+ const hold = {};
9084
+ const fail = (error) => {
9085
+ console.error(`[dsh-connect-trae] ${describe} failed to register (host provider unaffected):`, error);
9086
+ };
9087
+ const run = () => {
9088
+ releaseSlotEntry(identity, true);
9089
+ try {
9090
+ const disposeEntry = register();
9091
+ hold.disposeEntry = disposeEntry;
9092
+ const current = liveSlotEntries.get(identity);
9093
+ if (current === void 0) liveSlotEntries.set(identity, hold);
9094
+ else current.disposeEntry = disposeEntry;
9095
+ return disposeEntry;
9096
+ } catch (error) {
9097
+ fail(error);
9098
+ return () => {};
9099
+ }
9100
+ };
9101
+ try {
9102
+ const result = ctx.slots.inject(slotKey, () => {
9103
+ try {
9104
+ return run();
9105
+ } catch (error) {
9106
+ fail(error);
9107
+ return () => {};
9108
+ }
9109
+ });
9110
+ hold.disposeInject = result;
9111
+ const current = liveSlotEntries.get(identity);
9112
+ if (current === void 0) liveSlotEntries.set(identity, hold);
9113
+ else current.disposeInject = result;
9114
+ } catch (error) {
9115
+ releaseSlotEntry(identity);
9116
+ fail(error);
9117
+ }
9118
+ }
8064
9119
  function apply(ctx) {
8065
9120
  try {
8066
9121
  const namespace = "settings.trae";
@@ -8070,6 +9125,16 @@ window.__ModuleLoader__.load({
8070
9125
  }), "dsh-connect-trae: settings copy");
8071
9126
  const t = ctx.locale.bind(namespace);
8072
9127
  const softGet = (name) => ctx.get(name);
9128
+ /**
9129
+ * The composer panel's route to this plugin's own settings page.
9130
+ *
9131
+ * Wrapped in a closure, not resolved here: `pluginNavigation` belongs to the
9132
+ * Plugins bundle and its apply order is not ours to assume, so probing once
9133
+ * at `apply` could capture `undefined` permanently. The decision itself (and
9134
+ * its `layout` fallback) lives in `settings-route.ts`, where the spec can
9135
+ * exercise the real chain instead of a mirror.
9136
+ */
9137
+ const onOpenSettings = () => openPluginSettingsPage(softGet);
8073
9138
  const forms = softGet("configForms");
8074
9139
  /**
8075
9140
  * The card's settings scope, bound to whichever namespace the Host actually
@@ -8140,19 +9205,15 @@ window.__ModuleLoader__.load({
8140
9205
  return scope;
8141
9206
  })();
8142
9207
  const registerCard = (slotName, key) => {
8143
- try {
8144
- ctx.slots.inject(slotName, () => ctx.slots.register({
8145
- name: slotName,
8146
- key,
8147
- priority: 30,
8148
- inject: () => ({
8149
- t,
8150
- settingsScope
8151
- })
8152
- }, TraeUsageCard));
8153
- } catch (error) {
8154
- console.error(`[dsh-connect-trae] card slot "${slotName}" failed to register (host provider unaffected):`, error);
8155
- }
9208
+ registerSlotEntry(ctx, `${slotName}#${key}`, slotName, () => ctx.slots.register({
9209
+ name: slotName,
9210
+ key,
9211
+ priority: 30,
9212
+ inject: () => ({
9213
+ t,
9214
+ settingsScope
9215
+ })
9216
+ }, TraeUsageCard), `card slot "${slotName}"`);
8156
9217
  };
8157
9218
  registerCard("plugins.bundle.config", "dsh-connect-trae");
8158
9219
  registerCard("plugins.row.config", "dsh-connect-trae#dsh-connect-trae");
@@ -8183,21 +9244,18 @@ window.__ModuleLoader__.load({
8183
9244
  * deciding another's.
8184
9245
  */
8185
9246
  const registerPointsSlot = (slotName, id, order, position) => {
8186
- try {
8187
- ctx.slots.inject(slotName, () => ctx.slots.register({
8188
- name: slotName,
8189
- id,
8190
- order,
8191
- locale: namespace,
8192
- inject: () => ({
8193
- t,
8194
- settingsScope,
8195
- position
8196
- })
8197
- }, ComposerPointsGate));
8198
- } catch (error) {
8199
- console.error(`[dsh-connect-trae] points slot "${slotName}" failed to register (host provider unaffected):`, error);
8200
- }
9247
+ registerSlotEntry(ctx, `${slotName}#${id}`, slotName, () => ctx.slots.register({
9248
+ name: slotName,
9249
+ id,
9250
+ order,
9251
+ locale: namespace,
9252
+ inject: () => ({
9253
+ t,
9254
+ settingsScope,
9255
+ position,
9256
+ onOpenSettings
9257
+ })
9258
+ }, ComposerPointsGate), `points slot "${slotName}"`);
8201
9259
  };
8202
9260
  registerPointsSlot("conversation.input.right", "dsh-connect-trae-composer-points", 4, "composer");
8203
9261
  registerPointsSlot("conversation.composer.dock", "dsh-connect-trae-stats-points", 10, "stats");
@@ -8218,13 +9276,140 @@ window.__ModuleLoader__.load({
8218
9276
  * down with it.
8219
9277
  */
8220
9278
  const registerModelLabel = () => {
8221
- try {
8222
- return ctx.slots.inject("conversation.input.model.label", () => ctx.slots.register({ name: "conversation.input.model.label" }, TraeModelLabel));
8223
- } catch (error) {
8224
- console.error("[dsh-connect-trae] model label slot failed to register (host provider unaffected):", error);
8225
- }
9279
+ registerSlotEntry(ctx, "conversation.input.model.label", "conversation.input.model.label", () => ctx.slots.register({
9280
+ name: "conversation.input.model.label",
9281
+ priority: MODEL_LABEL_PRIORITY
9282
+ }, TraeModelLabel), "model label slot");
8226
9283
  };
8227
9284
  registerModelLabel();
9285
+ /**
9286
+ * The sidebar glyph for the optional panel row.
9287
+ *
9288
+ * The host renders `sidebar.panellist` registrants with `{ size, active }`
9289
+ * inside a `currentColor`-themed glyph cell, so the brand mark is drawn as a
9290
+ * small `<img>` — the PNG's own colours read as the plugin's identity, and
9291
+ * the row's selection state is the host's to paint around it.
9292
+ */
9293
+ function TraePanelIcon(props) {
9294
+ const edge = typeof props.size === "number" && props.size > 0 ? props.size : 16;
9295
+ return (0, react.createElement)("img", {
9296
+ className: "dsm-trae-panel-icon",
9297
+ src: TRAE_PLUGIN_ICON,
9298
+ alt: "",
9299
+ width: edge,
9300
+ height: edge,
9301
+ "aria-hidden": "true"
9302
+ });
9303
+ }
9304
+ /**
9305
+ * Back to the conversation, for the page's header.
9306
+ *
9307
+ * Soft-probed: `layout` is a host service this entry deliberately does not
9308
+ * declare in `inject` (an absent service keeps `apply` from running at all,
9309
+ * and the page is useful without the back affordance). `selectPanel(null)` is
9310
+ * the host's own way of handing the centre column back.
9311
+ */
9312
+ const backToConversation = () => {
9313
+ const layout = softGet("layout");
9314
+ if (layout !== void 0 && typeof layout.selectPanel === "function") layout.selectPanel(null);
9315
+ };
9316
+ /**
9317
+ * The Trae panel in the sidebar (`sidebar.panellist` row + `main` keyed
9318
+ * page), mirroring the sibling `dsh-connect-workbuddy` panel and the host's
9319
+ * own Plugins row.
9320
+ *
9321
+ * OPT-IN (`Config.sidebarPanel`, default false). Registering it is NOT the
9322
+ * same as showing it: the host's sidebar builds the nav row from the slot's
9323
+ * ENTRY METADATA, so a component returning `null` leaves a blank but still
9324
+ * clickable button rather than no button. The row therefore has to be
9325
+ * registered only while the switch is on, which is what
9326
+ * {@link syncPanelRegistration} owns. See `panel-switch.ts` for the store
9327
+ * that decision rides on.
9328
+ */
9329
+ const PANEL_ID = "dsh-connect-trae";
9330
+ const registerPanel = () => {
9331
+ registerSlotEntry(ctx, "sidebar.panellist#dsh-connect-trae", "sidebar.panellist", () => ctx.slots.register({
9332
+ name: "sidebar.panellist",
9333
+ id: PANEL_ID,
9334
+ order: 2,
9335
+ label: () => t("nav.panel"),
9336
+ locale: namespace
9337
+ }, TraePanelIcon), "sidebar panel row");
9338
+ registerSlotEntry(ctx, `main#${PANEL_ID}`, "main", () => ctx.slots.register({
9339
+ name: "main",
9340
+ key: PANEL_ID,
9341
+ locale: namespace,
9342
+ inject: () => ({
9343
+ t,
9344
+ settingsScope,
9345
+ surface: "page",
9346
+ backToConversation
9347
+ })
9348
+ }, TraeUsageCard), "sidebar panel page");
9349
+ return [() => {
9350
+ releaseSlotEntry("sidebar.panellist#dsh-connect-trae");
9351
+ }, () => {
9352
+ releaseSlotEntry(`main#${PANEL_ID}`);
9353
+ }];
9354
+ };
9355
+ /**
9356
+ * The live panel registrations, kept SEPARATE from the settings slots.
9357
+ *
9358
+ * They are the only slots in this plugin that come and go while the page is
9359
+ * open (the opt-in switch), so owning them apart means flipping the switch
9360
+ * does not dispose and re-create the card, the composer readout and the
9361
+ * model label — which would be a visible flicker for a change that has
9362
+ * nothing to do with them.
9363
+ */
9364
+ let panelDisposers = [];
9365
+ /**
9366
+ * Bring the panel registrations in line with the opt-in switch.
9367
+ *
9368
+ * Dispose FIRST, then register: a live `key`/`id` cannot be registered
9369
+ * twice (the ledger is keyed), and the throw would be swallowed into "no
9370
+ * entry at all".
9371
+ */
9372
+ const syncPanelRegistration = () => {
9373
+ if (panelDisposers.length > 0) {
9374
+ const layout = softGet("layout");
9375
+ try {
9376
+ if (layout?.panelInfo?.getSnapshot?.()?.activePanelId === PANEL_ID) layout.selectPanel?.(null);
9377
+ } catch {}
9378
+ }
9379
+ for (const dispose of panelDisposers) try {
9380
+ dispose();
9381
+ } catch {}
9382
+ panelDisposers = [];
9383
+ if (!sidebarPanelWanted()) return;
9384
+ panelDisposers = registerPanel();
9385
+ };
9386
+ /**
9387
+ * Follow the switch, and SEED it from the Host.
9388
+ *
9389
+ * The seed is a status read rather than the settings scope on purpose: this
9390
+ * namespace's browser mirror is documented as stale on the affected 0.1.7
9391
+ * deployments, and here a stale `false` would mean the row never appears —
9392
+ * with no later event to correct it, because the mirror reports namespace
9393
+ * edges, not value changes.
9394
+ *
9395
+ * A failed read is NOT an error to show: OFF is the documented default
9396
+ * answer, so a plugin whose status route is unreachable simply gets no row.
9397
+ *
9398
+ * The SUBSCRIPTION is what makes the switch live: the card calls
9399
+ * `setSidebarPanelWanted` after a verified write, so the row appears and
9400
+ * disappears without a reload. The host's layout service drops an
9401
+ * `activePanelId` whose `main` key has gone, so switching off while the page
9402
+ * is open returns the centre column to the conversation on its own.
9403
+ */
9404
+ ctx.effect(() => subscribeSidebarPanelWanted(syncPanelRegistration), "dsh-connect-trae: sidebar panel opt-in");
9405
+ syncPanelRegistration();
9406
+ (async () => {
9407
+ try {
9408
+ const response = await fetch(withTraeRegion(TRAE_USAGE_PATH, "cn"), { headers: { accept: "application/json" } });
9409
+ if (!response.ok) return;
9410
+ if ((await response.json()).sidebarPanelEnabled === true) setSidebarPanelWanted(true);
9411
+ } catch {}
9412
+ })();
8228
9413
  } catch (error) {
8229
9414
  console.error("[dsh-connect-trae] client card failed to load (host provider unaffected):", error);
8230
9415
  }