dsh-connect-trae 3.8.1 → 3.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -66,6 +66,50 @@ window.__ModuleLoader__.load({
66
66
  */
67
67
  const TRAE_CHECKIN_PATH = "/plugins/dsh-connect-trae/checkin";
68
68
  /**
69
+ * Plugin-owned account-pool endpoint: one route for the pool's batch actions.
70
+ *
71
+ * 参考:`dsh-connect-workbuddy` 的 `WORKBUDDY_POOL_PATH`。本路由与该实现同形
72
+ * (同一 `?action=` 风格、POST、loopback-only、per-row `accountId`),这样同时
73
+ * 装了两个插件的用户面对的是同一套交互。
74
+ *
75
+ * `?action=checkin` and `?action=test` run over the whole member set; `remove`
76
+ * addresses ONE account (the body carries `accountId`) and is the only way a
77
+ * measurement of an account the user no longer wants can leave the store — a
78
+ * deleted member otherwise keeps its row in the file forever, and a re-added
79
+ * member would silently inherit an old exclusion.
80
+ *
81
+ * POST + loopback-only because both batches SPEND something: check-in sends a
82
+ * claim, and a test is a real chat call. A page that is not the Host's own
83
+ * loopback must not be able to trigger either.
84
+ */
85
+ const TRAE_POOL_PATH = "/plugins/dsh-connect-trae/pool";
86
+ /** Query parameter selecting which pool batch action a request means. */
87
+ const TRAE_POOL_ACTION_PARAM = "action";
88
+ /** Query parameter naming ONE pool account a per-row request addresses. */
89
+ const TRAE_POOL_ACCOUNT_PARAM = "accountId";
90
+ /**
91
+ * Address one panel's pool route with its action.
92
+ *
93
+ * Kept as its own helper (rather than a `withTraeRegion` + hand-appended
94
+ * `&action=`) because a missing action parameter means "no action", not a
95
+ * default one — the typo has to be impossible rather than merely unlikely.
96
+ */
97
+ function withTraePoolAction(panel, action) {
98
+ return `${TRAE_POOL_PATH}?${TRAE_REGION_PARAM}=${panel}&${TRAE_POOL_ACTION_PARAM}=${action}`;
99
+ }
100
+ /**
101
+ * Address one panel's pool route for ONE account, e.g. a row's Test or Remove.
102
+ *
103
+ * Kept beside {@link withTraePoolAction} for the same reason that one exists: the
104
+ * account id is a 24-char hex string, and hand-appending `&accountId=` at a call
105
+ * site is one typo away from a request the route answers 400 to — or worse, from
106
+ * one that addresses the wrong action. Both parameters are therefore spelled by
107
+ * this module and nowhere else.
108
+ */
109
+ function withTraePoolAccount(panel, action, accountId) {
110
+ return `${withTraePoolAction(panel, action)}&${TRAE_POOL_ACCOUNT_PARAM}=${encodeURIComponent(accountId)}`;
111
+ }
112
+ /**
69
113
  * Plugin-owned account transfer endpoint: batch export and import of a region's
70
114
  * stored credentials (issue #37).
71
115
  *
@@ -276,6 +320,48 @@ window.__ModuleLoader__.load({
276
320
  });
277
321
  }
278
322
  /**
323
+ * One panel's saved pool PREFERENCES, as the card's settings block edits them.
324
+ *
325
+ * Read from the settings document rather than from the usage answer for the same
326
+ * reason `regionCreditPositionOf` is: the card must paint its controls correctly
327
+ * on the FIRST frame, before any fetch lands, and a control that starts on a
328
+ * default and jumps when data arrives is the "looks like it did nothing" defect.
329
+ *
330
+ * Absent reads as `{}` — every field of {@link TraePoolPreferences} is optional
331
+ * by design, and a profile written by an older build has no `pool` slot at all.
332
+ * The caller applies the defaults it means (OFF, automatic model, no members);
333
+ * this function never invents them, because `targetModelId: ''` and
334
+ * `targetModelId: undefined` mean the same thing and only the CALLER may decide
335
+ * to collapse them.
336
+ *
337
+ * `value` may be the whole settings section or the `regions` map (see
338
+ * {@link regionsMapOf}).
339
+ */
340
+ function regionPoolOf(value, panel) {
341
+ const unwrapped = unwrapVolatileDeep(regionSlotOf(value, panel)["pool"]);
342
+ return typeof unwrapped === "object" && unwrapped !== null && !Array.isArray(unwrapped) ? unwrapped : {};
343
+ }
344
+ /**
345
+ * Build the next `regions` settings value for one panel's pool preferences.
346
+ *
347
+ * Same merge rule as every sibling region write: only the addressed panel's
348
+ * `pool` slot changes, so saving the pool cannot drop that slot's model
349
+ * directory, selection, budgets, enabled flag or credit seat, and cannot touch
350
+ * the other panels. Panels are independent even where they share credentials
351
+ * (`cn` and `enterprise` bill the same Trae CN account): membership is a choice
352
+ * per panel, not per credential.
353
+ *
354
+ * `value` may be the whole settings section or the `regions` map; the RETURN
355
+ * value is always the `regions` map, i.e. exactly what `settingsScope.set(
356
+ * 'regions', ...)` needs.
357
+ */
358
+ function nextRegionPool(value, panel, pool) {
359
+ return nextRegionSlots(regionsMapOf(value), panel, {
360
+ ...regionSlotOf(value, panel),
361
+ pool
362
+ });
363
+ }
364
+ /**
279
365
  * Whether Trae's plan identity says this account is on a paying plan.
280
366
  *
281
367
  * `has_package` alone is NOT that question — it reports whether a package
@@ -312,9 +398,9 @@ window.__ModuleLoader__.load({
312
398
  .dsm-plugin-card-open{background:var(--dsw-alias-bg-layer-2,#25262b);border-color:var(--dsw-alias-label-dimmed,#777)}
313
399
  .dsm-plugin-card-header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:transparent;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}
314
400
  .dsm-plugin-card-header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:-2px}
315
- .dsm-plugin-card-head{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}
401
+ .dsm-plugin-card-head{flex-direction:column;flex:1;gap:6px;min-width:0;display:flex}
316
402
  .dsm-plugin-card-title{color:var(--dsw-alias-label-primary,#e6e6e6);font-size:15px;font-weight:600;line-height:1.4}
317
- .dsm-plugin-card-description{color:var(--dsw-alias-label-tertiary,#999);font-size:13px;line-height:1.5}
403
+ .dsm-plugin-card-description{color:var(--dsw-alias-label-tertiary,#999);font-size:12.5px;line-height:1.55}
318
404
  /* Pure-CSS caret: the host primitives' chevron icon names are not part of any
319
405
  stable contract across DSH releases, so no static import is used 鈥?a border
320
406
  caret in the plugin's own CSS is version-proof. */
@@ -336,18 +422,24 @@ window.__ModuleLoader__.load({
336
422
  .dsm-trae-mainui-switch-label input{margin:0;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
337
423
  .dsm-trae-mainui-switch-label:has(input:disabled){opacity:.6;cursor:default}
338
424
  .dsm-trae-usage{display:flex;flex-direction:column;gap:16px;margin:0;padding:16px 0 4px}
339
- .dsm-trae-tabs{display:flex;gap:6px;padding:4px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:10px;background:var(--dsw-alias-bg-layer-3,#2a2c33)}
340
- .dsm-trae-tab-cell{display:flex;align-items:center;gap:2px;flex:1;min-width:0}
341
- .dsm-trae-tab{appearance:none;font:inherit;cursor:pointer;flex:1;min-width:0;border:0;border-radius:7px;padding:7px 10px;color:var(--dsw-alias-label-tertiary,#999);font-size:13px;font-weight:500;line-height:18px;background:transparent;transition:color .15s,background .15s;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
425
+ .dsm-trae-tabs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
426
+ .dsm-trae-tab-cell{display:flex;align-items:center;gap:4px;min-width:0;padding:8px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:10px;background:var(--dsw-alias-bg-layer-3,#2a2c33);transition:border-color .15s,background .15s}
427
+ .dsm-trae-tab-cell:hover{border-color:var(--dsw-alias-label-dimmed,#777)}
428
+ /* The selected tab is marked on its CHIP, not on the button inside it: an
429
+ inset ring on an inner button, inside a bordered strip, drew two borders
430
+ a few pixels apart and read as a rendering mistake. The chip carries the
431
+ accent border, so the switch beside the label stays in the same unit. */
432
+ .dsm-trae-tab-cell:has(.dsm-trae-tab-active){border-color:var(--dsw-alias-brand-primary,#5686fe);background:var(--dsw-alias-bg-layer-2,#232529)}
433
+ .dsm-trae-tab{appearance:none;font:inherit;cursor:pointer;flex:1;min-width:0;border:0;border-radius:7px;padding:0;color:var(--dsw-alias-label-tertiary,#999);font-size:13px;font-weight:600;line-height:20px;background:transparent;text-align:left;transition:color .15s;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
342
434
  .dsm-trae-tab:hover:not(:disabled):not(.dsm-trae-tab-active){color:var(--dsw-alias-label-primary,#e6e6e6)}
343
435
  .dsm-trae-tab:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
344
- .dsm-trae-tab-active{color:var(--dsw-alias-label-primary,#e6e6e6);background:var(--dsw-alias-bg-layer-2,#232529);box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l2,#3a3d45)}
436
+ .dsm-trae-tab-active{color:var(--dsw-alias-label-primary,#e6e6e6)}
345
437
  .dsm-trae-tab-off{opacity:.55}
346
438
  /* Same checkbox as the settings block (.dsm-trae-switch); only the padding
347
439
  differs, so it sits inside the tab cell without crowding the label. */
348
- .dsm-trae-tab-switch{flex:none;padding:0 8px 0 2px}
349
- .dsm-trae-tab-dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:6px;vertical-align:baseline}
350
- .dsm-trae-tab-hint{margin:0;color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px}
440
+ .dsm-trae-tab-switch{flex:none;margin-left:auto;padding-left:10px;border-left:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.24))}
441
+ .dsm-trae-tab-dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:7px;vertical-align:baseline}
442
+ .dsm-trae-tab-hint{margin:0;padding:0 2px;color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px}
351
443
  .dsm-trae-credits-unavailable{margin:10px 0 0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:10px;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px;line-height:18px;background:var(--dsw-alias-bg-layer-3,#2a2c33)}
352
444
  .dsm-trae-account-value-muted{color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px}
353
445
  .dsm-trae-tab-off-notice{margin:0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:10px;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px;line-height:18px;background:var(--dsw-alias-bg-layer-3,#2a2c33)}
@@ -373,13 +465,27 @@ window.__ModuleLoader__.load({
373
465
  * 銆屼繚瀛樸€?銆屾斁寮冦€?footer. That is gone with the draft it existed to serve 鈥?this
374
466
  * is the sibling dsh-connect-workbuddy card's shape, where a preference is a
375
467
  * checkbox on a line that commits on the click. */
376
- .dsm-trae-prefs{display:flex;flex-direction:column;gap:8px}
377
- .dsm-trae-pref{display:flex;align-items:flex-start;gap:8px;font-size:13px;line-height:18px;color:var(--dsw-alias-label-secondary,#c6c9d0);cursor:pointer}
378
- .dsm-trae-pref input{flex:none;margin:2px 0 0;cursor:pointer;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
468
+ .dsm-trae-prefs{display:flex;flex-direction:column;gap:14px;padding:2px 2px 0}
469
+ .dsm-trae-pref{display:flex;align-items:flex-start;gap:10px;font-size:13px;line-height:18px;color:var(--dsw-alias-label-secondary,#c6c9d0);cursor:pointer}
470
+ .dsm-trae-pref input{flex:none;cursor:pointer;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
471
+ .dsm-trae-pref-check{inline-size:15px;block-size:15px;margin:3px 0 0}
472
+ /* The label and its explanation used to run together as one wrapped blob
473
+ ("跟随 Trae 当前登录的账号 在 Trae 桌面端切换登录后…"), which is what made
474
+ this block read as a wall of text. They are two lines now: what the switch
475
+ does, then what it means. */
476
+ .dsm-trae-pref-copy{display:flex;flex-direction:column;gap:3px;min-width:0}
477
+ .dsm-trae-pref-label{font-weight:500;color:var(--dsw-alias-label-primary,#e6e6e6)}
478
+ /* The select sits NEXT TO its label ("显示位置 [输入框工具栏 ▾]"), not at the far
479
+ right of the card. Pushing it to the opposite edge split one sentence into
480
+ two objects with half a card of empty space between them, and left the reader
481
+ to guess which label the control belonged to. The row packs from the left;
482
+ nothing in it stretches. */
483
+ .dsm-trae-pref-lead{display:flex;align-items:center;justify-content:flex-start;gap:10px;min-width:0}
484
+ .dsm-trae-pref-lead .dsm-trae-pref-label{flex:none}
379
485
  .dsm-trae-pref input:disabled{cursor:default}
380
486
  .dsm-trae-pref:has(input:disabled){opacity:.6;cursor:default}
381
487
  .dsm-trae-pref b{font-weight:500;color:var(--dsw-alias-label-primary,#e6e6e6)}
382
- .dsm-trae-pref-hint{color:var(--dsw-alias-label-tertiary,#9aa0a8)}
488
+ .dsm-trae-pref-hint{color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:12px;line-height:17px}
383
489
  .dsm-trae-pref span{min-width:0}
384
490
  /* The credit readout's seat picker (learned from workbuddy #62). Its OWN label
385
491
  class, deliberately not the plain .dsm-trae-pref the two checkboxes use: a
@@ -388,8 +494,13 @@ window.__ModuleLoader__.load({
388
494
  would otherwise start asserting about a <select>.
389
495
  The select keeps its own class for the same reason, and takes the checkbox
390
496
  column's width so it reads as a sibling of the box rather than a stray row. */
391
- .dsm-trae-credit-position-pref{align-items:flex-start}
392
- .dsm-trae-credit-position{flex:none;max-width:150px;margin-top:-1px;padding:2px 6px;font-size:12px}
497
+ /* The select row has no checkbox, so it borrows the checkbox's column as
498
+ padding — its label must start on the same left edge as the two labels
499
+ above it (15px box + 10px gap). */
500
+ .dsm-trae-credit-position-pref{align-items:flex-start;padding-left:25px}
501
+ /* Sized by its own two options, not stretched: the control answers one short
502
+ question and sits beside the words that ask it. */
503
+ .dsm-trae-credit-position{flex:none;max-width:none;margin-top:0;padding:3px 8px;font-size:12px;line-height:16px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:7px;background:var(--dsw-alias-bg-layer-3,#2a2c33);color:var(--dsw-alias-label-primary,#e6e6e6);cursor:pointer}
393
504
  .dsm-trae-credit-position:disabled{cursor:default}
394
505
  /* The card's checkboxes are the platform's own, styled only with
395
506
  accent-color 鈥?the SAME idiom .dsm-trae-mainui-switch-label above already
@@ -406,6 +517,16 @@ window.__ModuleLoader__.load({
406
517
  .dsm-trae-switch input{margin:0;cursor:pointer;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
407
518
  .dsm-trae-switch input:disabled{cursor:default}
408
519
  .dsm-trae-switch:has(input:disabled){opacity:.6;cursor:default}
520
+ /* A pill, only where a pill is the idiom: the standalone preference rows.
521
+ appearance:none hands the geometry to the stylesheet, so the track and the
522
+ knob are ours. It is scoped rather than global because the tab row's switch
523
+ sits inside a chip — a pill there reads as a second button, which is exactly
524
+ how it looked when this was global. */
525
+ .dsm-trae-switch-pill{display:inline-flex;align-items:center}
526
+ .dsm-trae-switch-pill input{appearance:none;-webkit-appearance:none;position:relative;inline-size:36px;block-size:20px;border:0;border-radius:999px;background:var(--dsw-alias-border-l2,#c4c7cc);transition:background-color .15s ease}
527
+ .dsm-trae-switch-pill input::after{content:"";position:absolute;inset-block-start:2px;inset-inline-start:2px;inline-size:16px;block-size:16px;border-radius:50%;background:var(--dsw-alias-bg-layer-3,#fff);box-shadow:0 1px 2px rgba(0,0,0,.24);transition:transform .15s ease}
528
+ .dsm-trae-switch-pill input:checked{background:var(--dsw-alias-brand-primary,#1b1c1e)}
529
+ .dsm-trae-switch-pill input:checked::after{transform:translateX(16px)}
409
530
  /* The account block is a real table: the question it answers is "which of these
410
531
  accounts, and what is the action on each one" 鈥?a dropdown can only ever show
411
532
  the one already chosen. */
@@ -426,7 +547,7 @@ window.__ModuleLoader__.load({
426
547
  content. That is the whole point 鈥?the previous four-column table let each
427
548
  column grow with its own text, so the columns did not line up from row to row
428
549
  and the table read as noise in a narrow panel. */
429
- .dsm-trae-account-table{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:12px;overflow:hidden;background:var(--dsw-alias-bg-layer-2,#24262c)}
550
+ .dsm-trae-account-table{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:var(--dsm-trae-pool-radius);overflow:hidden;background:var(--dsw-alias-bg-layer-2,#24262c)}
430
551
  /* Four tracks where the region has a check-in column, three where it does not
431
552
  (the international region has no check-in at all: the ug/* family answers
432
553
  404 on every ai gateway, so the column is absent rather than empty and
@@ -466,6 +587,7 @@ window.__ModuleLoader__.load({
466
587
  .dsm-trae-account-checkin{appearance:none;font:inherit;cursor:pointer;white-space:nowrap;border:1px solid rgba(86,134,254,.4);border-radius:7px;padding:2px 9px;font-size:12px;line-height:18px;font-weight:500;color:var(--dsw-alias-brand-primary,#5686fe);background:rgba(86,134,254,.08)}
467
588
  .dsm-trae-account-checkin:hover:not(:disabled){background:rgba(86,134,254,.16)}
468
589
  .dsm-trae-account-checkin:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
590
+ .dsm-trae-account-checkin-state{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,#9aa0a8);white-space:nowrap}
469
591
  /* A disabled check-in still STATES something ("claimed", "device used"), so it
470
592
  stays readable 鈥?clearly inactive, not invisible. */
471
593
  .dsm-trae-account-checkin:disabled{opacity:.55;cursor:default;border-color:transparent;color:var(--dsw-alias-label-tertiary,#9aa0a8);background:transparent}
@@ -537,6 +659,8 @@ window.__ModuleLoader__.load({
537
659
  must stay mounted to be clicked. */
538
660
  .dsm-trae-transfer{margin-top:12px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l2,rgba(127,127,127,.2))}
539
661
  .dsm-trae-transfer-label{margin:0 0 6px;font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary,GrayText)}
662
+ .dsm-trae-transfer-flat{margin-top:0;padding-top:0;border-top:0}
663
+ .dsm-trae-transfer-flat .dsm-trae-transfer-actions{flex-wrap:nowrap}
540
664
  .dsm-trae-transfer-actions{display:flex;flex-wrap:wrap;gap:8px}
541
665
  .dsm-trae-transfer-file{display:none}
542
666
  .dsm-trae-transfer-status{margin:6px 0 0;font-size:11px;line-height:16px}
@@ -641,6 +765,161 @@ window.__ModuleLoader__.load({
641
765
  @keyframes dsm-trae-veil-spin{to{transform:rotate(360deg)}}
642
766
  @keyframes dsm-trae-veil-dash{0%{stroke-dasharray:12 150;stroke-dashoffset:0}50%{stroke-dasharray:24 150;stroke-dashoffset:-6}to{stroke-dasharray:12 150;stroke-dashoffset:0}}
643
767
  @media (prefers-reduced-motion: reduce){.dsm-trae-veil-spinner{animation:none}.dsm-trae-veil-spinner-arc{animation:none;stroke-dasharray:18 150;stroke-dashoffset:-3}}
768
+
769
+ /* ------------------------------------------------------------------ *
770
+ * The account pool (3.9.0). *
771
+ * *
772
+ * Five tracks, because the table answers "which accounts, and what is *
773
+ * each one worth right now" — a single picker could only ever show the *
774
+ * one already chosen, and every field here is a reason to include or *
775
+ * exclude a row. The check-in track is CONDITIONAL (only the CN panel *
776
+ * grants a daily reward), so the actions cell is pinned to the LAST *
777
+ * track (grid-column:-2/-1) rather than to the fifth one: otherwise *
778
+ * a four-cell row would drop Test/Remove into the check-in slot and *
779
+ * leave a hole on the right. *
780
+ * ------------------------------------------------------------------ */
781
+ .dsm-trae-pool{--dsm-trae-pool-pad-x:14px;--dsm-trae-pool-pad-y:10px;--dsm-trae-pool-radius:14px;display:flex;flex-direction:column;gap:12px;border-top:1px solid var(--dsw-alias-border-l2,#36373b);padding:14px 0 0}
782
+ .dsm-trae-pool-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
783
+ .dsm-trae-pool-title{margin:0;font-size:13px;font-weight:600;line-height:18px;color:var(--dsw-alias-label-primary,#e6e6e6)}
784
+ .dsm-trae-pool-region-note{margin:3px 0 0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
785
+ .dsm-trae-pool-current{flex:none;padding:2px 8px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary,#c6c9d0);white-space:nowrap}
786
+ .dsm-trae-pool-actions{display:flex;align-items:center;flex-wrap:wrap;gap:8px}
787
+ .dsm-trae-pool-action-checkin{flex:none;white-space:nowrap}
788
+ .dsm-trae-pool-action-test{flex:none;white-space:nowrap}
789
+ /* Removing an account from the pool is destructive (it drops THIS panel's saved
790
+ membership), so the hover state takes the error tone rather than the neutral
791
+ one every other outline button gets. */
792
+ .dsm-trae-pool-remove{color:var(--dsw-alias-label-secondary,#c6c9d0)}
793
+ .dsm-trae-pool-remove:hover:not(:disabled){color:var(--dsw-alias-state-error-primary,#d92d20);border-color:var(--dsw-alias-state-error-primary,#d92d20)}
794
+ .dsm-trae-pool-busy{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
795
+ .dsm-trae-pool-action-note{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
796
+ .dsm-trae-pool-card{display:flex;flex-direction:column;min-width:0;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:var(--dsm-trae-pool-radius);background:var(--dsw-alias-bg-layer-2,#24262c);overflow:hidden}
797
+ .dsm-trae-pool-card-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px var(--dsm-trae-pool-pad-x) 12px}
798
+ .dsm-trae-pool-card-title{margin:0;font-size:13px;line-height:18px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6)}
799
+ .dsm-trae-pool-card-tools{display:flex;align-items:center;gap:8px;flex:none}
800
+ .dsm-trae-pool-count{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
801
+ .dsm-trae-pool-card .dsm-trae-pool-table{border:0;border-radius:0;border-top:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.18))}
802
+ .dsm-trae-pool-members{display:flex;flex-direction:column;gap:10px}
803
+ /* Prose, not a box. Every explanation in this section is one line of muted
804
+ text; boxing each one is what made the block look like a stack of cards. */
805
+ .dsm-trae-pool-actions-end{margin-inline-start:auto}
806
+ .dsm-trae-pool-credit-meta{display:block;margin-top:2px;font-size:11px;line-height:16px;white-space:nowrap;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
807
+ .dsm-trae-pool-account-note{grid-column:2;grid-row:3;font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
808
+ .dsm-trae-pool-tools-dashed{border:1px dashed var(--dsw-alias-border-l2,#3a3d45);border-radius:var(--dsm-trae-pool-radius);padding:var(--dsm-trae-pool-pad-y) var(--dsm-trae-pool-pad-x)}
809
+ .dsm-trae-pool-tool-label{margin-inline-end:auto;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary,#c6c9d0)}
810
+ .dsm-trae-pool-note{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
811
+ /* A setting row: label + hint on the left, the control on the right, one
812
+ rhythm for the switch, the target select and the manual binding. */
813
+ .dsm-trae-pool-set{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:var(--dsm-trae-pool-pad-y) var(--dsm-trae-pool-pad-x);border-top:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.18))}
814
+ .dsm-trae-pool-set:first-child{border-top:0}
815
+ .dsm-trae-pool-set-copy{display:flex;flex-direction:column;gap:3px;min-width:0}
816
+ .dsm-trae-pool-set-copy b{font-size:13px;line-height:18px;font-weight:500;color:var(--dsw-alias-label-primary,#e6e6e6)}
817
+ .dsm-trae-pool-set-copy span{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
818
+ .dsm-trae-pool-set-ctl{display:flex;align-items:center;gap:8px;flex:none}
819
+ .dsm-trae-pool-set-ctl select{min-width:180px}
820
+ .dsm-trae-pool-bind{border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:var(--dsm-trae-pool-radius);background:var(--dsw-alias-bg-layer-2,#24262c)}
821
+ .dsm-trae-pool-account-select{min-width:180px}
822
+ .dsm-trae-pool-tools{display:flex;flex-direction:column;gap:8px}
823
+ .dsm-trae-pool-tool-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
824
+ .dsm-trae-pool-meta{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary,#9aa0a8);white-space:nowrap}
825
+ .dsm-trae-pool-warn-tag{padding:0 6px;border-radius:999px;font-size:11px;line-height:16px;white-space:nowrap}
826
+ .dsm-trae-pool-warn-tag-soon{color:var(--dsw-alias-state-warn-primary,#b45309)}
827
+ .dsm-trae-pool-warn-tag-lapsed{color:var(--dsw-alias-state-error-primary,#d92d20)}
828
+ .dsm-trae-pool-claim{padding:2px 8px;font-size:12px;line-height:16px}
829
+ .dsm-trae-pool-settings-wrap{display:flex;flex-direction:column;gap:10px}
830
+
831
+ .dsm-trae-pool-settings{display:flex;flex-direction:column;gap:0;padding:0;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:var(--dsm-trae-pool-radius);overflow:hidden;background:var(--dsw-alias-bg-layer-2,#24262c)}
832
+ .dsm-trae-pool-members-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
833
+ .dsm-trae-pool-settings-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
834
+ .dsm-trae-pool-subtitle{margin:0;font-size:12px;font-weight:600;line-height:18px;color:var(--dsw-alias-label-primary,#e6e6e6)}
835
+ .dsm-trae-pool-subnote{margin:2px 0 0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
836
+ .dsm-trae-pool-select-actions{display:flex;flex:none;gap:6px}
837
+ .dsm-trae-pool-select-actions .dsm-btn,.dsm-trae-pool-row-actions .dsm-btn{padding:4px 10px;font-size:12px;line-height:1.4}
838
+ .dsm-trae-pool-table{--dsm-trae-pool-cols:minmax(0,1.4fr) 128px minmax(0,1.2fr) 96px 128px;display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:var(--dsm-trae-pool-radius);overflow:hidden;background:var(--dsw-alias-bg-layer-2,#24262c)}
839
+ .dsm-trae-pool-table-no-checkin{--dsm-trae-pool-cols:minmax(0,1.4fr) 128px minmax(0,1.2fr) 128px}
840
+ .dsm-trae-pool-row{display:grid;grid-template-columns:var(--dsm-trae-pool-cols);align-items:start;gap:10px;padding:var(--dsm-trae-pool-pad-y) var(--dsm-trae-pool-pad-x);border-top:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.18))}
841
+ .dsm-trae-pool-row:first-child{border-top:0}
842
+ /* The header carries labels for the first three tracks only: the check-in and
843
+ action cells are self-describing ("Not checked in", "Test"), and a header word
844
+ for an empty cell would be a promise the table does not keep. */
845
+ .dsm-trae-pool-col-account{padding-inline-start:23px}
846
+ .dsm-trae-pool-row-head{align-items:center;padding:8px var(--dsm-trae-pool-pad-x);border-top:0;background:var(--dsw-alias-bg-layer-3,#2a2c33);font-size:11px;line-height:16px;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
847
+ /* The row that would bill the next request. Tinted, not bordered: a border here
848
+ reads as a selection control, and the checkbox beside it already is one. */
849
+ .dsm-trae-pool-row-current{background:rgba(86,134,254,.07);box-shadow:inset 3px 0 0 var(--dsw-alias-brand-primary,#5686fe)}
850
+ .dsm-trae-pool-row-ghost{opacity:.7}
851
+ .dsm-trae-pool-row-actions{display:flex;flex-wrap:wrap;gap:6px;justify-content:flex-end}
852
+ .dsm-trae-pool-account{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:center;gap:2px 8px;min-width:0}
853
+ .dsm-trae-pool-check{display:inline-flex;align-items:center;flex:none;grid-column:1;grid-row:1;cursor:pointer}
854
+ .dsm-trae-pool-check input{flex:none;margin:0;cursor:pointer;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
855
+ .dsm-trae-pool-check:has(input:disabled){opacity:.6;cursor:default}
856
+ .dsm-trae-pool-account-name{display:flex;align-items:center;gap:6px;min-width:0;grid-column:2;grid-row:1;font-size:13px;line-height:18px;color:var(--dsw-alias-label-primary,#e6e6e6);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
857
+ .dsm-trae-pool-dot{flex:none;width:7px;height:7px;border-radius:50%;background:var(--dsw-alias-label-dimmed,#777)}
858
+ .dsm-trae-pool-dot-ok{background:var(--dsw-alias-state-success-primary,#12b76a)}
859
+ .dsm-trae-pool-dot-excluded{background:var(--dsw-alias-state-warn-primary,#b45309)}
860
+ .dsm-trae-pool-dot-ghost{background:var(--dsw-alias-label-dimmed,#777)}
861
+ .dsm-trae-pool-account-tags{display:flex;flex-wrap:wrap;align-items:center;gap:6px;grid-column:2;grid-row:2;min-width:0}
862
+ .dsm-trae-pool-tag{padding:0 6px;border-radius:999px;font-size:10px;line-height:16px;white-space:nowrap}
863
+ .dsm-trae-pool-member{font-size:11px;line-height:16px;color:var(--dsw-alias-brand-primary,#5686fe)}
864
+ .dsm-trae-pool-not-member{font-size:11px;line-height:16px;color:var(--dsw-alias-label-dimmed,#9aa0a6)}
865
+ .dsm-trae-pool-tag-current{padding:1px 6px;border-radius:999px;background:rgba(86,134,254,.16);color:var(--dsw-alias-brand-primary,#5686fe);font-size:11px;font-weight:600;line-height:16px}
866
+ .dsm-trae-pool-tag-member{border:1px solid var(--dsw-alias-border-l2,#3a3d45);color:var(--dsw-alias-label-tertiary,#9aa0a8)}
867
+ .dsm-trae-pool-tag-excluded{background:var(--dsw-alias-state-warn-tertiary,#fef3c7);color:var(--dsw-alias-state-warn-primary,#b45309)}
868
+ .dsm-trae-pool-tag-ghost{border:1px dashed var(--dsw-alias-border-l2,#3a3d45);color:var(--dsw-alias-label-tertiary,#9aa0a8)}
869
+ .dsm-trae-pool-credit{display:flex;flex-direction:column;gap:1px;min-width:0}
870
+ .dsm-trae-pool-credit-num{font-size:13px;line-height:18px;color:var(--dsw-alias-label-primary,#e6e6e6);font-variant-numeric:tabular-nums}
871
+ .dsm-trae-pool-credit-label{font-size:10px;line-height:14px;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
872
+ .dsm-trae-pool-probe{min-width:0}
873
+ .dsm-trae-pool-probe-cell{display:flex;flex-direction:column;gap:1px;min-width:0}
874
+ .dsm-trae-pool-probe-outcome{display:flex;align-items:center;gap:5px;font-size:12px;line-height:16px;color:var(--dsw-alias-label-secondary,#c6c9d0)}
875
+ .dsm-trae-pool-probe-ok{color:var(--dsw-alias-state-success-primary,#12b76a)}
876
+ .dsm-trae-pool-probe-warn{color:var(--dsw-alias-state-warn-primary,#b45309)}
877
+ .dsm-trae-pool-probe-meta{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary,#9aa0a8);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
878
+ .dsm-trae-pool-probe-detail{font-size:11px;line-height:15px;color:var(--dsw-alias-label-tertiary,#9aa0a8);overflow-wrap:anywhere}
879
+ .dsm-trae-pool-probe-none{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
880
+ .dsm-trae-pool-checkin{min-width:0}
881
+ .dsm-trae-pool-checkin-state{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
882
+ .dsm-trae-pool-checkin-ok{color:var(--dsw-alias-state-success-primary,#12b76a)}
883
+ .dsm-trae-pool-checkin-no{color:var(--dsw-alias-state-warn-primary,#b45309)}
884
+ /* "Unknown" and "device already claimed" are facts, not faults: a muted line
885
+ says so without dressing either one up as an error. */
886
+ .dsm-trae-pool-checkin-unknown{color:var(--dsw-alias-label-tertiary,#9aa0a8)}
887
+ .dsm-trae-pool-checkin-device{color:var(--dsw-alias-label-tertiary,#9aa0a8)}
888
+ /* The advisory lines share one shape (a quiet bordered box): each is a fact the
889
+ user may need to act on, none of them is an error, and five different
890
+ rectangles in one section would read as five different severities. */
891
+ .dsm-trae-pool-ghost-note{margin:0;padding:8px var(--dsm-trae-pool-pad-x);border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:10px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary,#c6c9d0);background:var(--dsw-alias-bg-layer-3,#2a2c33)}
892
+ .dsm-trae-pool-unsaved{margin:0;padding:8px var(--dsm-trae-pool-pad-x);border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:10px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary,#c6c9d0);background:var(--dsw-alias-bg-layer-3,#2a2c33)}
893
+ .dsm-trae-pool-off-notice{margin:0;padding:0 var(--dsm-trae-pool-pad-x) 10px;font-size:12px;line-height:18px;color:var(--dsw-alias-state-warn-primary,#b45309)}
894
+ .dsm-trae-pool-target-none{margin:0;padding:0 12px 10px;font-size:12px;line-height:18px;color:var(--dsw-alias-state-warn-primary,#b45309)}
895
+ .dsm-trae-pool-target-stale{display:flex;align-items:center;justify-content:space-between;gap:10px;margin:0 var(--dsm-trae-pool-pad-x);padding-bottom:10px;color:var(--dsw-alias-state-warn-primary,#b45309)}
896
+ .dsm-trae-pool-warning{display:flex;flex-direction:column;gap:2px;padding:8px var(--dsm-trae-pool-pad-x);border:1px solid var(--dsw-alias-state-warn-secondary,#fbbf24);border-radius:10px;background:var(--dsw-alias-state-warn-tertiary,#fef3c7);color:var(--dsw-alias-state-warn-primary,#b45309);font-size:12px;line-height:18px}
897
+ .dsm-trae-pool-warning-title{font-weight:600}
898
+ .dsm-trae-pool-warning-hint{opacity:.85}
899
+ /* "Empty" is an unfinished setup, not a fault: neutral tone. */
900
+ .dsm-trae-pool-warning-empty{border-color:var(--dsw-alias-border-l2,#3a3d45);background:var(--dsw-alias-bg-layer-3,#2a2c33);color:var(--dsw-alias-label-secondary,#c6c9d0)}
901
+ /* "Every member is unusable" is the one case that IS the pool's fault, so it
902
+ keeps the warn tone the base class sets. */
903
+ .dsm-trae-pool-warning-unusable{border-color:var(--dsw-alias-state-warn-secondary,#fbbf24);background:var(--dsw-alias-state-warn-tertiary,#fef3c7);color:var(--dsw-alias-state-warn-primary,#b45309)}
904
+ .dsm-trae-pool-save-row{display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--dsw-alias-border-l2,#36373b);padding-top:12px}
905
+ .dsm-trae-pool-save-buttons{display:flex;align-items:center;justify-content:flex-end;gap:8px}
906
+ .dsm-trae-pool-save-row .dsm-btn{padding:5px 14px;font-size:13px;line-height:1.5}
907
+ .dsm-trae-pool-dirty{padding:0 6px;border-radius:999px;background:var(--dsw-alias-state-warn-tertiary,#fef3c7);color:var(--dsw-alias-state-warn-primary,#b45309);font-size:10px;line-height:16px}
908
+ .dsm-trae-pool-save-error{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-state-error-primary,#d92d20)}
909
+ .dsm-trae-pool-target{display:flex;flex-direction:column;gap:6px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary,#c6c9d0)}
910
+ .dsm-trae-pool-target-label{color:var(--dsw-alias-label-secondary,#c6c9d0)}
911
+ .dsm-trae-select{max-width:220px;padding:2px 6px;font:inherit;font-size:12px;color:var(--dsw-alias-label-primary,#e6e6e6);background:var(--dsw-alias-bg-layer-3,#2a2c33);border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:8px}
912
+ .dsm-trae-select:disabled{opacity:.6;cursor:default}
913
+ .dsm-trae-pool-log{display:flex;flex-direction:column;gap:6px;padding-top:2px}
914
+ .dsm-trae-pool-log-head{display:flex;align-items:center;justify-content:space-between;gap:10px}
915
+ .dsm-trae-pool-log-empty{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
916
+ .dsm-trae-pool-log-list{display:flex;flex-direction:column;gap:3px;margin:0;padding:0;list-style:none;max-height:160px;overflow-y:auto}
917
+ .dsm-trae-pool-log-item{padding:0 0 0 8px;border-left:2px solid var(--dsw-alias-border-l2,#3a3d45);font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary,#c6c9d0)}
918
+ .dsm-trae-pool-log-info{border-left-color:var(--dsw-alias-border-l2,#3a3d45)}
919
+ .dsm-trae-pool-log-ok{border-left-color:var(--dsw-alias-state-success-primary,#12b76a)}
920
+ .dsm-trae-pool-log-warn{border-left-color:var(--dsw-alias-state-warn-primary,#b45309)}
921
+ .dsm-trae-pool-log-error{border-left-color:var(--dsw-alias-state-error-primary,#d92d20)}
922
+ .dsm-trae-pool-log-text{overflow-wrap:anywhere}
644
923
  `;
645
924
  /**
646
925
  * Composer-row readout styles, injected by the readout itself.
@@ -860,6 +1139,1129 @@ window.__ModuleLoader__.load({
860
1139
  });
861
1140
  }
862
1141
  //#endregion
1142
+ //#region src/client/AccountPool.tsx
1143
+ /**
1144
+ * The account-pool card UI for Trae, ported from the WorkBuddy sibling.
1145
+ *
1146
+ * Mounted by {@link TraeUsageCard} inside the signed-in branch, below the
1147
+ * account table, rendered only when the usage document carries `pool` (the Host
1148
+ * omits it when the panel is unconfigured).
1149
+ *
1150
+ * This half of the feature is deliberately dumb about rules: every verdict an
1151
+ * account needs — credits, exclusion, the in-pool flag, the serving row — comes
1152
+ * pre-derived by the Host (`TraeWebPoolAccount`), so the card never re-ranks on
1153
+ * its own idea of "which account should bill". It drafts the saved preferences
1154
+ * (`TraePoolPreferences`) and writes them back through the card's verified
1155
+ * regions-write helper.
1156
+ */
1157
+ /**
1158
+ * A finite number or the fallback.
1159
+ *
1160
+ * The usage document is served by a Host that may be OLDER than this bundle (the
1161
+ * card ships in the client bundle and updates on refresh, the Host only on
1162
+ * restart), so a field the Host does not send yet must degrade to a usable default
1163
+ * instead of rendering `undefined` into a select.
1164
+ */
1165
+ function numberOr(value, fallback) {
1166
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
1167
+ }
1168
+ /**
1169
+ * Probe sizes the card offers, in tokens. `0` is the minimal one-word probe.
1170
+ *
1171
+ * Deliberately a short, coarse list: the number is a spend decision (a probe at
1172
+ * 50k tokens costs real credit on every account in the batch), and offering a
1173
+ * free-text field invites values the upstream rounds away anyway.
1174
+ */
1175
+ const PROBE_INPUT_TOKEN_CHOICES = [
1176
+ 0,
1177
+ 1e4,
1178
+ 2e4,
1179
+ 3e4,
1180
+ 5e4,
1181
+ 1e5
1182
+ ];
1183
+ /** Compare two arrays of ids order-insensitively. */
1184
+ function sameIds(a, b) {
1185
+ const lhs = [...a ?? []].sort();
1186
+ const rhs = [...b ?? []].sort();
1187
+ return lhs.length === rhs.length && lhs.every((v, i) => v === rhs[i]);
1188
+ }
1189
+ function formatNumber$1(value) {
1190
+ return new Intl.NumberFormat(void 0, { maximumFractionDigits: 1 }).format(value);
1191
+ }
1192
+ /** `MM/DD, HH:mm` — the absolute stamp a row shows under its age. */
1193
+ function shortWhen(atMs) {
1194
+ const at = new Date(atMs);
1195
+ const pad = (value) => value < 10 ? `0${value}` : String(value);
1196
+ return `${pad(at.getMonth() + 1)}/${pad(at.getDate())}, ${pad(at.getHours())}:${pad(at.getMinutes())}`;
1197
+ }
1198
+ /** `MM/DD`, for a line that has room for a date but not a clock time. */
1199
+ function shortDate(atMs) {
1200
+ const at = new Date(atMs);
1201
+ const pad = (value) => value < 10 ? `0${value}` : String(value);
1202
+ return `${pad(at.getMonth() + 1)}/${pad(at.getDate())}`;
1203
+ }
1204
+ function relativeAt(atMs, now, t) {
1205
+ const mins = Math.max(0, Math.round((now - atMs) / 6e4));
1206
+ if (mins < 1) return t("row.poolAgoNow");
1207
+ if (mins < 60) return t("row.poolAgoMin", { n: mins });
1208
+ const hours = Math.round(mins / 60);
1209
+ if (hours < 24) return t("row.poolAgoHour", { n: hours });
1210
+ return t("row.poolAgoDay", { n: Math.round(hours / 24) });
1211
+ }
1212
+ /** A human label for every value of {@link TraeWebPoolOutcome}. */
1213
+ function outcomeLabel(outcome, t) {
1214
+ switch (outcome) {
1215
+ case "ok": return t("row.poolProbeOk");
1216
+ case "rate-limited": return t("row.poolProbeRateLimited");
1217
+ case "model-busy": return t("row.poolProbeModelBusy");
1218
+ case "out-of-credit": return t("row.poolProbeOutOfCredit");
1219
+ case "credential-rejected": return t("row.poolProbeCredentialRejected");
1220
+ case "policy-rejected": return t("row.poolProbePolicyRejected");
1221
+ case "plan-gated": return t("row.poolProbePlanGated");
1222
+ case "unavailable": return t("row.poolProbeUnavailable");
1223
+ case "not-found": return t("row.poolProbeNotFound");
1224
+ case "failed": return t("row.poolProbeFailed");
1225
+ }
1226
+ }
1227
+ /** A human label for every value of {@link TraeWebPoolExclusion}. */
1228
+ function exclusionLabel(exclusion, t) {
1229
+ switch (exclusion) {
1230
+ case "unusable": return t("row.poolExcludedUnusable");
1231
+ case "rate-limited": return t("row.poolExcludedRateLimited");
1232
+ case "model-busy": return t("row.poolExcludedModelBusy");
1233
+ case "out-of-credit": return t("row.poolExcludedOutOfCredit");
1234
+ case "credential-rejected": return t("row.poolExcludedCredentialRejected");
1235
+ case "policy-rejected": return t("row.poolExcludedPolicyRejected");
1236
+ case "plan-gated": return t("row.poolExcludedPlanGated");
1237
+ case "not-found": return t("row.poolExcludedNotFound");
1238
+ case "unavailable": return t("row.poolExcludedUnavailable");
1239
+ case "failed": return t("row.poolExcludedFailed");
1240
+ }
1241
+ }
1242
+ function probeSourceLabel(source, t) {
1243
+ switch (source) {
1244
+ case "test-batch": return t("row.poolProbeSourceTest");
1245
+ case "live-request": return t("row.poolProbeSourceLive");
1246
+ case void 0: return t("row.poolProbeSourceUnknown");
1247
+ }
1248
+ }
1249
+ /** The tone to log a test row result under. */
1250
+ function outcomeTone(outcome) {
1251
+ return outcome === "ok" ? "ok" : "warn";
1252
+ }
1253
+ /** The serving account (Host's answer), for the header badge. */
1254
+ function servingAccount(pool) {
1255
+ return pool.accounts.find((account) => account.current === true);
1256
+ }
1257
+ /**
1258
+ * Render a route refusal into a card-safe string. The error statuses the Host
1259
+ * can answer with are listed in the card doc; every one here must render as
1260
+ * TEXT (never crash). `origin-not-trusted`/`pool-unavailable` carry no body, so
1261
+ * they get a dedicated copy line keyed off the status.
1262
+ */
1263
+ function renderRouteError(t, status, body) {
1264
+ if (status === 403 && body?.reason === "origin-not-trusted") return t("row.poolErrOrigin");
1265
+ if (status === 503) return t("row.poolErrUnavailable");
1266
+ if (status === 405) return t("row.poolErrMethod");
1267
+ if (status === 400) return t("row.poolErrBadRequest");
1268
+ if (status === 409) switch (body?.reason) {
1269
+ case "no-members": return t("row.poolErrNoMembers");
1270
+ case "no-live-members": return t("row.poolErrNoLiveMembers");
1271
+ case "checkin-unsupported": return t("row.poolErrCheckinUnsupported");
1272
+ case "target-model-stale": return t("row.poolErrTargetStale");
1273
+ case "no-free-model": return t("row.poolErrNoFreeModel");
1274
+ default: return t("row.poolErrConflict");
1275
+ }
1276
+ return t("row.poolErrGeneric", { status: String(status) });
1277
+ }
1278
+ /**
1279
+ * Consume a `test` NDJSON answer line-by-line so rows render while the batch
1280
+ * runs. A non-JSON line is skipped; a `{reason:'pool-failed'}` line reports the
1281
+ * failure and counts as "saw something". Returns whether any usable line was
1282
+ * seen (a body with nothing parseable is a route bug the caller should surface).
1283
+ */
1284
+ function consumeTestNdjson(text, t, onRow, onFailure) {
1285
+ let sawAny = false;
1286
+ for (const line of text.split(/\r?\n/)) {
1287
+ if (line.trim() === "") continue;
1288
+ let parsed;
1289
+ try {
1290
+ parsed = JSON.parse(line);
1291
+ } catch {
1292
+ continue;
1293
+ }
1294
+ if (parsed.row !== void 0) {
1295
+ sawAny = true;
1296
+ onRow(parsed.row);
1297
+ } else if (parsed.rows !== void 0) {
1298
+ sawAny = true;
1299
+ for (const r of parsed.rows) onRow(r);
1300
+ } else if (parsed.reason === "pool-failed") {
1301
+ sawAny = true;
1302
+ onFailure(parsed.error ?? t("row.poolLogGeneric"));
1303
+ }
1304
+ }
1305
+ return sawAny;
1306
+ }
1307
+ function AccountPool({ panel, pool, models, t, settingsScope, onChanged, onRefresh, tools, topActions, details, claims, onClaim, deviceClaimed = false, onBind, bindingBusy }) {
1308
+ const [settingsTick, setSettingsTick] = (0, react.useState)(0);
1309
+ const saved = (0, react.useMemo)(() => {
1310
+ const base = regionPoolOf(settingsScope?.getSnapshot().value, panel);
1311
+ return {
1312
+ enabled: base.enabled === true,
1313
+ targetModelId: base.targetModelId ?? pool.targetModelId ?? pool.staleTargetModelId ?? "",
1314
+ memberAccountIds: [...base.memberAccountIds ?? []],
1315
+ probeInputTokens: numberOr(base.probeInputTokens, numberOr(pool.probeInputTokens, 0))
1316
+ };
1317
+ }, [
1318
+ settingsTick,
1319
+ settingsScope,
1320
+ panel,
1321
+ pool.targetModelId,
1322
+ pool.staleTargetModelId,
1323
+ pool.probeInputTokens
1324
+ ]);
1325
+ /**
1326
+ * Whether the running Host knows the probe-size preference at all.
1327
+ *
1328
+ * The card bundle and the Host update on different clocks (a page refresh vs a
1329
+ * restart), so an older Host serves a pool document without this field. Writing
1330
+ * a key that Host's schema does not declare would be dropped on the way in and
1331
+ * then fail the read-back check — a save that reports an error and changes
1332
+ * nothing. Hiding the row is the honest state: this Host cannot do it yet.
1333
+ */
1334
+ const probeSupported = typeof pool.probeInputTokens === "number";
1335
+ const [enabled, setEnabled] = (0, react.useState)(saved.enabled);
1336
+ const [targetModelId, setTargetModelId] = (0, react.useState)(saved.targetModelId ?? "");
1337
+ const [probeInputTokens, setProbeInputTokens] = (0, react.useState)(saved.probeInputTokens);
1338
+ const [selected, setSelected] = (0, react.useState)(() => new Set(saved.memberAccountIds ?? []));
1339
+ const [busy, setBusy] = (0, react.useState)(void 0);
1340
+ const [saveError, setSaveError] = (0, react.useState)(void 0);
1341
+ const [testing, setTesting] = (0, react.useState)(void 0);
1342
+ const [rows, setRows] = (0, react.useState)({});
1343
+ const [removed, setRemoved] = (0, react.useState)({});
1344
+ const [log, setLog] = (0, react.useState)([]);
1345
+ const [now, setNow] = (0, react.useState)(Date.now());
1346
+ const logId = (0, react.useRef)(0);
1347
+ const mountRef = (0, react.useRef)(true);
1348
+ (0, react.useEffect)(() => {
1349
+ mountRef.current = true;
1350
+ return () => {
1351
+ mountRef.current = false;
1352
+ };
1353
+ }, []);
1354
+ (0, react.useEffect)(() => settingsScope?.subscribe(() => {
1355
+ setSettingsTick((tick) => tick + 1);
1356
+ }), [settingsScope]);
1357
+ const pushLog = (tone, text) => {
1358
+ if (!mountRef.current) return;
1359
+ logId.current += 1;
1360
+ const id = logId.current;
1361
+ setLog((prev) => [...prev.slice(-49), {
1362
+ id,
1363
+ tone,
1364
+ text
1365
+ }]);
1366
+ };
1367
+ const knownIds = (0, react.useMemo)(() => new Set(pool.accounts.map((account) => account.accountId)), [pool]);
1368
+ const ghosts = (0, react.useMemo)(() => {
1369
+ const names = pool.memberNames ?? {};
1370
+ const result = [];
1371
+ for (const id of saved.memberAccountIds ?? []) if (!knownIds.has(id)) result.push({
1372
+ accountId: id,
1373
+ accountName: names[id] ?? id
1374
+ });
1375
+ return result;
1376
+ }, [
1377
+ saved,
1378
+ pool,
1379
+ knownIds
1380
+ ]);
1381
+ const dirty = enabled !== saved.enabled || (targetModelId ?? "") !== (saved.targetModelId ?? "") || probeSupported && probeInputTokens !== saved.probeInputTokens || !sameIds([...selected], saved.memberAccountIds);
1382
+ const writable = settingsScope?.getSnapshot().writable === true;
1383
+ const savedKey = (0, react.useMemo)(() => `${panel}:${saved.enabled}:${saved.targetModelId ?? ""}:${saved.probeInputTokens}:${[...saved.memberAccountIds ?? []].sort().join("\0")}`, [panel, saved]);
1384
+ (0, react.useEffect)(() => {
1385
+ setEnabled(saved.enabled);
1386
+ setTargetModelId(saved.targetModelId ?? "");
1387
+ setProbeInputTokens(saved.probeInputTokens);
1388
+ setSelected(new Set(saved.memberAccountIds ?? []));
1389
+ setSaveError(void 0);
1390
+ }, [savedKey]);
1391
+ const toggleMember = (accountId) => {
1392
+ setSelected((prev) => {
1393
+ const next = new Set(prev);
1394
+ if (next.has(accountId)) next.delete(accountId);
1395
+ else next.add(accountId);
1396
+ return next;
1397
+ });
1398
+ };
1399
+ const selectAll = () => {
1400
+ setSelected(/* @__PURE__ */ new Set([...pool.accounts.map((a) => a.accountId), ...saved.memberAccountIds ?? []]));
1401
+ };
1402
+ const selectNone = () => setSelected(/* @__PURE__ */ new Set());
1403
+ const discard = () => {
1404
+ setEnabled(saved.enabled);
1405
+ setTargetModelId(saved.targetModelId ?? "");
1406
+ setProbeInputTokens(saved.probeInputTokens);
1407
+ setSelected(new Set(saved.memberAccountIds ?? []));
1408
+ setSaveError(void 0);
1409
+ pushLog("info", t("row.poolLogDiscarded"));
1410
+ setNow(Date.now());
1411
+ };
1412
+ const save = async () => {
1413
+ if (!writable || busy !== void 0) return;
1414
+ setBusy("save");
1415
+ setSaveError(void 0);
1416
+ try {
1417
+ const next = {
1418
+ enabled,
1419
+ ...targetModelId === "" ? {} : { targetModelId },
1420
+ memberAccountIds: [...selected],
1421
+ ...probeSupported ? { probeInputTokens } : {}
1422
+ };
1423
+ await writeSettingsField(settingsScope, "regions", nextRegionPool(settingsScope.getSnapshot().value, panel, next), (readBack) => {
1424
+ const readPool = regionPoolOf(readBack, panel);
1425
+ return readPool.enabled === (next.enabled ?? false) && sameIds(readPool.memberAccountIds, next.memberAccountIds) && (readPool.targetModelId ?? "") === (next.targetModelId ?? "") && (!probeSupported || (readPool.probeInputTokens ?? 0) === probeInputTokens);
1426
+ });
1427
+ pushLog("ok", t("row.poolLogSaved"));
1428
+ onChanged?.();
1429
+ onRefresh?.();
1430
+ setNow(Date.now());
1431
+ } catch (error) {
1432
+ const message = error instanceof Error ? error.message : String(error);
1433
+ setSaveError(t("row.poolSaveFailed", { message }));
1434
+ pushLog("error", t("row.poolLogSaveFailed"));
1435
+ } finally {
1436
+ if (mountRef.current) setBusy(void 0);
1437
+ }
1438
+ };
1439
+ const runCheckin = async () => {
1440
+ if (busy !== void 0) return;
1441
+ const members = [...selected];
1442
+ if (members.length === 0) {
1443
+ pushLog("warn", t("row.poolLogNoMembers"));
1444
+ return;
1445
+ }
1446
+ setBusy("checkin");
1447
+ setNow(Date.now());
1448
+ pushLog("info", t("row.poolLogCheckinStart", { count: members.length }));
1449
+ try {
1450
+ const response = await fetch(withTraePoolAction(panel, "checkin"), { method: "POST" });
1451
+ const body = await response.json();
1452
+ if (!response.ok) throw new Error(renderRouteError(t, response.status, body));
1453
+ for (const row of body?.rows ?? []) switch (row.status) {
1454
+ case "claimed":
1455
+ pushLog("ok", t("row.poolLogCheckinClaimed", { accountName: row.accountName }));
1456
+ break;
1457
+ case "already":
1458
+ case "device-already":
1459
+ pushLog("info", t("row.poolLogCheckinAlready", { accountName: row.accountName }));
1460
+ break;
1461
+ case "failed":
1462
+ case "disabled": pushLog("error", t("row.poolLogCheckinFailed", {
1463
+ accountName: row.accountName,
1464
+ message: row.message ?? ""
1465
+ }));
1466
+ }
1467
+ pushLog(members.length > 0 && body?.rows ? "ok" : "info", t("row.poolLogCheckinDone"));
1468
+ onRefresh?.();
1469
+ setNow(Date.now());
1470
+ } catch (error) {
1471
+ pushLog("error", t("row.poolLogBatchFailed", { message: error instanceof Error ? error.message : String(error) }));
1472
+ } finally {
1473
+ if (mountRef.current) setBusy(void 0);
1474
+ }
1475
+ };
1476
+ const runTest = async () => {
1477
+ if (busy !== void 0) return;
1478
+ const members = [...selected];
1479
+ if (members.length === 0) {
1480
+ pushLog("warn", t("row.poolLogNoMembers"));
1481
+ return;
1482
+ }
1483
+ setBusy("test");
1484
+ setRows({});
1485
+ setTesting({ done: 0 });
1486
+ setNow(Date.now());
1487
+ const targetLabel = targetModelId === "" ? t("row.poolTargetAutoOption") : models.find((m) => m.id === targetModelId)?.name ?? targetModelId;
1488
+ pushLog("info", t("row.poolLogTestStart", {
1489
+ count: members.length,
1490
+ model: targetLabel
1491
+ }));
1492
+ try {
1493
+ const text = await (await fetch(withTraePoolAction(panel, "test"), { method: "POST" })).text();
1494
+ const seen = /* @__PURE__ */ new Set();
1495
+ if (!consumeTestNdjson(text, t, (r) => {
1496
+ setRows((prev) => ({
1497
+ ...prev,
1498
+ [r.accountId]: r
1499
+ }));
1500
+ seen.add(r.accountId);
1501
+ if (mountRef.current) setTesting({ done: seen.size });
1502
+ pushLog(outcomeTone(r.outcome), t("row.poolLogTestRow", {
1503
+ accountName: r.accountName,
1504
+ outcome: outcomeLabel(r.outcome, t)
1505
+ }));
1506
+ }, (errorText) => {
1507
+ pushLog("error", t("row.poolLogBatchFailed", { message: errorText }));
1508
+ })) throw new Error(t("row.poolLogGeneric"));
1509
+ pushLog("ok", t("row.poolLogTestDone"));
1510
+ onRefresh?.();
1511
+ setNow(Date.now());
1512
+ } catch (error) {
1513
+ pushLog("error", t("row.poolLogBatchFailed", { message: error instanceof Error ? error.message : String(error) }));
1514
+ } finally {
1515
+ if (mountRef.current) {
1516
+ setBusy(void 0);
1517
+ setTesting(void 0);
1518
+ }
1519
+ }
1520
+ };
1521
+ const runSingleTest = async (account) => {
1522
+ if (busy !== void 0) return;
1523
+ setBusy("test");
1524
+ setNow(Date.now());
1525
+ const targetLabel = targetModelId === "" ? t("row.poolTargetAutoOption") : models.find((m) => m.id === targetModelId)?.name ?? targetModelId;
1526
+ pushLog("info", t("row.poolLogRowTesting", { accountName: account.accountName }));
1527
+ try {
1528
+ if (!consumeTestNdjson(await (await fetch(withTraePoolAccount(panel, "test", account.accountId), { method: "POST" })).text(), t, (r) => {
1529
+ setRows((prev) => ({
1530
+ ...prev,
1531
+ [r.accountId]: r
1532
+ }));
1533
+ pushLog(outcomeTone(r.outcome), t("row.poolLogTestRow", {
1534
+ accountName: r.accountName,
1535
+ outcome: outcomeLabel(r.outcome, t),
1536
+ model: targetLabel
1537
+ }));
1538
+ }, (errorText) => {
1539
+ pushLog("error", t("row.poolLogBatchFailed", { message: errorText }));
1540
+ })) throw new Error(t("row.poolLogGeneric"));
1541
+ onRefresh?.();
1542
+ setNow(Date.now());
1543
+ } catch (error) {
1544
+ pushLog("error", t("row.poolLogBatchFailed", { message: error instanceof Error ? error.message : String(error) }));
1545
+ } finally {
1546
+ if (mountRef.current) setBusy(void 0);
1547
+ }
1548
+ };
1549
+ const removeAccount = async (account) => {
1550
+ if (busy !== void 0) return;
1551
+ setBusy("remove");
1552
+ setRemoved((prev) => ({
1553
+ ...prev,
1554
+ [account.accountId]: false
1555
+ }));
1556
+ try {
1557
+ const response = await fetch(withTraePoolAccount(panel, "remove", account.accountId), { method: "POST" });
1558
+ const body = await response.json();
1559
+ if (!response.ok) throw new Error(renderRouteError(t, response.status, body));
1560
+ if (body?.removed !== true) throw new Error(t("row.poolRowRemoveFailed"));
1561
+ if (mountRef.current) setRemoved((prev) => ({
1562
+ ...prev,
1563
+ [account.accountId]: true
1564
+ }));
1565
+ pushLog("ok", t("row.poolRowRemoved", { accountName: account.accountName }));
1566
+ setSelected((prev) => {
1567
+ const next = new Set(prev);
1568
+ next.delete(account.accountId);
1569
+ return next;
1570
+ });
1571
+ onRefresh?.();
1572
+ setNow(Date.now());
1573
+ } catch (error) {
1574
+ pushLog("error", t("row.poolRowRemoveError", { message: error instanceof Error ? error.message : String(error) }));
1575
+ } finally {
1576
+ if (mountRef.current) setBusy(void 0);
1577
+ }
1578
+ };
1579
+ const noCandidate = pool.noCandidate;
1580
+ const showNoCandidate = pool.enabled && noCandidate !== void 0;
1581
+ const serving = servingAccount(pool);
1582
+ /**
1583
+ * The batch buttons are disabled for reasons the user cannot see (they are not
1584
+ * under the pointer, so a `title` only helps after a hover). Name them here.
1585
+ */
1586
+ const actionNotes = [];
1587
+ if (selected.size === 0) actionNotes.push(t("row.poolActionNoteNoMembers"));
1588
+ if (deviceClaimed) actionNotes.push(t("row.poolActionNoteCheckinDevice"));
1589
+ if (pool.targetModelSource === "none") actionNotes.push(t("row.poolActionNoteTestNone"));
1590
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1591
+ className: "dsm-trae-pool",
1592
+ "aria-label": t("row.poolTitle"),
1593
+ children: [
1594
+ pool.enabled !== true && onBind !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1595
+ className: "dsm-trae-pool-set dsm-trae-pool-bind",
1596
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1597
+ className: "dsm-trae-pool-set-copy",
1598
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: t("row.poolManualAccountLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.poolManualAccountHint") })]
1599
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1600
+ className: "dsm-trae-pool-set-ctl",
1601
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1602
+ className: "dsm-trae-select dsm-trae-pool-account-select",
1603
+ value: serving?.accountId ?? "",
1604
+ disabled: bindingBusy === true || !writable,
1605
+ onChange: (event) => {
1606
+ const next = event.target.value;
1607
+ if (next !== "") onBind(next);
1608
+ },
1609
+ children: [serving === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1610
+ value: "",
1611
+ disabled: true,
1612
+ children: t("row.poolManualAccountNone")
1613
+ }) : null, pool.accounts.filter((account) => account.reachable !== false).map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1614
+ value: account.accountId,
1615
+ children: account.accountName
1616
+ }, account.accountId))]
1617
+ })
1618
+ })]
1619
+ }) : null,
1620
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1621
+ className: "dsm-trae-pool-actions",
1622
+ children: [
1623
+ pool.checkinSupported === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1624
+ type: "button",
1625
+ className: "dsm-btn dsm-btn-outline dsm-trae-pool-action-checkin",
1626
+ disabled: busy !== void 0 || !writable || selected.size === 0 || deviceClaimed,
1627
+ title: deviceClaimed ? t("row.checkinDeviceSpent") : void 0,
1628
+ onClick: () => {
1629
+ runCheckin();
1630
+ },
1631
+ children: busy === "checkin" ? t("row.poolCheckingIn") : t("row.poolCheckinAll")
1632
+ }) : null,
1633
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1634
+ type: "button",
1635
+ className: "dsm-btn dsm-btn-outline dsm-trae-pool-action-test",
1636
+ disabled: busy !== void 0 || selected.size === 0 || pool.targetModelSource === "none",
1637
+ title: pool.targetModelSource === "none" ? t("row.poolTargetNoneHint") : void 0,
1638
+ onClick: () => {
1639
+ runTest();
1640
+ },
1641
+ children: busy === "test" ? t("row.poolTesting") : t("row.poolTestAll")
1642
+ }),
1643
+ busy === "checkin" || busy === "test" || testing !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1644
+ className: "dsm-trae-pool-busy",
1645
+ role: "status",
1646
+ children: t("row.poolBusyHint")
1647
+ }) : null,
1648
+ topActions === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1649
+ className: "dsm-trae-pool-actions-end",
1650
+ children: topActions
1651
+ })
1652
+ ]
1653
+ }),
1654
+ actionNotes.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1655
+ className: "dsm-trae-pool-action-note",
1656
+ children: actionNotes.join(" · ")
1657
+ }),
1658
+ showNoCandidate ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NoCandidateNotice, {
1659
+ noCandidate,
1660
+ t
1661
+ }) : null,
1662
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1663
+ className: "dsm-trae-pool-card",
1664
+ children: [
1665
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1666
+ className: "dsm-trae-pool-card-head",
1667
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1668
+ className: "dsm-trae-pool-card-title",
1669
+ children: t("row.poolMembersTitle")
1670
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1671
+ className: "dsm-trae-pool-card-tools",
1672
+ children: [
1673
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1674
+ className: "dsm-trae-pool-count",
1675
+ children: t("row.poolSelectedCount", {
1676
+ count: selected.size,
1677
+ total: pool.accounts.length + ghosts.length
1678
+ })
1679
+ }),
1680
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1681
+ type: "button",
1682
+ className: "dsm-btn dsm-btn-outline",
1683
+ disabled: busy !== void 0,
1684
+ onClick: selectAll,
1685
+ children: t("row.poolSelectAll")
1686
+ }),
1687
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1688
+ type: "button",
1689
+ className: "dsm-btn dsm-btn-outline",
1690
+ disabled: busy !== void 0,
1691
+ onClick: selectNone,
1692
+ children: t("row.poolSelectNone")
1693
+ })
1694
+ ]
1695
+ })]
1696
+ }),
1697
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1698
+ className: pool.checkinSupported ? "dsm-trae-pool-table" : "dsm-trae-pool-table dsm-trae-pool-table-no-checkin",
1699
+ role: "table",
1700
+ "aria-label": t("row.poolMembersTitle"),
1701
+ children: [
1702
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemberRowHeader, {
1703
+ t,
1704
+ checkinSupported: pool.checkinSupported
1705
+ }),
1706
+ pool.accounts.map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemberRow, {
1707
+ account,
1708
+ showCheckin: pool.checkinSupported,
1709
+ deviceClaimed,
1710
+ models,
1711
+ rows,
1712
+ checked: selected.has(account.accountId),
1713
+ detail: details?.[account.accountId],
1714
+ claim: claims?.[account.accountId],
1715
+ onClaim,
1716
+ onToggle: () => {
1717
+ toggleMember(account.accountId);
1718
+ },
1719
+ onTest: () => {
1720
+ runSingleTest(account);
1721
+ },
1722
+ t,
1723
+ disabled: busy !== void 0 || !writable,
1724
+ removing: removed[account.accountId] === true,
1725
+ onRemove: () => {
1726
+ removeAccount(account);
1727
+ },
1728
+ formatNumber: formatNumber$1,
1729
+ relativeAt
1730
+ }, account.accountId)),
1731
+ ghosts.map((ghost) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemberGhostRow, {
1732
+ ghost,
1733
+ showCheckin: pool.checkinSupported,
1734
+ checked: selected.has(ghost.accountId),
1735
+ onToggle: () => {
1736
+ toggleMember(ghost.accountId);
1737
+ },
1738
+ t,
1739
+ disabled: busy !== void 0 || !writable,
1740
+ removing: removed[ghost.accountId] === true,
1741
+ onRemove: () => {
1742
+ removeAccount(ghost);
1743
+ }
1744
+ }, `ghost:${ghost.accountId}`))
1745
+ ]
1746
+ }),
1747
+ ghosts.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1748
+ className: "dsm-trae-pool-ghost-note",
1749
+ role: "status",
1750
+ children: t("row.poolGhostMembers", {
1751
+ count: ghosts.length,
1752
+ names: ghosts.map((g) => g.accountName).join("、")
1753
+ })
1754
+ }) : null,
1755
+ dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1756
+ className: "dsm-trae-pool-unsaved",
1757
+ role: "status",
1758
+ children: t("row.poolUnsavedMembers")
1759
+ }) : null
1760
+ ]
1761
+ }),
1762
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1763
+ className: "dsm-trae-pool-settings-wrap",
1764
+ children: [
1765
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1766
+ className: "dsm-trae-pool-head",
1767
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1768
+ className: "dsm-trae-pool-title",
1769
+ children: t("row.poolSettingsTitle")
1770
+ }), dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1771
+ className: "dsm-trae-pool-dirty",
1772
+ role: "status",
1773
+ children: t("row.poolDirty")
1774
+ }) : null]
1775
+ }),
1776
+ tools === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1777
+ className: "dsm-trae-pool-tools dsm-trae-pool-tools-dashed",
1778
+ children: tools
1779
+ }),
1780
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1781
+ className: "dsm-trae-pool-note",
1782
+ children: t("row.poolSettingsNote")
1783
+ }),
1784
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1785
+ className: "dsm-trae-pool-settings",
1786
+ children: [
1787
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1788
+ className: "dsm-trae-pool-set",
1789
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1790
+ className: "dsm-trae-pool-set-copy",
1791
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: t("row.poolEnabled") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.poolEnabledHint") })]
1792
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1793
+ className: "dsm-trae-pool-set-ctl",
1794
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1795
+ className: "dsm-trae-switch dsm-trae-switch-pill",
1796
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1797
+ type: "checkbox",
1798
+ checked: enabled,
1799
+ disabled: busy !== void 0 || !writable,
1800
+ "aria-label": t("row.poolEnabled"),
1801
+ onChange: (e) => {
1802
+ setEnabled(e.target.checked);
1803
+ }
1804
+ })
1805
+ })
1806
+ })]
1807
+ }),
1808
+ saved.enabled === true && enabled === false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1809
+ className: "dsm-trae-pool-off-notice",
1810
+ role: "status",
1811
+ children: t("row.poolOffNotice")
1812
+ }) : null,
1813
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1814
+ className: "dsm-trae-pool-set",
1815
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1816
+ className: "dsm-trae-pool-set-copy",
1817
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: t("row.poolTargetLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.poolTargetHint") })]
1818
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1819
+ className: "dsm-trae-pool-set-ctl",
1820
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1821
+ className: "dsm-trae-select",
1822
+ "aria-label": t("row.poolTargetLabel"),
1823
+ value: targetModelId,
1824
+ disabled: busy !== void 0 || !writable,
1825
+ onChange: (e) => {
1826
+ setTargetModelId(e.target.value);
1827
+ },
1828
+ children: [
1829
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1830
+ value: "",
1831
+ children: t("row.poolTargetAutoOption")
1832
+ }),
1833
+ models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1834
+ value: model.id,
1835
+ children: model.name
1836
+ }, model.id)),
1837
+ pool.targetModelSource === "stale" && pool.staleTargetModelId !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1838
+ value: pool.staleTargetModelId,
1839
+ children: t("row.poolTargetStaleOption", { model: pool.staleTargetModelId })
1840
+ }) : null
1841
+ ]
1842
+ })
1843
+ })]
1844
+ }),
1845
+ pool.targetModelSource === "stale" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1846
+ className: "dsm-trae-pool-target-stale",
1847
+ role: "status",
1848
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.poolTargetStale", { model: pool.staleTargetModelId ?? "" }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1849
+ type: "button",
1850
+ className: "dsm-btn dsm-btn-outline",
1851
+ disabled: busy !== void 0,
1852
+ onClick: () => {
1853
+ setTargetModelId("");
1854
+ pushLog("info", t("row.poolLogTargetCleared"));
1855
+ },
1856
+ children: t("row.poolTargetStaleClear")
1857
+ })]
1858
+ }) : null,
1859
+ pool.targetModelSource === "none" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1860
+ className: "dsm-trae-pool-target-none",
1861
+ role: "status",
1862
+ children: [
1863
+ t("row.poolTargetNone"),
1864
+ " — ",
1865
+ t("row.poolTargetNoneHint")
1866
+ ]
1867
+ }) : null,
1868
+ probeSupported ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1869
+ className: "dsm-trae-pool-set",
1870
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1871
+ className: "dsm-trae-pool-set-copy",
1872
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: t("row.poolProbeSizeLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.poolProbeSizeHint") })]
1873
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1874
+ className: "dsm-trae-pool-set-ctl",
1875
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
1876
+ className: "dsm-trae-select",
1877
+ "aria-label": t("row.poolProbeSizeLabel"),
1878
+ value: String(probeInputTokens),
1879
+ disabled: busy !== void 0 || !writable,
1880
+ onChange: (e) => {
1881
+ setProbeInputTokens(Number(e.target.value));
1882
+ },
1883
+ children: PROBE_INPUT_TOKEN_CHOICES.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1884
+ value: String(choice),
1885
+ children: choice === 0 ? t("row.poolProbeSizeDefault") : t("row.poolProbeSizeOption", { wan: choice / 1e4 })
1886
+ }, choice))
1887
+ })
1888
+ })]
1889
+ }) : null,
1890
+ saveError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1891
+ className: "dsm-trae-pool-save-error",
1892
+ role: "alert",
1893
+ children: saveError
1894
+ })
1895
+ ]
1896
+ }),
1897
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1898
+ className: "dsm-trae-pool-save-row",
1899
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
1900
+ className: "dsm-trae-usage-cheer",
1901
+ href: "https://github.com/dingminhua/dsh-connect-trae",
1902
+ target: "_blank",
1903
+ rel: "noreferrer noopener",
1904
+ children: [t("row.cheer"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1905
+ className: "dsm-trae-usage-cheer-star",
1906
+ "aria-hidden": "true",
1907
+ children: "★"
1908
+ })]
1909
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1910
+ className: "dsm-trae-pool-save-buttons",
1911
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1912
+ type: "button",
1913
+ className: "dsm-btn dsm-btn-outline",
1914
+ disabled: !dirty || busy !== void 0,
1915
+ onClick: discard,
1916
+ children: t("row.poolDiscard")
1917
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1918
+ type: "button",
1919
+ className: "dsm-btn dsm-btn-primary",
1920
+ disabled: !dirty || busy !== void 0,
1921
+ onClick: () => {
1922
+ save();
1923
+ },
1924
+ children: busy === "save" ? t("row.poolSaving") : t("row.poolSaved")
1925
+ })]
1926
+ })]
1927
+ })
1928
+ ]
1929
+ }),
1930
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActivityLog, {
1931
+ log,
1932
+ t,
1933
+ now,
1934
+ relativeAt,
1935
+ onClear: () => setLog([])
1936
+ })
1937
+ ]
1938
+ });
1939
+ }
1940
+ function NoCandidateNotice({ noCandidate, t }) {
1941
+ if (noCandidate === "empty") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1942
+ className: "dsm-trae-pool-warning dsm-trae-pool-warning-empty",
1943
+ role: "status",
1944
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1945
+ className: "dsm-trae-pool-warning-title",
1946
+ children: t("row.poolNoCandidateEmpty")
1947
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1948
+ className: "dsm-trae-pool-warning-hint",
1949
+ children: t("row.poolNoCandidateEmptyHint")
1950
+ })]
1951
+ });
1952
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1953
+ className: "dsm-trae-pool-warning dsm-trae-pool-warning-unusable",
1954
+ role: "status",
1955
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1956
+ className: "dsm-trae-pool-warning-title",
1957
+ children: t("row.poolNoCandidateUnusable")
1958
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1959
+ className: "dsm-trae-pool-warning-hint",
1960
+ children: [
1961
+ t("row.poolNoCandidateHint"),
1962
+ " ",
1963
+ exclusionLabel(noCandidate, t)
1964
+ ]
1965
+ })]
1966
+ });
1967
+ }
1968
+ function MemberRowHeader({ t, checkinSupported }) {
1969
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1970
+ className: "dsm-trae-pool-row dsm-trae-pool-row-head",
1971
+ role: "row",
1972
+ children: [
1973
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1974
+ className: "dsm-trae-pool-col-account",
1975
+ children: t("row.poolColumnAccount")
1976
+ }),
1977
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.poolColumnCredits") }),
1978
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.poolColumnProbe") }),
1979
+ checkinSupported ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.poolColumnCheckin") }) : null,
1980
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {})
1981
+ ]
1982
+ });
1983
+ }
1984
+ function MemberRow({ account, models, rows, checked, onToggle, onTest, t, disabled, removing, onRemove, formatNumber, relativeAt, detail, claim, onClaim, showCheckin, deviceClaimed }) {
1985
+ const isCurrent = account.current === true;
1986
+ const credit = account.generalAvailable;
1987
+ const excluded = account.excludedBy;
1988
+ const checkin = account.checkedInToday;
1989
+ const device = account.deviceCheckedIn;
1990
+ const testRow = rows[account.accountId];
1991
+ /**
1992
+ * Whether the probe column already explains WHY this row is excluded.
1993
+ *
1994
+ * The exclusion tag and the probe cell are derived from the same verdict, so a
1995
+ * row that shows both says 「登录凭据被拒」 twice, six centimetres apart. Keep
1996
+ * the tag only for the case the cell cannot cover: no probe to show at all
1997
+ * (e.g. a stored member whose last test predates this session).
1998
+ */
1999
+ const probeOnScreen = testRow !== void 0 || account.probe !== void 0;
2000
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2001
+ className: `dsm-trae-pool-row${isCurrent ? " dsm-trae-pool-row-current" : ""}`,
2002
+ role: "row",
2003
+ "data-account-id": account.accountId,
2004
+ children: [
2005
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2006
+ className: "dsm-trae-pool-account",
2007
+ children: [
2008
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2009
+ className: "dsm-trae-pool-check",
2010
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2011
+ type: "checkbox",
2012
+ checked,
2013
+ disabled,
2014
+ onChange: onToggle,
2015
+ "aria-label": t("row.poolSelectAria", { accountName: account.accountName })
2016
+ })
2017
+ }),
2018
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2019
+ className: "dsm-trae-pool-account-name",
2020
+ children: [
2021
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2022
+ className: `dsm-trae-pool-dot dsm-trae-pool-dot-${excluded ? "excluded" : "ok"}`,
2023
+ "aria-hidden": "true"
2024
+ }),
2025
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: account.accountName }),
2026
+ detail?.meta === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2027
+ className: "dsm-trae-pool-meta",
2028
+ children: detail.meta
2029
+ }),
2030
+ detail?.warn === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2031
+ className: `dsm-trae-pool-warn-tag dsm-trae-pool-warn-tag-${detail.warnTone ?? "soon"}`,
2032
+ role: "status",
2033
+ children: detail.warn
2034
+ })
2035
+ ]
2036
+ }),
2037
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2038
+ className: "dsm-trae-pool-account-tags",
2039
+ children: [
2040
+ isCurrent ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2041
+ className: "dsm-trae-pool-tag dsm-trae-pool-tag-current",
2042
+ children: t("row.poolCurrentBadge")
2043
+ }) : null,
2044
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2045
+ className: checked ? "dsm-trae-pool-member" : "dsm-trae-pool-not-member",
2046
+ children: checked ? t("row.poolMember") : t("row.poolNotMember")
2047
+ }),
2048
+ excluded && !probeOnScreen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2049
+ className: "dsm-trae-pool-tag dsm-trae-pool-tag-excluded",
2050
+ children: exclusionLabel(excluded, t)
2051
+ }) : null,
2052
+ account.tokenExpired === true && excluded !== "credential-rejected" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2053
+ className: "dsm-trae-pool-warn-tag dsm-trae-pool-warn-tag-lapsed",
2054
+ children: t("row.poolTokenExpired")
2055
+ }) : null
2056
+ ]
2057
+ }),
2058
+ account.probe?.atMs === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2059
+ className: "dsm-trae-pool-account-note",
2060
+ children: t("row.poolProbeAt", { when: shortWhen(account.probe.atMs) })
2061
+ })
2062
+ ]
2063
+ }),
2064
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2065
+ className: "dsm-trae-pool-credit",
2066
+ title: t("row.poolWorkHint"),
2067
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2068
+ className: "dsm-trae-pool-credit-num",
2069
+ children: credit === void 0 ? t("row.poolCreditNone") : formatNumber(credit)
2070
+ }), credit === void 0 || account.nearestExpiryMs === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2071
+ className: "dsm-trae-pool-credit-meta",
2072
+ children: t("row.poolCreditExpiry", { when: shortDate(account.nearestExpiryMs) })
2073
+ })]
2074
+ }),
2075
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2076
+ className: "dsm-trae-pool-probe",
2077
+ children: testRow !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProbeResult, {
2078
+ outcome: testRow.outcome,
2079
+ modelId: testRow.modelId,
2080
+ atMs: void 0,
2081
+ source: void 0,
2082
+ t,
2083
+ relativeAt,
2084
+ now: Date.now(),
2085
+ detail: testRow.detail
2086
+ }) : account.probe !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProbeResult, {
2087
+ outcome: account.probe.outcome,
2088
+ modelId: account.probe.modelId,
2089
+ atMs: account.probe.atMs,
2090
+ source: account.probe.source,
2091
+ t,
2092
+ relativeAt,
2093
+ now: Date.now(),
2094
+ detail: account.probe.message
2095
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2096
+ className: "dsm-trae-pool-probe-none",
2097
+ children: t("row.poolNeverTested")
2098
+ })
2099
+ }),
2100
+ showCheckin ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2101
+ className: "dsm-trae-pool-checkin",
2102
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2103
+ className: `dsm-trae-pool-checkin-state dsm-trae-pool-checkin-${checkin === void 0 ? "unknown" : checkin ? "ok" : device === true || deviceClaimed ? "device" : "no"}`,
2104
+ children: checkin === void 0 ? t("row.poolCheckinUnknown") : checkin ? t("row.poolCheckedIn") : device === true || deviceClaimed ? t("row.poolDeviceUsed") : t("row.poolNotCheckedIn")
2105
+ }), claim === void 0 || onClaim === void 0 || claim.claimable === false ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2106
+ type: "button",
2107
+ className: "dsm-btn dsm-btn-outline dsm-trae-pool-claim",
2108
+ disabled: claim.disabled || disabled,
2109
+ title: claim.title,
2110
+ onClick: () => {
2111
+ onClaim(account.accountId);
2112
+ },
2113
+ children: claim.label
2114
+ })]
2115
+ }) : null,
2116
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2117
+ className: "dsm-trae-pool-row-actions",
2118
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2119
+ type: "button",
2120
+ className: "dsm-btn dsm-btn-outline",
2121
+ disabled,
2122
+ onClick: onTest,
2123
+ children: t("row.poolRowTest")
2124
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2125
+ type: "button",
2126
+ className: "dsm-btn dsm-btn-outline dsm-trae-pool-remove",
2127
+ disabled: disabled || removing,
2128
+ "aria-label": t("row.poolRowRemoveAria", { accountName: account.accountName }),
2129
+ onClick: onRemove,
2130
+ children: removing ? t("row.poolRowRemoving") : t("row.poolRowRemove")
2131
+ })]
2132
+ })
2133
+ ]
2134
+ });
2135
+ }
2136
+ function MemberGhostRow({ ghost, checked, onToggle, t, disabled, removing, onRemove, showCheckin }) {
2137
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2138
+ className: "dsm-trae-pool-row dsm-trae-pool-row-ghost",
2139
+ role: "row",
2140
+ "data-account-id": ghost.accountId,
2141
+ children: [
2142
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2143
+ className: "dsm-trae-pool-account",
2144
+ children: [
2145
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2146
+ className: "dsm-trae-pool-check",
2147
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2148
+ type: "checkbox",
2149
+ checked,
2150
+ disabled,
2151
+ onChange: onToggle,
2152
+ "aria-label": t("row.poolSelectAria", { accountName: ghost.accountName })
2153
+ })
2154
+ }),
2155
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2156
+ className: "dsm-trae-pool-account-name",
2157
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2158
+ className: "dsm-trae-pool-dot dsm-trae-pool-dot-ghost",
2159
+ "aria-hidden": "true"
2160
+ }), ghost.accountName]
2161
+ }),
2162
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2163
+ className: "dsm-trae-pool-account-tags",
2164
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2165
+ className: checked ? "dsm-trae-pool-member" : "dsm-trae-pool-not-member",
2166
+ children: checked ? t("row.poolMember") : t("row.poolNotMember")
2167
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2168
+ className: "dsm-trae-pool-tag dsm-trae-pool-tag-ghost",
2169
+ children: t("row.poolExcludedUnusable")
2170
+ })]
2171
+ })
2172
+ ]
2173
+ }),
2174
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2175
+ className: "dsm-trae-pool-credit",
2176
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2177
+ className: "dsm-trae-pool-credit-num",
2178
+ children: t("row.poolCreditNone")
2179
+ })
2180
+ }),
2181
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2182
+ className: "dsm-trae-pool-probe",
2183
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2184
+ className: "dsm-trae-pool-probe-none",
2185
+ children: t("row.poolNeverTested")
2186
+ })
2187
+ }),
2188
+ showCheckin ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2189
+ className: "dsm-trae-pool-checkin",
2190
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2191
+ className: "dsm-trae-pool-checkin-state dsm-trae-pool-checkin-unknown",
2192
+ children: t("row.poolCheckinUnknown")
2193
+ })
2194
+ }) : null,
2195
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2196
+ className: "dsm-trae-pool-row-actions",
2197
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2198
+ type: "button",
2199
+ className: "dsm-btn dsm-btn-outline dsm-trae-pool-remove",
2200
+ disabled: disabled || removing,
2201
+ "aria-label": t("row.poolGhostRemoveAria", { accountName: ghost.accountName }),
2202
+ onClick: onRemove,
2203
+ children: removing ? t("row.poolRowRemoving") : t("row.poolGhostRemove")
2204
+ })
2205
+ })
2206
+ ]
2207
+ });
2208
+ }
2209
+ function ProbeResult({ outcome, modelId, atMs, source, detail, t, relativeAt, now }) {
2210
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2211
+ className: "dsm-trae-pool-probe-cell",
2212
+ children: [
2213
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2214
+ className: `dsm-trae-pool-probe-outcome dsm-trae-pool-probe-${outcome === "ok" ? "ok" : "warn"}`,
2215
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2216
+ className: "dsm-trae-pool-dot dsm-trae-pool-dot-ok",
2217
+ "aria-hidden": "true"
2218
+ }), outcomeLabel(outcome, t)]
2219
+ }),
2220
+ atMs === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2221
+ className: "dsm-trae-pool-probe-meta",
2222
+ children: t("row.poolProbeMeasured", { when: relativeAt(atMs, now, t) })
2223
+ }),
2224
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2225
+ className: "dsm-trae-pool-probe-meta",
2226
+ children: [modelId !== void 0 ? t("row.poolProbeModel", { model: modelId }) : t("row.poolProbeModelUnknown"), source === void 0 ? "" : ` · ${probeSourceLabel(source, t)}`]
2227
+ }),
2228
+ detail !== void 0 && detail !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2229
+ className: "dsm-trae-pool-probe-detail",
2230
+ children: detail
2231
+ }) : null
2232
+ ]
2233
+ });
2234
+ }
2235
+ function ActivityLog({ log, t, now, relativeAt, onClear }) {
2236
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2237
+ className: "dsm-trae-pool-log",
2238
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2239
+ className: "dsm-trae-pool-log-head",
2240
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
2241
+ className: "dsm-trae-pool-subtitle",
2242
+ children: t("row.poolLogTitle")
2243
+ }), log.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2244
+ type: "button",
2245
+ className: "dsm-btn dsm-btn-outline",
2246
+ onClick: onClear,
2247
+ children: t("row.poolLogClear")
2248
+ }) : null]
2249
+ }), log.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2250
+ className: "dsm-trae-pool-log-empty",
2251
+ children: t("row.poolLogEmpty")
2252
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
2253
+ className: "dsm-trae-pool-log-list",
2254
+ children: log.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
2255
+ className: `dsm-trae-pool-log-item dsm-trae-pool-log-${entry.tone}`,
2256
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2257
+ className: "dsm-trae-pool-log-text",
2258
+ children: entry.text
2259
+ })
2260
+ }, entry.id))
2261
+ })]
2262
+ });
2263
+ }
2264
+ //#endregion
863
2265
  //#region src/client/AccountTransfer.tsx
864
2266
  /**
865
2267
  * The card's account-transfer section: batch export and import of a region's
@@ -914,7 +2316,7 @@ window.__ModuleLoader__.load({
914
2316
  setTimeout(resolve, timeoutMs);
915
2317
  })]);
916
2318
  }
917
- function AccountTransfer({ t, region, accounts, onChanged }) {
2319
+ function AccountTransfer({ t, region, accounts, onChanged, hideLabel = false }) {
918
2320
  const [phase, setPhase] = (0, react.useState)({ kind: "idle" });
919
2321
  const [dialog, setDialog] = (0, react.useState)(void 0);
920
2322
  const [selected, setSelected] = (0, react.useState)(/* @__PURE__ */ new Set());
@@ -1065,7 +2467,7 @@ window.__ModuleLoader__.load({
1065
2467
  const busy = phase.kind === "exporting" || phase.kind === "importing" || phase.kind === "refreshing";
1066
2468
  const veilMessage = phase.kind === "exporting" ? t("row.transferExporting") : phase.kind === "importing" || phase.kind === "refreshing" ? t("row.transferImporting") : "";
1067
2469
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1068
- className: "dsm-trae-transfer",
2470
+ className: hideLabel ? "dsm-trae-transfer dsm-trae-transfer-flat" : "dsm-trae-transfer",
1069
2471
  "aria-label": t("row.transferTitle"),
1070
2472
  children: [
1071
2473
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BusyOverlay, {
@@ -1073,7 +2475,7 @@ window.__ModuleLoader__.load({
1073
2475
  message: veilMessage,
1074
2476
  ...busy ? { hint: t("row.transferVeilHint") } : {}
1075
2477
  }),
1076
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2478
+ hideLabel ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1077
2479
  className: "dsm-trae-transfer-label",
1078
2480
  children: t("row.transferTitle")
1079
2481
  }),
@@ -2378,23 +3780,63 @@ window.__ModuleLoader__.load({
2378
3780
  * Until the row's status lands the button waits, disabled, so the row keeps
2379
3781
  * its shape instead of reflowing the moment the read returns.
2380
3782
  */
2381
- const checkinControl = (account, state) => {
3783
+ /**
3784
+ * Everything a check-in button needs, derived once. The pool's table replaces
3785
+ * this card's roster table, so the same derivation has to feed both: two
3786
+ * copies would drift the moment one of them learned about a new refusal.
3787
+ */
3788
+ /**
3789
+ * Whether THIS machine has already been paid today, for any account.
3790
+ *
3791
+ * `didCheckedIn` is not an account fact. Trae keys the daily claim to
3792
+ * `x-device-id` and answers a second claim with 9095, so once one account has
3793
+ * claimed from this machine, every other account on it is refused for the
3794
+ * rest of the day. Keeping that flag per account made the card invite a
3795
+ * check-in that could only fail — visible as soon as two accounts sit next to
3796
+ * each other in one table.
3797
+ */
3798
+ const deviceClaimedToday = (() => {
3799
+ for (const state of Object.values(accountStatusByRegion[activeRegion])) if (state?.checkin?.didCheckedIn === true) return true;
3800
+ for (const spent of Object.values(deviceSpentByRegion[activeRegion])) if (spent) return true;
3801
+ return status.status === "signed-in" && status.pool !== void 0 && status.pool.accounts.some((account) => account.deviceCheckedIn === true);
3802
+ })();
3803
+ const claimViewOf = (accountId, state) => {
2382
3804
  const checkin = state?.checkin;
2383
3805
  const reward = checkin === void 0 ? void 0 : formatNumber(checkin.credits);
2384
3806
  const claimed = checkin?.checkedIn === true;
2385
- const deviceSpent = deviceSpentByRegion[activeRegion][account.id] === true || checkin?.didCheckedIn === true;
2386
- const spending = claimingFor === account.id;
3807
+ const deviceSpent = deviceClaimedToday;
3808
+ const spending = claimingFor === accountId;
2387
3809
  const usable = checkin !== void 0 && checkin.enabled;
2388
3810
  const title = reward === void 0 || !usable ? t("row.checkinDisabled") : deviceSpent && !claimed ? t("row.checkinDeviceSpent") : claimed ? t("row.checkinClaimed") : t("row.checkinReward", { reward });
3811
+ return {
3812
+ label: spending ? t("row.checkinClaiming") : claimed ? t("row.checkinClaimed") : deviceSpent ? t("row.checkinDeviceSpentShort") : !usable || reward === void 0 ? t("row.checkinClaim") : t("row.checkinClaimWith", { reward }),
3813
+ title,
3814
+ disabled: !usable || claimed || deviceSpent || spending,
3815
+ busy: spending,
3816
+ /**
3817
+ * False when there is nothing left to DO: a disabled "already claimed"
3818
+ * button restates the status line above it, in grey, forever. Callers
3819
+ * drop the control instead of buying it a permanent disabled state.
3820
+ */
3821
+ claimable: !claimed && !deviceSpent,
3822
+ settled: claimed ? "claimed" : deviceSpent ? "device" : void 0
3823
+ };
3824
+ };
3825
+ const checkinControl = (account, state) => {
3826
+ const view = claimViewOf(account.id, state);
3827
+ if (!view.claimable) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3828
+ className: "dsm-trae-account-checkin-state",
3829
+ children: view.settled === "claimed" ? t("row.poolCheckedIn") : t("row.poolDeviceUsed")
3830
+ });
2389
3831
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2390
3832
  type: "button",
2391
3833
  className: "dsm-trae-account-checkin",
2392
- disabled: !usable || claimed || deviceSpent || spending,
2393
- title,
3834
+ disabled: view.disabled,
3835
+ title: view.title,
2394
3836
  onClick: () => {
2395
3837
  claimFor(account.id);
2396
3838
  },
2397
- children: spending ? t("row.checkinClaiming") : claimed ? t("row.checkinClaimed") : deviceSpent ? t("row.checkinDeviceSpentShort") : !usable || reward === void 0 ? t("row.checkinClaim") : t("row.checkinClaimWith", { reward })
3839
+ children: view.label
2398
3840
  });
2399
3841
  };
2400
3842
  const label = status.status === "signed-in" ? t("row.signedIn", { accountName: status.accountName }) : status.status === "error" ? t("row.requestFailed") : t("row.signedOut");
@@ -2498,6 +3940,7 @@ window.__ModuleLoader__.load({
2498
3940
  title: t("row.followAppLoginHint"),
2499
3941
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2500
3942
  type: "checkbox",
3943
+ className: "dsm-trae-pref-check",
2501
3944
  checked: settingsEdits.followAppLogin,
2502
3945
  disabled: settingsScope?.getSnapshot().writable !== true || savingSettings,
2503
3946
  "aria-describedby": followHintId,
@@ -2505,18 +3948,16 @@ window.__ModuleLoader__.load({
2505
3948
  writePreference("followAppLogin", event.currentTarget.checked);
2506
3949
  }
2507
3950
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2508
- id: followHintId,
2509
- children: [
2510
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", {
2511
- id: followLabelId,
2512
- children: t("row.followAppLogin")
2513
- }),
2514
- " ",
2515
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2516
- className: "dsm-trae-pref-hint",
2517
- children: t("row.followAppLoginHint")
2518
- })
2519
- ]
3951
+ className: "dsm-trae-pref-copy",
3952
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3953
+ className: "dsm-trae-pref-label",
3954
+ id: followLabelId,
3955
+ children: t("row.followAppLogin")
3956
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3957
+ className: "dsm-trae-pref-hint",
3958
+ id: followHintId,
3959
+ children: t("row.followAppLoginHint")
3960
+ })]
2520
3961
  })]
2521
3962
  }),
2522
3963
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
@@ -2524,6 +3965,7 @@ window.__ModuleLoader__.load({
2524
3965
  title: t(isCnPool ? "row.showPointsInMainUiHint" : "row.showPointsInMainUiHintAi"),
2525
3966
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2526
3967
  type: "checkbox",
3968
+ className: "dsm-trae-pref-check",
2527
3969
  checked: settingsEdits.showPointsInMainUi,
2528
3970
  disabled: settingsScope?.getSnapshot().writable !== true || savingSettings,
2529
3971
  "aria-describedby": creditsHintId,
@@ -2531,50 +3973,53 @@ window.__ModuleLoader__.load({
2531
3973
  writePreference("showPointsInMainUi", event.currentTarget.checked);
2532
3974
  }
2533
3975
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2534
- id: creditsHintId,
2535
- children: [
2536
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", {
2537
- id: creditsLabelId,
2538
- children: t("row.showPointsInMainUi")
2539
- }),
2540
- " ",
2541
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2542
- className: "dsm-trae-pref-hint",
2543
- children: t(isCnPool ? "row.showPointsInMainUiHint" : "row.showPointsInMainUiHintAi")
2544
- })
2545
- ]
3976
+ className: "dsm-trae-pref-copy",
3977
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3978
+ className: "dsm-trae-pref-label",
3979
+ id: creditsLabelId,
3980
+ children: t("row.showPointsInMainUi")
3981
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3982
+ className: "dsm-trae-pref-hint",
3983
+ id: creditsHintId,
3984
+ children: t(isCnPool ? "row.showPointsInMainUiHint" : "row.showPointsInMainUiHintAi")
3985
+ })]
2546
3986
  })]
2547
3987
  }),
2548
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3988
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2549
3989
  className: "dsm-trae-pref dsm-trae-credit-position-pref",
2550
3990
  title: t("row.creditPositionHint"),
2551
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2552
- className: "dsm-trae-credit-position",
2553
- value: activeRegionOn ? creditPositionOn(activeRegion) : "composer",
2554
- disabled: !activeRegionOn || settingsScope?.getSnapshot().writable !== true || savingSettings,
2555
- "aria-label": t("row.creditPositionAria", { region: activeRegion === "cn" ? t("row.tabCn") : activeRegion === "enterprise" ? t("row.tabEnterprise") : t("row.tabAi") }),
2556
- onChange: (event) => {
2557
- changeCreditPosition(activeRegion, event.currentTarget.value === "stats" ? "stats" : "composer");
2558
- },
2559
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2560
- value: "composer",
2561
- children: t("row.creditPositionComposer")
2562
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2563
- value: "stats",
2564
- children: t("row.creditPositionStats")
2565
- })]
2566
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
2567
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: t("row.creditPositionLabel") }),
2568
- " ",
2569
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3991
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3992
+ className: "dsm-trae-pref-copy",
3993
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3994
+ className: "dsm-trae-pref-lead",
3995
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3996
+ className: "dsm-trae-pref-label",
3997
+ children: t("row.creditPositionLabel")
3998
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3999
+ className: "dsm-trae-credit-position",
4000
+ value: activeRegionOn ? creditPositionOn(activeRegion) : "composer",
4001
+ disabled: !activeRegionOn || settingsScope?.getSnapshot().writable !== true || savingSettings,
4002
+ "aria-label": t("row.creditPositionAria", { region: activeRegion === "cn" ? t("row.tabCn") : activeRegion === "enterprise" ? t("row.tabEnterprise") : t("row.tabAi") }),
4003
+ onChange: (event) => {
4004
+ changeCreditPosition(activeRegion, event.currentTarget.value === "stats" ? "stats" : "composer");
4005
+ },
4006
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4007
+ value: "composer",
4008
+ children: t("row.creditPositionComposer")
4009
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4010
+ value: "stats",
4011
+ children: t("row.creditPositionStats")
4012
+ })]
4013
+ })]
4014
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2570
4015
  className: "dsm-trae-pref-hint",
2571
4016
  children: t("row.creditPositionHint")
2572
- })
2573
- ] })]
4017
+ })]
4018
+ })
2574
4019
  })
2575
4020
  ]
2576
4021
  }),
2577
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4022
+ status.status === "signed-in" && status.pool !== void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2578
4023
  className: "dsm-trae-accounts",
2579
4024
  "aria-label": t("row.accountsTitle"),
2580
4025
  children: [
@@ -2698,62 +4143,6 @@ window.__ModuleLoader__.load({
2698
4143
  }, account.id);
2699
4144
  })]
2700
4145
  }) : null,
2701
- status.status === "signed-in" && status.creditsUnavailable === "enterprise" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2702
- className: "dsm-trae-credits-unavailable",
2703
- role: "note",
2704
- children: t("row.enterpriseCreditsNotice")
2705
- }) : null,
2706
- status.status === "signed-in" && status.alternatives !== void 0 && status.alternatives.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2707
- className: "dsm-trae-usage-alternative",
2708
- role: "status",
2709
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
2710
- className: "dsm-trae-usage-alternative-text",
2711
- children: [
2712
- t("row.creditsExhausted", { account: status.accountName }),
2713
- " ",
2714
- t("row.creditsAlternative", { accounts: status.alternatives.map((alt) => `${alt.accountName}(${t(`row.edition${alt.edition === "cn" ? "Cn" : alt.edition === "sg" ? "Sg" : alt.edition === "solo" ? "Solo" : "SoloSg"}`)}:${formatNumber(alt.workAvailable + alt.generalAvailable)})`).join("、") })
2715
- ]
2716
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2717
- type: "button",
2718
- className: "dsm-trae-usage-switch-button",
2719
- disabled: switchingAccount || settingsScope?.getSnapshot()?.writable !== true,
2720
- onClick: () => {
2721
- const target = status.alternatives?.[0];
2722
- if (target !== void 0) switchAccount(target.id);
2723
- },
2724
- children: t("row.creditsAlternativeSwitch")
2725
- })]
2726
- }) : null,
2727
- status.status === "signed-in" && status.credits?.expiry !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExpiryNotice, {
2728
- expiry: status.credits.expiry,
2729
- t,
2730
- formatNumber,
2731
- formatDateTime
2732
- }) : null,
2733
- status.status === "signed-in" && status.appLogin !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2734
- className: "dsm-trae-usage-alternative",
2735
- role: "status",
2736
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2737
- className: "dsm-trae-usage-alternative-text",
2738
- children: t("row.appLoginMismatch", {
2739
- account: status.appLogin.accountName,
2740
- bound: status.accountName
2741
- })
2742
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2743
- type: "button",
2744
- className: "dsm-trae-usage-switch-button",
2745
- disabled: switchingAccount || settingsScope?.getSnapshot()?.writable !== true,
2746
- onClick: () => {
2747
- followAppLogin();
2748
- },
2749
- children: t("row.appLoginFollow", { account: status.appLogin.accountName })
2750
- })]
2751
- }) : null,
2752
- rescanNote === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2753
- className: "dsm-trae-usage-note",
2754
- role: "status",
2755
- children: rescanNote
2756
- }),
2757
4146
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountTransfer, {
2758
4147
  t,
2759
4148
  region: activeRegion === "ai" ? "ai" : "cn",
@@ -2771,7 +4160,134 @@ window.__ModuleLoader__.load({
2771
4160
  })
2772
4161
  ]
2773
4162
  }),
4163
+ status.status === "signed-in" && status.creditsUnavailable === "enterprise" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4164
+ className: "dsm-trae-credits-unavailable",
4165
+ role: "note",
4166
+ children: t("row.enterpriseCreditsNotice")
4167
+ }) : null,
4168
+ status.status === "signed-in" && status.alternatives !== void 0 && status.alternatives.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4169
+ className: "dsm-trae-usage-alternative",
4170
+ role: "status",
4171
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
4172
+ className: "dsm-trae-usage-alternative-text",
4173
+ children: [
4174
+ t("row.creditsExhausted", { account: status.accountName }),
4175
+ " ",
4176
+ t("row.creditsAlternative", { accounts: status.alternatives.map((alt) => `${alt.accountName}(${t(`row.edition${alt.edition === "cn" ? "Cn" : alt.edition === "sg" ? "Sg" : alt.edition === "solo" ? "Solo" : "SoloSg"}`)}:${formatNumber(alt.workAvailable + alt.generalAvailable)})`).join("、") })
4177
+ ]
4178
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4179
+ type: "button",
4180
+ className: "dsm-trae-usage-switch-button",
4181
+ disabled: switchingAccount || settingsScope?.getSnapshot()?.writable !== true,
4182
+ onClick: () => {
4183
+ const target = status.alternatives?.[0];
4184
+ if (target !== void 0) switchAccount(target.id);
4185
+ },
4186
+ children: t("row.creditsAlternativeSwitch")
4187
+ })]
4188
+ }) : null,
4189
+ status.status === "signed-in" && status.credits?.expiry !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExpiryNotice, {
4190
+ expiry: status.credits.expiry,
4191
+ t,
4192
+ formatNumber,
4193
+ formatDateTime
4194
+ }) : null,
4195
+ status.status === "signed-in" && status.appLogin !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4196
+ className: "dsm-trae-usage-alternative",
4197
+ role: "status",
4198
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4199
+ className: "dsm-trae-usage-alternative-text",
4200
+ children: t("row.appLoginMismatch", {
4201
+ account: status.appLogin.accountName,
4202
+ bound: status.accountName
4203
+ })
4204
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4205
+ type: "button",
4206
+ className: "dsm-trae-usage-switch-button",
4207
+ disabled: switchingAccount || settingsScope?.getSnapshot()?.writable !== true,
4208
+ onClick: () => {
4209
+ followAppLogin();
4210
+ },
4211
+ children: t("row.appLoginFollow", { account: status.appLogin.accountName })
4212
+ })]
4213
+ }) : null,
4214
+ rescanNote === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4215
+ className: "dsm-trae-usage-note",
4216
+ role: "status",
4217
+ children: rescanNote
4218
+ }),
2774
4219
  status.status === "signed-in" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4220
+ status.pool !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountPool, {
4221
+ panel: activeRegion,
4222
+ pool: status.pool,
4223
+ models: visibleModels,
4224
+ t,
4225
+ settingsScope,
4226
+ topActions: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4227
+ type: "button",
4228
+ className: "dsm-btn dsm-btn-outline",
4229
+ disabled: busy,
4230
+ onClick: () => {
4231
+ rescanAccounts();
4232
+ },
4233
+ children: busy ? t("row.accountsScanning") : t("row.poolRescan")
4234
+ }),
4235
+ tools: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4236
+ className: "dsm-trae-pool-tool-row",
4237
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4238
+ className: "dsm-trae-pool-tool-label",
4239
+ children: t("row.poolToolsLabel")
4240
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountTransfer, {
4241
+ t,
4242
+ hideLabel: true,
4243
+ region: activeRegion === "ai" ? "ai" : "cn",
4244
+ accounts: status.accounts.map((account) => ({
4245
+ id: account.id,
4246
+ accountName: account.accountName
4247
+ })),
4248
+ onChanged: async () => {
4249
+ const fresh = await refreshUsage(activeRegion);
4250
+ if (fresh !== void 0) setStatusByRegion((current) => ({
4251
+ ...current,
4252
+ [activeRegion]: fresh
4253
+ }));
4254
+ }
4255
+ })]
4256
+ }),
4257
+ details: Object.fromEntries(status.accounts.map((account) => {
4258
+ const warn = refreshTokenWarning(account.refreshExpiresAtMs, Date.now(), t, formatDateTime);
4259
+ return [account.id, {
4260
+ meta: `${editionLabel(account.edition)} · ${sourceLabel(account.source)}`,
4261
+ ...warn === void 0 ? {} : {
4262
+ warn: warn.text,
4263
+ warnTone: warn.tone
4264
+ }
4265
+ }];
4266
+ })),
4267
+ claims: status.pool.checkinSupported === true ? Object.fromEntries(status.accounts.map((account) => [account.id, claimViewOf(account.id, accountStatusByRegion[activeRegion][account.id])])) : void 0,
4268
+ onClaim: (accountId) => {
4269
+ claimFor(accountId);
4270
+ },
4271
+ deviceClaimed: deviceClaimedToday,
4272
+ onBind: (accountId) => {
4273
+ switchAccount(accountId);
4274
+ },
4275
+ bindingBusy: switchingAccount,
4276
+ onChanged: async () => {
4277
+ const fresh = await refreshUsage(activeRegion);
4278
+ if (fresh !== void 0) setStatusByRegion((current) => ({
4279
+ ...current,
4280
+ [activeRegion]: fresh
4281
+ }));
4282
+ },
4283
+ onRefresh: async () => {
4284
+ const fresh = await refreshUsage(activeRegion);
4285
+ if (fresh !== void 0) setStatusByRegion((current) => ({
4286
+ ...current,
4287
+ [activeRegion]: fresh
4288
+ }));
4289
+ }
4290
+ }) : null,
2775
4291
  status.checkin !== void 0 && !status.checkin.enabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2776
4292
  className: "dsm-trae-usage-error",
2777
4293
  children: t("row.checkinDisabled")
@@ -3859,6 +5375,151 @@ window.__ModuleLoader__.load({
3859
5375
  "row.modelReasoningSupported": "Reasoning: supported",
3860
5376
  "row.modelUnknown": "Unknown",
3861
5377
  "row.modelCapabilityPending": "Image input follows Trae's own multimodal flag, plus anything you tick yourself.",
5378
+ "row.poolTitle": "Account pool",
5379
+ "row.poolEnabled": "Enable the account pool",
5380
+ "row.poolEnabledHint": "The pool picks the account to bill for this panel's requests, ranking by credits and availability. Off means the manually bound account serves.",
5381
+ "row.poolCurrentHeader": "Billing now: {account}",
5382
+ "row.poolCurrentBadge": "serving",
5383
+ "row.poolMember": "in pool",
5384
+ "row.poolNotMember": "not in pool",
5385
+ "row.poolCheckinAll": "Check in all accounts",
5386
+ "row.poolBusyHint": "Working through accounts one at a time…",
5387
+ "row.poolTestAll": "Test all accounts",
5388
+ "row.poolTesting": "Testing…",
5389
+ "row.poolOffNotice": "The pool is off — the bound account is what serves. Turn the switch below on to let the pool decide.",
5390
+ "row.poolNoCandidate": "No usable pool member can serve this panel right now.",
5391
+ "row.poolNoCandidateEmpty": "No pool members are selected yet.",
5392
+ "row.poolNoCandidateEmptyHint": "Tick accounts in the membership list to bring them into the pool.",
5393
+ "row.poolNoCandidateUnusable": "Every selected member is unavailable right now.",
5394
+ "row.poolNoCandidateHint": "Traffic keeps landing on the saved selection until one recovers.",
5395
+ "row.poolMembersTitle": "Accounts in this pool",
5396
+ "row.poolRescan": "Re-detect accounts",
5397
+ "row.poolToolsLabel": "Export / import accounts",
5398
+ "row.poolCreditExpiry": "expires {when}",
5399
+ "row.poolProbeMeasured": "measured {when}",
5400
+ "row.poolProbeAt": "tested {when}",
5401
+ "row.poolSettingsNote": "Preferences below take effect when you save; test results are written by the plugin and are not affected by that button.",
5402
+ "row.poolSelectedCount": "Selected {count} / {total}",
5403
+ "row.poolSelectAll": "All",
5404
+ "row.poolSelectNone": "None",
5405
+ "row.poolColumnAccount": "Account",
5406
+ "row.poolColumnCredits": "Credits",
5407
+ "row.poolColumnProbe": "Target model",
5408
+ "row.poolColumnCheckin": "Check-in",
5409
+ "row.poolGeneralLabel": "General",
5410
+ "row.poolWorkHint": "Work credits are not spendable by DSH — only General powers chat.",
5411
+ "row.poolCreditNone": "—",
5412
+ "row.poolTestedAt": "tested {at}",
5413
+ "row.poolNeverTested": "not tested yet",
5414
+ "row.poolRetryAt": "retry {at}",
5415
+ "row.poolRetryUnknown": "retry time unknown",
5416
+ "row.poolTargetCurrentPreferred": "Current: your choice {model}",
5417
+ "row.poolTargetCurrentFree": "Current: free model {model}",
5418
+ "row.poolTargetStale": "Model {model} is no longer offered by this panel",
5419
+ "row.poolTargetStaleOption": "{model} (no longer offered)",
5420
+ "row.poolTargetStaleClear": "Back to automatic",
5421
+ "row.poolTargetNone": "No free model available for this panel",
5422
+ "row.poolTargetNoneHint": "The pool can only pick a free model; none resolves right now, so tests are disabled.",
5423
+ "row.poolActionNoteCheckinDevice": "This machine already used today's check-in (Trae allows one per device per day) — check-in is unavailable until tomorrow.",
5424
+ "row.poolActionNoteTestNone": "No free model resolves in this panel — pick a target model before testing.",
5425
+ "row.poolActionNoteNoMembers": "Tick the accounts you want in the pool and save first.",
5426
+ "row.poolTargetAutoOption": "Automatic (free model)",
5427
+ "row.poolTargetLabel": "Target test model",
5428
+ "row.poolTargetHint": "The model a probe test uses.",
5429
+ "row.poolProbeSizeLabel": "Probe request size",
5430
+ "row.poolProbeSizeHint": "How much text a test sends. A one-word probe only proves the account can answer a one-word request — if the upstream refuses long turns, this is where you find out. Bigger is closer to a real conversation, and costs more credit on every account in the batch.",
5431
+ "row.poolProbeSizeDefault": "Minimal (default)",
5432
+ "row.poolProbeSizeOption": "{wan}0k tokens",
5433
+ "row.poolSettingsTitle": "Pool settings",
5434
+ "row.poolManualAccountLabel": "Manually billed account",
5435
+ "row.poolManualAccountHint": "Used while the pool is off. With it on, the Host picks by credits.",
5436
+ "row.poolManualAccountNone": "No account in effect",
5437
+ "row.poolDirty": "unsaved",
5438
+ "row.poolSaved": "Save",
5439
+ "row.poolSaving": "Saving…",
5440
+ "row.poolDiscard": "Discard",
5441
+ "row.poolSaveFailed": "Save failed: {message}",
5442
+ "row.poolGhostMembers": "{count} saved member(s) no longer resolve to a local sign-in: {names}",
5443
+ "row.poolGhostRemove": "Remove {account}",
5444
+ "row.poolGhostRemoveHint": "Forget this panel's binding to {account}.",
5445
+ "row.poolUnsavedMembers": "Unsaved membership changes — Save below to apply.",
5446
+ "row.poolRowTest": "Test",
5447
+ "row.poolRowRemove": "Remove",
5448
+ "row.poolRowRemoveAria": "Remove {accountName} from this panel",
5449
+ "row.poolRowRemoving": "Removing…",
5450
+ "row.poolRowRemoved": "Removed {accountName} from this panel",
5451
+ "row.poolLogTitle": "Activity",
5452
+ "row.poolLogClear": "Clear",
5453
+ "row.poolLogEmpty": "No pool activity yet.",
5454
+ "row.poolLogCheckinStart": "Check-in started for {count} account(s).",
5455
+ "row.poolLogTestStart": "Test started for {count} account(s) against {model}.",
5456
+ "row.poolLogCheckinClaimed": "{accountName} checked in.",
5457
+ "row.poolLogCheckinAlready": "{accountName} already checked in today.",
5458
+ "row.poolLogCheckinFailed": "{accountName} check-in failed: {message}",
5459
+ "row.poolLogCheckinDone": "Check-in finished.",
5460
+ "row.poolLogTestRow": "{accountName}: {outcome}.",
5461
+ "row.poolLogTestRowMalformed": "{accountName}: malformed result.",
5462
+ "row.poolLogTestDone": "Test finished.",
5463
+ "row.poolLogBatchFailed": "Pool action failed: {message}",
5464
+ "row.poolSelectAria": "Include {accountName} in the pool",
5465
+ "row.poolCheckedIn": "Checked in",
5466
+ "row.poolNotCheckedIn": "Not checked in",
5467
+ "row.poolCheckinUnknown": "Unknown",
5468
+ "row.poolDeviceUsed": "device used",
5469
+ "row.poolTestingPerRow": "testing…",
5470
+ "row.poolProbeOk": "works",
5471
+ "row.poolProbeRateLimited": "rate limited",
5472
+ "row.poolProbeModelBusy": "model busy",
5473
+ "row.poolProbeOutOfCredit": "out of credit",
5474
+ "row.poolProbeCredentialRejected": "sign-in rejected",
5475
+ "row.poolProbePolicyRejected": "policy rejected",
5476
+ "row.poolProbePlanGated": "needs a higher plan",
5477
+ "row.poolProbeUnavailable": "unavailable",
5478
+ "row.poolProbeNotFound": "not served",
5479
+ "row.poolProbeFailed": "failed",
5480
+ "row.poolProbeSourceUnknown": "reason unknown",
5481
+ "row.poolProbeSourceLive": "from a live request",
5482
+ "row.poolProbeSourceTest": "from a test",
5483
+ "row.poolProbeModel": "model {model}",
5484
+ "row.poolProbeModelUnknown": "model unknown",
5485
+ "row.poolExcludedUnusable": "unusable",
5486
+ "row.poolTokenExpired": "sign-in expired",
5487
+ "row.poolExcludedRateLimited": "rate limited",
5488
+ "row.poolExcludedModelBusy": "model busy",
5489
+ "row.poolExcludedOutOfCredit": "out of credit",
5490
+ "row.poolExcludedCredentialRejected": "sign-in rejected",
5491
+ "row.poolExcludedPolicyRejected": "policy rejected",
5492
+ "row.poolExcludedPlanGated": "needs a higher plan",
5493
+ "row.poolExcludedNotFound": "not served",
5494
+ "row.poolExcludedUnavailable": "unavailable",
5495
+ "row.poolExcludedFailed": "failed",
5496
+ "row.poolAgoNow": "just now",
5497
+ "row.poolAgoMin": "{n} min ago",
5498
+ "row.poolAgoHour": "{n} h ago",
5499
+ "row.poolAgoDay": "{n} d ago",
5500
+ "row.poolErrOrigin": "the page is not the plugin's own page, so the request was refused",
5501
+ "row.poolErrUnavailable": "the pool is unavailable right now",
5502
+ "row.poolErrMethod": "the pool does not accept that request method",
5503
+ "row.poolErrBadRequest": "the pool refused the request",
5504
+ "row.poolErrNoMembers": "the pool has no members to act on",
5505
+ "row.poolErrNoLiveMembers": "every member is unavailable — nothing can serve",
5506
+ "row.poolErrCheckinUnsupported": "this panel does not offer a daily check-in",
5507
+ "row.poolErrTargetStale": "the saved target model is no longer offered",
5508
+ "row.poolErrNoFreeModel": "no free model is available for this panel",
5509
+ "row.poolErrConflict": "the pool refused the request",
5510
+ "row.poolErrGeneric": "pool request failed (HTTP {status})",
5511
+ "row.poolCheckingIn": "Checking in…",
5512
+ "row.poolSettingsHint": "The switch and the target model are preferences — save them. Test, check in and remove act at once.",
5513
+ "row.poolGhostRemoveAria": "Remove the saved but unreachable account {accountName} from this panel",
5514
+ "row.poolLogSaved": "Pool settings saved.",
5515
+ "row.poolLogSaveFailed": "Could not save the pool settings.",
5516
+ "row.poolLogNoMembers": "Nothing to do — no pool members are selected.",
5517
+ "row.poolLogDiscarded": "Discarded the unsaved pool changes.",
5518
+ "row.poolLogGeneric": "the pool answered nothing usable",
5519
+ "row.poolLogRowTesting": "Testing {accountName}…",
5520
+ "row.poolLogTargetCleared": "Returned the test target to automatic.",
5521
+ "row.poolRowRemoveFailed": "the pool did not confirm the removal",
5522
+ "row.poolRowRemoveError": "Could not remove {accountName}: {message}",
3862
5523
  "row.cheer": "Star on GitHub"
3863
5524
  };
3864
5525
  const zh = {
@@ -3869,7 +5530,7 @@ window.__ModuleLoader__.load({
3869
5530
  "row.tabCn": "国内版",
3870
5531
  "row.tabEnterprise": "企业版",
3871
5532
  "row.tabAi": "国际版",
3872
- "row.tabHint": "国内版、企业版、国际版是三个独立供应商,各有账号、用量与模型;三者同时生效,互不影响。",
5533
+ "row.tabHint": "国内版、企业版、国际版各有账号、用量与模型,可同时启用。企业版与国内版共用同一份 Trae CN 登录。",
3873
5534
  "row.tabEnterpriseHint": "企业版用同一个 Trae CN 账号走企业租户池计费,而不是个人积分池。它不会回落:被拒时如实报错,绝不悄悄花掉个人额度。",
3874
5535
  "row.tabSwitchHint": "关闭该供应商:其模型不再提供给 DSH;账号与设置保留,可随时恢复。",
3875
5536
  "row.tabSwitchAria": "是否向 DSH 提供{region}供应商",
@@ -4031,6 +5692,151 @@ window.__ModuleLoader__.load({
4031
5692
  "row.modelReasoningSupported": "推理:支持",
4032
5693
  "row.modelUnknown": "未知",
4033
5694
  "row.modelCapabilityPending": "图片输入跟随 Trae 自身的多模态标注,并叠加你手动勾选的模型。",
5695
+ "row.poolTitle": "账号池",
5696
+ "row.poolEnabled": "启用账号池",
5697
+ "row.poolEnabledHint": "账号池按积分与可用性挑选本面板请求的计费账号;关闭时由手动绑定的账号计费。",
5698
+ "row.poolCurrentHeader": "当前计费:{account}",
5699
+ "row.poolCurrentBadge": "派单中",
5700
+ "row.poolMember": "已在池中",
5701
+ "row.poolNotMember": "未入池",
5702
+ "row.poolCheckinAll": "一键签到所有账号",
5703
+ "row.poolBusyHint": "逐个处理账号中…",
5704
+ "row.poolTestAll": "一键测试所有账号",
5705
+ "row.poolTesting": "测速中…",
5706
+ "row.poolOffNotice": "账号池已关闭——由绑定的账号计费。开启上方开关即可让账号池派单。",
5707
+ "row.poolNoCandidate": "当前没有可用的池成员能为本面板提供请求。",
5708
+ "row.poolNoCandidateEmpty": "尚未勾选任何池成员。",
5709
+ "row.poolNoCandidateEmptyHint": "在成员列表中勾选账号即可加入账号池。",
5710
+ "row.poolNoCandidateUnusable": "所有已选成员当前均不可用。",
5711
+ "row.poolNoCandidateHint": "请求会继续落在已保存的账号上,直到有成员恢复可用。",
5712
+ "row.poolMembersTitle": "本池包含的账号",
5713
+ "row.poolRescan": "重新检测账号",
5714
+ "row.poolToolsLabel": "导出 / 导入账号",
5715
+ "row.poolCreditExpiry": "{when} 过期",
5716
+ "row.poolProbeMeasured": "测得 {when}",
5717
+ "row.poolProbeAt": "测试于 {when}",
5718
+ "row.poolSettingsNote": "下面是偏好设置:点保存才生效;测试结果由插件自动写入,不受这个保存按钮影响。",
5719
+ "row.poolSelectedCount": "已选 {count} / {total}",
5720
+ "row.poolSelectAll": "全部",
5721
+ "row.poolSelectNone": "清空",
5722
+ "row.poolColumnAccount": "账号",
5723
+ "row.poolColumnCredits": "积分",
5724
+ "row.poolColumnProbe": "目标模型",
5725
+ "row.poolColumnCheckin": "签到",
5726
+ "row.poolGeneralLabel": "通用",
5727
+ "row.poolWorkHint": "工作积分不能由 DSH 消费——只有通用积分用于对话。",
5728
+ "row.poolCreditNone": "—",
5729
+ "row.poolTestedAt": "测于 {at}",
5730
+ "row.poolNeverTested": "尚未测速",
5731
+ "row.poolRetryAt": "{at} 后重试",
5732
+ "row.poolRetryUnknown": "重试时间未知",
5733
+ "row.poolTargetCurrentPreferred": "当前:你选定的模型 {model}",
5734
+ "row.poolTargetCurrentFree": "当前:免费模型 {model}",
5735
+ "row.poolTargetStale": "模型 {model} 已不再由本面板提供",
5736
+ "row.poolTargetStaleOption": "{model}(已下架)",
5737
+ "row.poolTargetStaleClear": "改回自动",
5738
+ "row.poolTargetNone": "本面板暂无可用免费模型",
5739
+ "row.poolTargetNoneHint": "账号池只会挑免费模型;当前无法解析出可用模型,测速暂被禁用。",
5740
+ "row.poolActionNoteCheckinDevice": "本机今日的签到已用掉(Trae 按每台设备每天一次限制),明天才能再签。",
5741
+ "row.poolActionNoteTestNone": "本面板没有解析出免费模型——先指定一个目标模型再测速。",
5742
+ "row.poolActionNoteNoMembers": "先勾选要入池的账号并保存。",
5743
+ "row.poolTargetAutoOption": "自动(免费模型)",
5744
+ "row.poolTargetLabel": "目标测速模型",
5745
+ "row.poolTargetHint": "探针测速所用的模型。",
5746
+ "row.poolProbeSizeLabel": "测试请求体积",
5747
+ "row.poolProbeSizeHint": "测试时发送多少文本。只发一个词的探针,只能证明「这个账号能回答一个词」——如果上游拒绝长请求,问题只会在你真实对话时才出现。填得越多越接近真实对话,每个账号每次测试消耗的积分也越多。",
5748
+ "row.poolProbeSizeDefault": "最小请求(默认)",
5749
+ "row.poolProbeSizeOption": "{wan} 万 token",
5750
+ "row.poolSettingsTitle": "账号池设置",
5751
+ "row.poolManualAccountLabel": "手动计费账号",
5752
+ "row.poolManualAccountHint": "账号池关闭时由它计费;开启后由 Host 按积分挑选。",
5753
+ "row.poolManualAccountNone": "当前无生效账号",
5754
+ "row.poolDirty": "未保存",
5755
+ "row.poolSaved": "保存",
5756
+ "row.poolSaving": "保存中…",
5757
+ "row.poolDiscard": "放弃",
5758
+ "row.poolSaveFailed": "保存失败:{message}",
5759
+ "row.poolGhostMembers": "{count} 个已保存成员不再对应本机登录:{names}",
5760
+ "row.poolGhostRemove": "移除 {account}",
5761
+ "row.poolGhostRemoveHint": "忘记本面板对 {account} 的绑定。",
5762
+ "row.poolUnsavedMembers": "成员改动尚未保存——请点击下方保存。",
5763
+ "row.poolRowTest": "测速",
5764
+ "row.poolRowRemove": "移除",
5765
+ "row.poolRowRemoveAria": "从本面板移除 {accountName}",
5766
+ "row.poolRowRemoving": "移除中…",
5767
+ "row.poolRowRemoved": "已从本面板移除 {accountName}",
5768
+ "row.poolLogTitle": "活动记录",
5769
+ "row.poolLogClear": "清空",
5770
+ "row.poolLogEmpty": "暂无账号池活动。",
5771
+ "row.poolLogCheckinStart": "开始为 {count} 个账号签到。",
5772
+ "row.poolLogTestStart": "开始为 {count} 个账号测速({model})。",
5773
+ "row.poolLogCheckinClaimed": "{accountName} 已签到。",
5774
+ "row.poolLogCheckinAlready": "{accountName} 今日已签到。",
5775
+ "row.poolLogCheckinFailed": "{accountName} 签到失败:{message}",
5776
+ "row.poolLogCheckinDone": "签到完成。",
5777
+ "row.poolLogTestRow": "{accountName}:{outcome}。",
5778
+ "row.poolLogTestRowMalformed": "{accountName}:结果异常。",
5779
+ "row.poolLogTestDone": "测速完成。",
5780
+ "row.poolLogBatchFailed": "账号池操作失败:{message}",
5781
+ "row.poolSelectAria": "把 {accountName} 加入账号池",
5782
+ "row.poolCheckedIn": "已签到",
5783
+ "row.poolNotCheckedIn": "未签到",
5784
+ "row.poolCheckinUnknown": "未知",
5785
+ "row.poolDeviceUsed": "本设备今日已用",
5786
+ "row.poolTestingPerRow": "测速中…",
5787
+ "row.poolProbeOk": "正常",
5788
+ "row.poolProbeRateLimited": "被限流",
5789
+ "row.poolProbeModelBusy": "模型繁忙",
5790
+ "row.poolProbeOutOfCredit": "积分不足",
5791
+ "row.poolProbeCredentialRejected": "登录凭据被拒",
5792
+ "row.poolProbePolicyRejected": "被策略拒绝",
5793
+ "row.poolProbePlanGated": "需要更高套餐",
5794
+ "row.poolProbeUnavailable": "不可用",
5795
+ "row.poolProbeNotFound": "上游不提供",
5796
+ "row.poolProbeFailed": "失败",
5797
+ "row.poolProbeSourceUnknown": "原因未知",
5798
+ "row.poolProbeSourceLive": "来自实时请求",
5799
+ "row.poolProbeSourceTest": "来自测速",
5800
+ "row.poolProbeModel": "模型 {model}",
5801
+ "row.poolProbeModelUnknown": "模型未知",
5802
+ "row.poolExcludedUnusable": "不可用",
5803
+ "row.poolTokenExpired": "登录已失效",
5804
+ "row.poolExcludedRateLimited": "被限流",
5805
+ "row.poolExcludedModelBusy": "模型繁忙",
5806
+ "row.poolExcludedOutOfCredit": "积分不足",
5807
+ "row.poolExcludedCredentialRejected": "登录凭据被拒",
5808
+ "row.poolExcludedPolicyRejected": "被策略拒绝",
5809
+ "row.poolExcludedPlanGated": "需要更高套餐",
5810
+ "row.poolExcludedNotFound": "上游不提供",
5811
+ "row.poolExcludedUnavailable": "不可用",
5812
+ "row.poolExcludedFailed": "失败",
5813
+ "row.poolAgoNow": "刚刚",
5814
+ "row.poolAgoMin": "{n} 分钟前",
5815
+ "row.poolAgoHour": "{n} 小时前",
5816
+ "row.poolAgoDay": "{n} 天前",
5817
+ "row.poolErrOrigin": "页面并非插件自身页面,请求因而不被接受",
5818
+ "row.poolErrUnavailable": "账号池当前不可用",
5819
+ "row.poolErrMethod": "账号池不接受该请求方法",
5820
+ "row.poolErrBadRequest": "账号池拒绝了该请求",
5821
+ "row.poolErrNoMembers": "账号池没有任何成员可处理",
5822
+ "row.poolErrNoLiveMembers": "所有成员均不可用——没有可派单的账号",
5823
+ "row.poolErrCheckinUnsupported": "本面板不提供每日签到",
5824
+ "row.poolErrTargetStale": "已保存的目标模型已下架",
5825
+ "row.poolErrNoFreeModel": "本面板没有可用的免费模型",
5826
+ "row.poolErrConflict": "账号池拒绝了该请求",
5827
+ "row.poolErrGeneric": "账号池请求失败(HTTP {status})",
5828
+ "row.poolCheckingIn": "签到中…",
5829
+ "row.poolSettingsHint": "开关与目标模型是偏好,改完要保存;行内的测速、签到与移除立即生效。",
5830
+ "row.poolGhostRemoveAria": "把已保存但本机无法登录的账号 {accountName} 移出本面板",
5831
+ "row.poolLogSaved": "账号池设置已保存。",
5832
+ "row.poolLogSaveFailed": "无法保存账号池设置。",
5833
+ "row.poolLogNoMembers": "没有可处理的内容——尚未勾选任何池成员。",
5834
+ "row.poolLogDiscarded": "已放弃未保存的账号池修改。",
5835
+ "row.poolLogGeneric": "账号池未返回可用的结果",
5836
+ "row.poolLogRowTesting": "正在测速 {accountName}…",
5837
+ "row.poolLogTargetCleared": "测速目标已改回自动。",
5838
+ "row.poolRowRemoveFailed": "账号池未确认移除",
5839
+ "row.poolRowRemoveError": "无法移除 {accountName}:{message}",
4034
5840
  "row.cheer": "鼓励一下"
4035
5841
  };
4036
5842
  //#endregion