dsh-workbuddy-xdpool 1.6.1 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -5,7 +5,6 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react = require("react");
8
- let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
8
  let react_jsx_runtime = require("react/jsx-runtime");
10
9
  //#region src/status-paths.ts
11
10
  /**
@@ -28,10 +27,39 @@ window.__ModuleLoader__.load({
28
27
  const POOL_RESET_COOLDOWN_PATH = "/plugins/dsh-workbuddy-xdpool/cooldowns/reset";
29
28
  /** Plugin-owned daily check-in action endpoint (claim today's reward). */
30
29
  const POOL_CHECKIN_PATH = "/plugins/dsh-workbuddy-xdpool/checkin";
30
+ /**
31
+ * Throw one account out of the pool for good, or take it back.
32
+ *
33
+ * Separate from the disable route because the semantics differ: disabling is a
34
+ * rotation preference the account survives, ignoring survives the account.
35
+ */
36
+ const POOL_ACCOUNT_IGNORE_PATH = "/plugins/dsh-workbuddy-xdpool/accounts/ignored";
31
37
  /** Run one automation job immediately, so the card can verify it on demand. */
32
38
  const POOL_AUTOMATION_RUN_PATH = "/plugins/dsh-workbuddy-xdpool/automation/run";
33
39
  /** Set or clear one account's reserved-credit floor. */
34
40
  const POOL_CREDIT_RESERVE_PATH = "/plugins/dsh-workbuddy-xdpool/accounts/credit-reserve";
41
+ /**
42
+ * The schedule every automation job falls back to.
43
+ *
44
+ * Shared by both halves on purpose. The host uses it when a configured hour
45
+ * list arrives empty (the settings schema materializes "never configured" into
46
+ * `[]`), and the card uses it when it writes the `automation` block back, so a
47
+ * document that already holds an empty list is healed instead of being saved
48
+ * back as an unrunnable schedule.
49
+ *
50
+ * This lives here rather than in `scheduler.ts` because the browser half cannot
51
+ * import the host module: `scheduler.ts` pulls in `node:crypto` and the whole
52
+ * upstream client, none of which exists in the browser bundle. Two hand-written
53
+ * copies would drift, and the drift is invisible — the card would write a
54
+ * schedule the scheduler does not run.
55
+ */
56
+ const DEFAULT_AUTOMATION_HOURS = {
57
+ checkin: [9],
58
+ report: [10],
59
+ tasks: [11],
60
+ streak: [12],
61
+ travel: [9, 21]
62
+ };
35
63
  //#endregion
36
64
  //#region src/client/icon.ts
37
65
  /**
@@ -64,28 +92,38 @@ window.__ModuleLoader__.load({
64
92
  * naturally next to the other configuration rows instead of looking like a
65
93
  * bright Google-Material block on top of DSH's dark surface.
66
94
  *
67
- * The collapsible shell mirrors dingminhua/dsh-connect-trae (which itself
68
- * borrows from the LaoDing plugin family) so the row header behaves exactly
69
- * like the built-in cards next to it; the inner workbuddy-specific classes
70
- * are renamed to `dsm-workbuddy-xdpool-*` to stay namespaced.
95
+ * The page renders at full height with no collapse affordance, and its content is
96
+ * split into cards: a header row with the primary actions, a status card (region
97
+ * switch + distribution), then one card per feature area. The inner
98
+ * workbuddy-specific classes are namespaced `dsm-workbuddy-xdpool-*`.
71
99
  *
72
100
  * @module dsh-workbuddy-xdpool/client/styles
73
101
  */
74
102
  const POOL_CARD_CSS = `
75
- /* Shell: same collapse affordance as every other plugin config row. */
76
- .dsm-plugin-card{border:1px solid var(--dsw-alias-border-l2,#36373b);background:var(--dsw-alias-bg-module-platform,#202126);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}
77
- .dsm-plugin-card:hover{border-color:var(--dsw-alias-label-dimmed,#777)}
78
- .dsm-plugin-card-open{background:var(--dsw-alias-bg-layer-2,#25262b);border-color:var(--dsw-alias-label-dimmed,#777)}
79
- .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}
80
- .dsm-plugin-card-header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:-2px}
81
- .dsm-plugin-card-head{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}
82
- .dsm-plugin-card-title{color:var(--dsw-alias-label-primary,#e6e6e6);font-size:15px;font-weight:600;line-height:1.4}
83
- .dsm-plugin-card-description{color:var(--dsw-alias-label-tertiary,#999);font-size:13px;line-height:1.5}
84
- .dsm-plugin-card-chevron{color:var(--dsw-alias-label-tertiary,#999);flex:none;display:inline-flex;transition:transform .16s}
85
- .dsm-plugin-card-chevron-open{transform:rotate(180deg)}
86
- .dsm-plugin-card-body{border-top:1px solid var(--dsw-alias-border-l2,#36373b);margin:0 16px;padding:0 0 8px}
87
- .dsm-plugin-card-icon{width:32px;height:32px;flex:none;border-radius:7px}
88
-
103
+ /*
104
+ * Page shell. The settings shell renders this inside its own scrollable content
105
+ * column, so the page supplies only the rhythm and the cards — no outer border
106
+ * and no extra margin (the column pads its own edges).
107
+ */
108
+ .dsm-workbuddy-xdpool-page{max-width:860px;flex-direction:column;gap:16px;display:flex}
109
+ /*
110
+ * Header row: identity on the left, primary actions on the right. Wraps so a
111
+ * narrow panel drops the buttons below the title instead of squeezing it.
112
+ */
113
+ .dsm-workbuddy-xdpool-page-head{align-items:center;gap:12px;flex-wrap:wrap;display:flex}
114
+ .dsm-workbuddy-xdpool-page-icon{width:32px;height:32px;flex:none;border-radius:7px}
115
+ .dsm-workbuddy-xdpool-page-copy{flex-direction:column;gap:4px;min-width:0;flex:1 1 220px;display:flex}
116
+ .dsm-workbuddy-xdpool-page-title{color:var(--dsw-alias-label-primary,#e6e6e6);margin:0;font-size:16px;font-weight:600;line-height:1.4}
117
+ .dsm-workbuddy-xdpool-page-desc{color:var(--dsw-alias-label-tertiary,#999);margin:0;font-size:13px;line-height:1.5}
118
+ .dsm-workbuddy-xdpool-page-actions{align-items:center;gap:8px;flex-wrap:wrap;flex:none;display:flex}
119
+ /*
120
+ * One feature area per card. The surface separates the sections the way the
121
+ * built-in settings pages do; without it every panel ran together into a single
122
+ * column of text with no visible boundary.
123
+ */
124
+ .dsm-workbuddy-xdpool-card{border:1px solid var(--dsw-alias-border-l2,#36373b);background:var(--dsw-alias-bg-layer-2,#232529);border-radius:14px;flex-direction:column;gap:12px;padding:14px 16px;display:flex}
125
+ /* Status card: the region switch sits above the state line, then the switch. */
126
+ .dsm-workbuddy-xdpool-status{gap:14px}
89
127
  /* Reusable button primitives shared with the rest of the card body. */
90
128
  .dsm-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid transparent;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}
91
129
  .dsm-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
@@ -95,12 +133,10 @@ window.__ModuleLoader__.load({
95
133
  .dsm-btn-primary{background:var(--dsw-alias-label-primary,#e6e6e6);color:var(--dsw-alias-bg-layer-3,#202126)}
96
134
  .dsm-btn-primary:hover:not(:disabled){opacity:.9}
97
135
 
98
- /* Body layout: status row + accounts list + models list. */
99
- .dsm-workbuddy-xdpool-usage{display:flex;flex-direction:column;gap:14px;margin:0;padding:14px 0 4px}
100
136
  /* Region tabs: two independent suppliers, one shown at a time. */
101
137
  .dsm-workbuddy-xdpool-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)}
102
138
  /* Empty-region guide: steps for signing in on the other gateway. */
103
- .dsm-workbuddy-xdpool-empty{display:flex;flex-direction:column;gap:10px;padding:14px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#24262c)}
139
+ .dsm-workbuddy-xdpool-empty{gap:10px}
104
140
  .dsm-workbuddy-xdpool-empty-title{margin:0;color:var(--dsw-alias-label-primary,#e6e6e6);font-size:14px;font-weight:600;line-height:20px}
105
141
  .dsm-workbuddy-xdpool-empty-steps{display:flex;flex-direction:column;gap:6px;padding:12px;border-radius:10px;background:var(--dsw-alias-bg-layer-3,#2a2c33)}
106
142
  .dsm-workbuddy-xdpool-empty-steps-title{margin:0;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px;font-weight:600;line-height:18px}
@@ -114,14 +150,14 @@ window.__ModuleLoader__.load({
114
150
  .dsm-workbuddy-xdpool-tab-dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:6px;vertical-align:baseline;background:var(--dsw-alias-state-success-primary,#22a06b)}
115
151
  .dsm-workbuddy-xdpool-tab-dot[data-state="error"]{background:var(--dsw-alias-state-error-primary,#ef4444)}
116
152
  .dsm-workbuddy-xdpool-tab-dot[data-state="idle"]{background:var(--dsw-alias-label-dimmed,#9aa0a6)}
117
- .dsm-workbuddy-xdpool-usage-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap}
153
+ .dsm-workbuddy-xdpool-usage-head{display:flex;flex-direction:column;gap:12px;min-width:0}
118
154
  .dsm-workbuddy-xdpool-usage-copy{display:flex;flex-direction:column;gap:3px;min-width:0}
119
- .dsm-workbuddy-xdpool-usage-status{display:flex;align-items:center;gap:10px;font-size:15px;font-weight:500;color:var(--dsw-alias-label-primary,#e6e6e6)}
155
+ .dsm-workbuddy-xdpool-usage-status{display:flex;align-items:center;gap:10px;font-size:15px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6)}
120
156
  .dsm-workbuddy-xdpool-usage-dot{width:9px;height:9px;border-radius:50%;flex:0 0 auto}
121
- .dsm-workbuddy-xdpool-usage-hint{padding-left:19px;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:12px;line-height:18px}
157
+ .dsm-workbuddy-xdpool-usage-hint{margin:0;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:12px;line-height:18px}
122
158
  /* Distribution switch: priority (drain one) vs round-robin (spread). */
123
- .dsm-workbuddy-xdpool-dist{display:flex;flex-direction:column;gap:6px;padding-left:19px;margin-top:8px}
124
- .dsm-workbuddy-xdpool-dist-title{color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:12px;line-height:18px}
159
+ .dsm-workbuddy-xdpool-dist{display:flex;flex-direction:column;gap:7px}
160
+ .dsm-workbuddy-xdpool-dist-title{color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:11px;line-height:16px;letter-spacing:.03em;text-transform:uppercase;font-weight:600}
125
161
  .dsm-workbuddy-xdpool-dist-option{appearance:none;font:inherit;cursor:pointer;text-align:left;display:flex;flex-direction:column;justify-content:center;gap:2px;min-height:44px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:8px;padding:6px 10px;background:transparent;color:var(--dsw-alias-label-tertiary,#9aa0a8);transition:color .16s,border-color .16s,background .16s}
126
162
  .dsm-workbuddy-xdpool-dist-option:hover:not(:disabled):not(.dsm-workbuddy-xdpool-dist-option-active){color:var(--dsw-alias-label-primary,#e6e6e6);border-color:var(--dsw-alias-label-dimmed,#777)}
127
163
  .dsm-workbuddy-xdpool-dist-option:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
@@ -133,7 +169,7 @@ window.__ModuleLoader__.load({
133
169
  .dsm-workbuddy-xdpool-usage-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
134
170
 
135
171
  /* Account list (each account = a labeled subpanel, same as dingminhua). */
136
- .dsm-workbuddy-xdpool-accounts{display:flex;flex-direction:column;gap:14px;border-top:1px solid var(--dsw-alias-border-l2,#36373b);padding-top:14px}
172
+ .dsm-workbuddy-xdpool-accounts{gap:14px}
137
173
  .dsm-workbuddy-xdpool-accounts-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
138
174
  /* "In use now": answers which account is serving without scanning every row. */
139
175
  /* A hairline + tint reads as status; a filled bar would read as a call to action. */
@@ -161,6 +197,23 @@ window.__ModuleLoader__.load({
161
197
  .dsm-workbuddy-xdpool-account-toggle:disabled{cursor:default;opacity:.6}
162
198
  .dsm-workbuddy-xdpool-account-toggle-on{background:color-mix(in oklab, var(--dsw-alias-state-success-primary,#22a06b) 14%, transparent);border-color:color-mix(in oklab, var(--dsw-alias-state-success-primary,#22a06b) 55%, transparent);color:var(--dsw-alias-state-success-primary,#22a06b);font-weight:600}
163
199
  .dsm-workbuddy-xdpool-account-toggle-dot{width:6px;height:6px;border-radius:50%;flex:none;background:currentColor;opacity:.9}
200
+ /* "Remove" button: destructive and reversible, so it is quiet until hovered. */
201
+ .dsm-workbuddy-xdpool-account-ignore{appearance:none;font:inherit;flex:none;cursor:pointer;border:1px solid transparent;border-radius:999px;padding:3px 10px;font-size:11px;line-height:17px;background:transparent;color:var(--dsw-alias-label-tertiary,#9aa0a8);transition:color .16s,border-color .16s,background .16s}
202
+ .dsm-workbuddy-xdpool-account-ignore:hover:not(:disabled){color:var(--dsw-alias-state-error-primary,#ef4444);border-color:color-mix(in oklab, var(--dsw-alias-state-error-primary,#ef4444) 45%, transparent);background:color-mix(in oklab, var(--dsw-alias-state-error-primary,#ef4444) 10%, transparent)}
203
+ .dsm-workbuddy-xdpool-account-ignore:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
204
+ .dsm-workbuddy-xdpool-account-ignore:disabled{cursor:default;opacity:.6}
205
+ /* Ignored-account list: visible proof that "remove" can be undone. */
206
+ .dsm-workbuddy-xdpool-ignored-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;padding:12px 16px;border-bottom:1px solid color-mix(in oklab, var(--dsw-alias-border-l2,#3a3d45) 45%, transparent)}
207
+ .dsm-workbuddy-xdpool-ignored-title{margin:0;color:var(--dsw-alias-label-primary,#e6e6e6);font-size:13px;font-weight:600;line-height:19px}
208
+ .dsm-workbuddy-xdpool-ignored-summary{margin:0;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:11px;line-height:17px}
209
+ .dsm-workbuddy-xdpool-ignored-list{display:flex;flex-direction:column}
210
+ .dsm-workbuddy-xdpool-ignored-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 16px}
211
+ .dsm-workbuddy-xdpool-ignored-row+.dsm-workbuddy-xdpool-ignored-row{border-top:1px solid color-mix(in oklab, var(--dsw-alias-border-l2,#3a3d45) 30%, transparent)}
212
+ .dsm-workbuddy-xdpool-ignored-label{color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px;line-height:18px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
213
+ .dsm-workbuddy-xdpool-ignored-restore{appearance:none;font:inherit;flex:none;cursor:pointer;border:1px solid color-mix(in oklab, var(--dsw-alias-border-l2,#3a3d45) 70%, transparent);border-radius:999px;padding:3px 10px;font-size:11px;line-height:17px;background:transparent;color:var(--dsw-alias-label-secondary,#c6c9d0);transition:color .16s,border-color .16s}
214
+ .dsm-workbuddy-xdpool-ignored-restore:hover:not(:disabled){color:var(--dsw-alias-label-primary,#e6e6e6);border-color:var(--dsw-alias-label-dimmed,#777)}
215
+ .dsm-workbuddy-xdpool-ignored-restore:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
216
+ .dsm-workbuddy-xdpool-ignored-restore:disabled{cursor:default;opacity:.6}
164
217
  .dsm-workbuddy-xdpool-account-tags{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
165
218
  .dsm-workbuddy-xdpool-account-tag{padding:1px 8px;border-radius:999px;font-size:11px;line-height:18px;background:var(--dsw-alias-state-success-subtle,rgba(34,160,107,.12));color:var(--dsw-alias-state-success-primary,#22a06b)}
166
219
  .dsm-workbuddy-xdpool-account-tag-cooling{background:var(--dsw-alias-state-warning-subtle,rgba(217,119,6,.15));color:var(--dsw-alias-state-warning-primary,#d97706)}
@@ -196,7 +249,7 @@ window.__ModuleLoader__.load({
196
249
  .dsm-workbuddy-xdpool-total-value{color:var(--dsw-alias-state-success-primary,#22a06b);font-size:28px;line-height:32px;font-weight:800;letter-spacing:-.02em;white-space:nowrap;font-variant-numeric:tabular-nums}
197
250
 
198
251
  /* Model directory list. */
199
- .dsm-workbuddy-xdpool-models{display:flex;flex-direction:column;gap:10px;border-top:1px solid var(--dsw-alias-border-l2,#36373b);padding-top:14px}
252
+ .dsm-workbuddy-xdpool-models{gap:10px}
200
253
  .dsm-workbuddy-xdpool-models-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
201
254
  .dsm-workbuddy-xdpool-models-title{margin:0;color:var(--dsw-alias-label-primary,#e6e6e6);font-size:14px;font-weight:600;line-height:20px}
202
255
  .dsm-workbuddy-xdpool-models-summary{margin:2px 0 0;color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px}
@@ -225,6 +278,12 @@ window.__ModuleLoader__.load({
225
278
  .dsm-workbuddy-xdpool-models-actions{display:flex;align-items:center;gap:8px;flex:none}
226
279
 
227
280
 
281
+ /* Automation: run-now button and the per-job progress line. */
282
+ .dsm-workbuddy-xdpool-auto-run{flex:none}
283
+ .dsm-workbuddy-xdpool-auto-job-detail{grid-column:1/-1;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:11px;line-height:16px}
284
+ /* Credit packages panel heading inside the right-hand column. */
285
+ .dsm-workbuddy-xdpool-panel-packages{display:flex;flex-direction:column;gap:7px;min-width:0}
286
+
228
287
  /* Check-in docked under the total, inside the right-hand panel. */
229
288
  .dsm-workbuddy-xdpool-checkin{display:flex;flex-direction:column;align-items:center;gap:7px;width:100%;margin-top:10px;padding-top:11px;border-top:1px solid var(--dsw-alias-border-l2,#36373b)}
230
289
  .dsm-workbuddy-xdpool-checkin-meta{display:flex;flex-direction:column;align-items:center;gap:2px;width:100%}
@@ -305,8 +364,9 @@ window.__ModuleLoader__.load({
305
364
  * token expiry / cooldown info / credit packages, then the model directory
306
365
  * with per-model free/limited/night/image badges and context size.
307
366
  *
308
- * The outer shell reuses the host's `dsm-plugin-card*` classes so the
309
- * collapse affordance is identical to every other plugin configuration row.
367
+ * Rendered as a full settings page (see `client/index.tsx`): the shell gives it
368
+ * a left-nav row and a scrollable content column, so everything is visible at
369
+ * once — a fold inside the page only hid the pool behind a click.
310
370
  *
311
371
  * @module dsh-workbuddy-xdpool/client/PoolCard
312
372
  */
@@ -377,6 +437,17 @@ window.__ModuleLoader__.load({
377
437
  "streak",
378
438
  "travel"
379
439
  ];
440
+ /**
441
+ * The hour list to save for one job: what the card holds, or the default.
442
+ *
443
+ * Mirrors the host's own fallback (`hoursOrDefault` in `scheduler.ts`). An
444
+ * empty list must resolve to the default on BOTH sides, or the card would save
445
+ * a schedule the scheduler then refuses to run — the exact silent standstill
446
+ * this pair of fixes exists to remove.
447
+ */
448
+ function hoursOrDefault(configured, fallback) {
449
+ return configured !== void 0 && configured.length > 0 ? [...configured] : [...fallback];
450
+ }
380
451
  /** Read one job's configured hours off the status document. */
381
452
  function automationHours(status, kind) {
382
453
  const automation = status.automation;
@@ -459,9 +530,21 @@ window.__ModuleLoader__.load({
459
530
  const days = daysUntil(pack.expiresAtMs);
460
531
  return days !== void 0 && days <= 3;
461
532
  }
533
+ /**
534
+ * The promo badge for one model, or undefined when it has none.
535
+ *
536
+ * `free` is read off the CREDIT MULTIPLIER, not off a tag. Neither gateway ever
537
+ * sends a literal `free` tag: the global roster marks its zero-cost models
538
+ * (`hy3`, `hy4-preview-f`, `deepseek-v4.1-flash`) as `credits: "x0.00"`, so
539
+ * waiting for a tag meant the badge NEVER appeared — including on models that
540
+ * genuinely cost nothing.
541
+ *
542
+ * An explicit free-ish tag still wins when present, so a gateway that starts
543
+ * tagging them keeps working without another change.
544
+ */
462
545
  function tagFor(model) {
463
546
  const tags = model.tags ?? [];
464
- if (tags.includes("free")) return "free";
547
+ if (tags.includes("free") || model.multiplier === 0) return "free";
465
548
  if (tags.includes("limited-free")) return "limited";
466
549
  if (tags.includes("night-discount")) return "night";
467
550
  }
@@ -470,7 +553,6 @@ window.__ModuleLoader__.load({
470
553
  const settingsWritable = settingsScope?.getSnapshot().writable === true;
471
554
  /** Which region tab is showing. A CN-only install never leaves this. */
472
555
  const [activeRegion, setActiveRegion] = (0, react.useState)("cn");
473
- const [open, setOpen] = (0, react.useState)(false);
474
556
  /**
475
557
  * Last-known status per region. Kept per region (not a single slot) so
476
558
  * switching tabs shows the other side's last answer immediately instead of
@@ -568,7 +650,6 @@ window.__ModuleLoader__.load({
568
650
  }
569
651
  }, []);
570
652
  (0, react.useEffect)(() => {
571
- if (!open) return;
572
653
  const controller = new AbortController();
573
654
  refresh(activeRegion, controller.signal);
574
655
  const timer = window.setInterval(() => {
@@ -578,11 +659,7 @@ window.__ModuleLoader__.load({
578
659
  window.clearInterval(timer);
579
660
  controller.abort();
580
661
  };
581
- }, [
582
- open,
583
- refresh,
584
- activeRegion
585
- ]);
662
+ }, [refresh, activeRegion]);
586
663
  const rescan = async () => {
587
664
  setBusy(true);
588
665
  setFlash(void 0);
@@ -679,6 +756,37 @@ window.__ModuleLoader__.load({
679
756
  if (mounted.current) setAccountBusyId(void 0);
680
757
  }
681
758
  };
759
+ /**
760
+ * Throw one account out of the pool for good, or take it back.
761
+ *
762
+ * The host owns the ignore file, so this is a plain route call: no settings
763
+ * scope is involved, which is also why it keeps working on a profile whose
764
+ * settings section is read-only.
765
+ */
766
+ const setAccountIgnored = async (accountId, ignored) => {
767
+ setAccountBusyId(accountId);
768
+ setFlash(void 0);
769
+ try {
770
+ const response = await fetch(POOL_ACCOUNT_IGNORE_PATH, {
771
+ method: "POST",
772
+ headers: { "Content-Type": "application/json" },
773
+ body: JSON.stringify({
774
+ accountId,
775
+ ignored
776
+ })
777
+ });
778
+ if (!response.ok) {
779
+ const detail = await response.json().catch(() => ({}));
780
+ throw new Error(detail.error ?? `HTTP ${response.status}`);
781
+ }
782
+ await refresh(activeRegion);
783
+ } catch (cause) {
784
+ const message = cause instanceof Error ? cause.message : String(cause);
785
+ if (mounted.current) setError(t?.("row.accountIgnoreError", { message }) ?? "Could not change the ignore list: " + message);
786
+ } finally {
787
+ if (mounted.current) setAccountBusyId(void 0);
788
+ }
789
+ };
682
790
  /** Keep the draft in step with the server copy while nothing is dirty. */
683
791
  const modelDraft = draft ?? (status === void 0 ? {} : draftFromStatus(status));
684
792
  /** Model edits need a writable settings scope; otherwise the rows are read-only. */
@@ -782,12 +890,11 @@ window.__ModuleLoader__.load({
782
890
  setFlash(void 0);
783
891
  try {
784
892
  await write.call(settingsScope, "automation", {
785
- ...existing === void 0 ? {} : {
786
- checkinHours: [...existing.checkinHours],
787
- reportHours: [...existing.reportHours],
788
- taskHours: [...existing.taskHours],
789
- streakHours: [...existing.streakHours]
790
- },
893
+ checkinHours: hoursOrDefault(existing?.checkinHours, DEFAULT_AUTOMATION_HOURS.checkin),
894
+ reportHours: hoursOrDefault(existing?.reportHours, DEFAULT_AUTOMATION_HOURS.report),
895
+ taskHours: hoursOrDefault(existing?.taskHours, DEFAULT_AUTOMATION_HOURS.tasks),
896
+ streakHours: hoursOrDefault(existing?.streakHours, DEFAULT_AUTOMATION_HOURS.streak),
897
+ travelHours: hoursOrDefault(existing?.travelHours, DEFAULT_AUTOMATION_HOURS.travel),
791
898
  enabled
792
899
  });
793
900
  await refresh(activeRegion);
@@ -929,396 +1036,437 @@ window.__ModuleLoader__.load({
929
1036
  const stateLabel = error !== void 0 ? t?.("row.requestFailed") ?? "Request failed" : accountCount === 0 ? t?.("row.regionEmpty") ?? t?.("row.poolEmpty") ?? "No account yet" : state === "ok" ? t?.("row.ok") ?? "Healthy" : t?.("row.allCooling") ?? "All cooling";
930
1037
  const shimRunning = status?.shim.running === true;
931
1038
  const shimHint = status === void 0 ? null : shimRunning ? `${t?.("row.shimRunning") ?? "Provider listening"}${status.shim.baseUrl === void 0 ? "" : ` · ${status.shim.baseUrl}`}` : t?.("row.shimStopped") ?? "Provider loopback not running";
932
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
933
- className: `dsm-plugin-card${open ? " dsm-plugin-card-open" : ""}`,
934
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
935
- type: "button",
936
- className: "dsm-plugin-card-header",
937
- "aria-expanded": open,
938
- "aria-label": `${t?.(open ? "row.collapse" : "row.expand") ?? ""}: ${title}`,
939
- onClick: () => {
940
- setOpen(!open);
941
- },
942
- children: [
943
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
944
- className: "dsm-plugin-card-icon",
945
- src: POOL_PLUGIN_ICON,
946
- alt: ""
947
- }),
948
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
949
- className: "dsm-plugin-card-head",
950
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
951
- className: "dsm-plugin-card-title",
952
- children: title
953
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
954
- className: "dsm-plugin-card-description",
955
- children: description
956
- })]
957
- }),
958
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
959
- "aria-hidden": "true",
960
- className: `dsm-plugin-card-chevron${open ? " dsm-plugin-card-chevron-open" : ""}`,
961
- children: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
962
- })
963
- ]
964
- }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
965
- className: "dsm-plugin-card-body",
966
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
967
- className: "dsm-workbuddy-xdpool-usage",
1039
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1040
+ className: "dsm-workbuddy-xdpool-page",
1041
+ children: [
1042
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
1043
+ className: "dsm-workbuddy-xdpool-page-head",
968
1044
  children: [
969
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
970
- className: "dsm-workbuddy-xdpool-tabs",
971
- role: "tablist",
972
- children: status?.regions.map((region) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1045
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
1046
+ className: "dsm-workbuddy-xdpool-page-icon",
1047
+ src: POOL_PLUGIN_ICON,
1048
+ alt: ""
1049
+ }),
1050
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1051
+ className: "dsm-workbuddy-xdpool-page-copy",
1052
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1053
+ className: "dsm-workbuddy-xdpool-page-title",
1054
+ children: title
1055
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1056
+ className: "dsm-workbuddy-xdpool-page-desc",
1057
+ children: description
1058
+ })]
1059
+ }),
1060
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1061
+ className: "dsm-workbuddy-xdpool-page-actions",
1062
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
973
1063
  type: "button",
974
- role: "tab",
975
- "aria-selected": region === activeRegion,
976
- className: `dsm-workbuddy-xdpool-tab${region === activeRegion ? " dsm-workbuddy-xdpool-tab-active" : ""}`,
1064
+ className: "dsm-btn dsm-btn-outline",
1065
+ disabled: busy,
977
1066
  onClick: () => {
978
- setActiveRegion(region);
1067
+ rescan();
979
1068
  },
980
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
981
- className: "dsm-workbuddy-xdpool-tab-dot",
982
- "data-state": state
983
- }), region === "cn" ? t?.("row.tabCn") ?? "CN" : t?.("row.tabGlobal") ?? "Global"]
984
- }, region))
985
- }),
1069
+ children: busy ? t?.("row.accountsScanning") ?? "Detecting…" : t?.("row.accountsRescan") ?? "Detect accounts again"
1070
+ }), cooling > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1071
+ type: "button",
1072
+ className: "dsm-btn dsm-btn-outline",
1073
+ disabled: cooldownBusy,
1074
+ onClick: () => {
1075
+ resetCooldowns();
1076
+ },
1077
+ children: cooldownBusy ? t?.("row.resetCooldownsBusy") ?? "Clearing…" : t?.("row.resetCooldowns") ?? "Clear all cooldowns"
1078
+ }) : null]
1079
+ })
1080
+ ]
1081
+ }),
1082
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1083
+ className: "dsm-workbuddy-xdpool-card dsm-workbuddy-xdpool-status",
1084
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1085
+ className: "dsm-workbuddy-xdpool-tabs",
1086
+ role: "tablist",
1087
+ children: status?.regions.map((region) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1088
+ type: "button",
1089
+ role: "tab",
1090
+ "aria-selected": region === activeRegion,
1091
+ className: `dsm-workbuddy-xdpool-tab${region === activeRegion ? " dsm-workbuddy-xdpool-tab-active" : ""}`,
1092
+ onClick: () => {
1093
+ setActiveRegion(region);
1094
+ },
1095
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1096
+ className: "dsm-workbuddy-xdpool-tab-dot",
1097
+ "data-state": state
1098
+ }), region === "cn" ? t?.("row.tabCn") ?? "CN" : t?.("row.tabGlobal") ?? "Global"]
1099
+ }, region))
1100
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1101
+ className: "dsm-workbuddy-xdpool-usage-head",
1102
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1103
+ className: "dsm-workbuddy-xdpool-usage-copy",
1104
+ role: "status",
1105
+ children: [
1106
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1107
+ className: "dsm-workbuddy-xdpool-usage-status",
1108
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1109
+ "aria-hidden": "true",
1110
+ className: "dsm-workbuddy-xdpool-usage-dot",
1111
+ style: { background: dotColor(state) }
1112
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: stateLabel })]
1113
+ }),
1114
+ accountCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1115
+ className: "dsm-workbuddy-xdpool-usage-hint",
1116
+ children: t?.("row.accountsSummary", {
1117
+ count: accountCount,
1118
+ cooling
1119
+ }) ?? `${accountCount} account(s) · ${cooling} cooling`
1120
+ }) : null,
1121
+ shimHint === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1122
+ className: "dsm-workbuddy-xdpool-usage-hint",
1123
+ children: shimHint
1124
+ }),
1125
+ status === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1126
+ className: "dsm-workbuddy-xdpool-dist",
1127
+ role: "radiogroup",
1128
+ "aria-label": t?.("row.distTitle") ?? "Account usage",
1129
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1130
+ className: "dsm-workbuddy-xdpool-dist-title",
1131
+ children: t?.("row.distTitle") ?? "Account usage"
1132
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1133
+ className: "dsm-workbuddy-xdpool-dist-options",
1134
+ children: [
1135
+ "priority",
1136
+ "balanced",
1137
+ "round-robin"
1138
+ ].map((option) => {
1139
+ const active = (status.distribution ?? "priority") === option;
1140
+ const label = option === "priority" ? t?.("row.distPriority") ?? "Priority" : option === "balanced" ? t?.("row.distBalanced") ?? "Balanced" : t?.("row.distRoundRobin") ?? "Round-robin";
1141
+ const hint = option === "priority" ? t?.("row.distPriorityHint") ?? "" : option === "balanced" ? t?.("row.distBalancedHint") ?? "" : t?.("row.distRoundRobinHint") ?? "";
1142
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1143
+ type: "button",
1144
+ role: "radio",
1145
+ "aria-checked": active,
1146
+ title: hint,
1147
+ disabled: !modelsEditable,
1148
+ className: "dsm-workbuddy-xdpool-dist-option" + (active ? " dsm-workbuddy-xdpool-dist-option-active" : ""),
1149
+ onClick: () => {
1150
+ setDistribution(option);
1151
+ },
1152
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1153
+ className: "dsm-workbuddy-xdpool-dist-option-name",
1154
+ children: label
1155
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1156
+ className: "dsm-workbuddy-xdpool-dist-option-hint",
1157
+ children: hint
1158
+ })]
1159
+ }, option);
1160
+ })
1161
+ })]
1162
+ })
1163
+ ]
1164
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1165
+ className: "dsm-workbuddy-xdpool-usage-actions",
1166
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1167
+ type: "button",
1168
+ className: "dsm-btn dsm-btn-outline",
1169
+ disabled: busy,
1170
+ onClick: () => {
1171
+ rescan();
1172
+ },
1173
+ children: busy ? t?.("row.accountsScanning") ?? "Detecting…" : t?.("row.accountsRescan") ?? "Detect accounts again"
1174
+ }), cooling > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1175
+ type: "button",
1176
+ className: "dsm-btn dsm-btn-outline",
1177
+ disabled: cooldownBusy,
1178
+ onClick: () => {
1179
+ resetCooldowns();
1180
+ },
1181
+ children: cooldownBusy ? t?.("row.resetCooldownsBusy") ?? "Clearing…" : t?.("row.resetCooldowns") ?? "Clear all cooldowns"
1182
+ }) : null]
1183
+ })]
1184
+ })]
1185
+ }),
1186
+ activeRegion !== "cn" || status?.automation === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1187
+ className: "dsm-workbuddy-xdpool-card dsm-workbuddy-xdpool-auto",
1188
+ "aria-label": t?.("row.autoTitle") ?? "Automation",
1189
+ children: [
986
1190
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
987
- className: "dsm-workbuddy-xdpool-usage-head",
988
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
989
- className: "dsm-workbuddy-xdpool-usage-copy",
990
- role: "status",
991
- children: [
992
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
993
- className: "dsm-workbuddy-xdpool-usage-status",
994
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
995
- "aria-hidden": "true",
996
- className: "dsm-workbuddy-xdpool-usage-dot",
997
- style: { background: dotColor(state) }
998
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: stateLabel })]
999
- }),
1000
- accountCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1001
- className: "dsm-workbuddy-xdpool-usage-hint",
1002
- children: t?.("row.accountsSummary", {
1003
- count: accountCount,
1004
- cooling
1005
- }) ?? `${accountCount} account(s) · ${cooling} cooling`
1006
- }) : null,
1007
- shimHint === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1008
- className: "dsm-workbuddy-xdpool-usage-hint",
1009
- children: shimHint
1010
- }),
1011
- status === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1012
- className: "dsm-workbuddy-xdpool-dist",
1013
- role: "radiogroup",
1014
- "aria-label": t?.("row.distTitle") ?? "Account usage",
1015
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1016
- className: "dsm-workbuddy-xdpool-dist-title",
1017
- children: t?.("row.distTitle") ?? "Account usage"
1018
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1019
- className: "dsm-workbuddy-xdpool-dist-options",
1020
- children: [
1021
- "priority",
1022
- "balanced",
1023
- "round-robin"
1024
- ].map((option) => {
1025
- const active = (status.distribution ?? "priority") === option;
1026
- const label = option === "priority" ? t?.("row.distPriority") ?? "Priority" : option === "balanced" ? t?.("row.distBalanced") ?? "Balanced" : t?.("row.distRoundRobin") ?? "Round-robin";
1027
- const hint = option === "priority" ? t?.("row.distPriorityHint") ?? "" : option === "balanced" ? t?.("row.distBalancedHint") ?? "" : t?.("row.distRoundRobinHint") ?? "";
1028
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1029
- type: "button",
1030
- role: "radio",
1031
- "aria-checked": active,
1032
- title: hint,
1033
- disabled: !modelsEditable,
1034
- className: "dsm-workbuddy-xdpool-dist-option" + (active ? " dsm-workbuddy-xdpool-dist-option-active" : ""),
1035
- onClick: () => {
1036
- setDistribution(option);
1037
- },
1038
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1039
- className: "dsm-workbuddy-xdpool-dist-option-name",
1040
- children: label
1041
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1042
- className: "dsm-workbuddy-xdpool-dist-option-hint",
1043
- children: hint
1044
- })]
1045
- }, option);
1046
- })
1047
- })]
1048
- })
1049
- ]
1050
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1051
- className: "dsm-workbuddy-xdpool-usage-actions",
1052
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1191
+ className: "dsm-workbuddy-xdpool-auto-head",
1192
+ children: [
1193
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1194
+ className: "dsm-workbuddy-xdpool-auto-title",
1195
+ children: t?.("row.autoTitle") ?? "Automation"
1196
+ }),
1197
+ !status.automation.enabled ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1053
1198
  type: "button",
1054
- className: "dsm-btn dsm-btn-outline",
1055
- disabled: busy,
1199
+ className: "dsm-btn dsm-btn-outline dsm-workbuddy-xdpool-auto-run",
1200
+ disabled: automationRun !== void 0,
1056
1201
  onClick: () => {
1057
- rescan();
1202
+ runAutomationJob();
1058
1203
  },
1059
- children: busy ? t?.("row.accountsScanning") ?? "Detecting…" : t?.("row.accountsRescan") ?? "Detect accounts again"
1060
- }), cooling > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1204
+ children: automationRun !== void 0 ? t?.("row.autoRunning") ?? "Running…" : t?.("row.autoRunAll") ?? "Run all now"
1205
+ }),
1206
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1061
1207
  type: "button",
1062
- className: "dsm-btn dsm-btn-outline",
1063
- disabled: cooldownBusy,
1208
+ role: "switch",
1209
+ "aria-checked": status.automation.enabled,
1210
+ disabled: !settingsWritable || automationBusy,
1211
+ className: `dsm-workbuddy-xdpool-auto-switch${status.automation.enabled ? " dsm-workbuddy-xdpool-auto-switch-on" : ""}`,
1064
1212
  onClick: () => {
1065
- resetCooldowns();
1213
+ setAutomationEnabled(!status.automation.enabled);
1066
1214
  },
1067
- children: cooldownBusy ? t?.("row.resetCooldownsBusy") ?? "Clearing…" : t?.("row.resetCooldowns") ?? "Clear all cooldowns"
1068
- }) : null]
1215
+ children: automationBusy ? t?.("row.autoBusy") ?? "Saving…" : status.automation.enabled ? t?.("row.autoOn") ?? "On" : t?.("row.autoOff") ?? "Off"
1216
+ })
1217
+ ]
1218
+ }),
1219
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1220
+ className: "dsm-workbuddy-xdpool-auto-hint",
1221
+ children: status.automation.enabled ? t?.("row.autoHintOn") ?? "Reports activity, claims task rewards and checks in once a day." : t?.("row.autoHintOff") ?? "Off: no background requests are made for you."
1222
+ }),
1223
+ automationTotals.credit === 0 && automationTotals.checkinCredit === 0 && automationTotals.bonusCredit === 0 && automationTotals.travelCredit === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1224
+ className: "dsm-workbuddy-xdpool-auto-total",
1225
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1226
+ className: "dsm-workbuddy-xdpool-auto-total-label",
1227
+ children: t?.("row.autoToday") ?? "Today"
1228
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1229
+ className: "dsm-workbuddy-xdpool-auto-total-list",
1230
+ children: [
1231
+ automationTotals.credit > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1232
+ className: "dsm-workbuddy-xdpool-auto-total-row",
1233
+ children: t?.("row.autoFromTasks", {
1234
+ credit: automationTotals.credit,
1235
+ energy: automationTotals.energy,
1236
+ count: automationTotals.claimed
1237
+ }) ?? `Tasks +${automationTotals.credit}`
1238
+ }) : null,
1239
+ automationTotals.checkinCredit > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1240
+ className: "dsm-workbuddy-xdpool-auto-total-row",
1241
+ children: t?.("row.autoFromCheckin", { credit: automationTotals.checkinCredit }) ?? `Check-in +${automationTotals.checkinCredit}`
1242
+ }) : null,
1243
+ automationTotals.bonusCredit > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1244
+ className: "dsm-workbuddy-xdpool-auto-total-row",
1245
+ children: t?.("row.autoFromBonus", { credit: automationTotals.bonusCredit }) ?? `Streak +${automationTotals.bonusCredit}`
1246
+ }) : null,
1247
+ automationTotals.travelCredit > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1248
+ className: "dsm-workbuddy-xdpool-auto-total-row",
1249
+ children: t?.("row.autoFromTravel", { credit: automationTotals.travelCredit }) ?? `Buddy +${automationTotals.travelCredit}`
1250
+ }) : null
1251
+ ]
1069
1252
  })]
1070
1253
  }),
1071
- activeRegion !== "cn" || status?.automation === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1072
- className: "dsm-workbuddy-xdpool-auto",
1073
- "aria-label": t?.("row.autoTitle") ?? "Automation",
1074
- children: [
1075
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1076
- className: "dsm-workbuddy-xdpool-auto-head",
1254
+ status.automation.enabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1255
+ className: "dsm-workbuddy-xdpool-auto-jobs",
1256
+ children: AUTOMATION_JOBS.map((kind) => {
1257
+ const job = automationJob(status, kind);
1258
+ const hours = automationHours(status, kind);
1259
+ const label = t?.(`row.autoJob_${kind}`) ?? kind;
1260
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1261
+ className: "dsm-workbuddy-xdpool-auto-job",
1077
1262
  children: [
1078
1263
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1079
- className: "dsm-workbuddy-xdpool-auto-title",
1080
- children: t?.("row.autoTitle") ?? "Automation"
1264
+ className: "dsm-workbuddy-xdpool-auto-job-name",
1265
+ children: label
1266
+ }),
1267
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1268
+ className: "dsm-workbuddy-xdpool-auto-job-when",
1269
+ children: hours.length === 0 ? t?.("row.autoHourNone") ?? "not scheduled" : hours.map((hour) => `${String(hour).padStart(2, "0")}:00`).join(" · ")
1270
+ }),
1271
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1272
+ className: "dsm-workbuddy-xdpool-auto-job-last",
1273
+ children: job?.lastRunDate === void 0 ? t?.("row.autoNever") ?? "not run yet" : `${job.lastRunDate} · ${job.ok}${job.failed > 0 ? `/${job.failed}` : ""}`
1081
1274
  }),
1082
- !status.automation.enabled ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1083
- type: "button",
1084
- className: "dsm-btn dsm-btn-outline dsm-workbuddy-xdpool-auto-run",
1085
- disabled: automationRun !== void 0,
1086
- onClick: () => {
1087
- runAutomationJob();
1088
- },
1089
- children: automationRun !== void 0 ? t?.("row.autoRunning") ?? "Running…" : t?.("row.autoRunAll") ?? "Run all now"
1275
+ job?.progress === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1276
+ className: "dsm-workbuddy-xdpool-auto-job-note",
1277
+ children: job.progress
1090
1278
  }),
1091
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1092
- type: "button",
1093
- role: "switch",
1094
- "aria-checked": status.automation.enabled,
1095
- disabled: !settingsWritable || automationBusy,
1096
- className: `dsm-workbuddy-xdpool-auto-switch${status.automation.enabled ? " dsm-workbuddy-xdpool-auto-switch-on" : ""}`,
1097
- onClick: () => {
1098
- setAutomationEnabled(!status.automation.enabled);
1099
- },
1100
- children: automationBusy ? t?.("row.autoBusy") ?? "Saving…" : status.automation.enabled ? t?.("row.autoOn") ?? "On" : t?.("row.autoOff") ?? "Off"
1279
+ job?.detail === void 0 || job.detail.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1280
+ className: "dsm-workbuddy-xdpool-auto-job-detail",
1281
+ children: job.detail.join(" · ")
1101
1282
  })
1102
1283
  ]
1103
- }),
1104
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1105
- className: "dsm-workbuddy-xdpool-auto-hint",
1106
- children: status.automation.enabled ? t?.("row.autoHintOn") ?? "Reports activity, claims task rewards and checks in once a day." : t?.("row.autoHintOff") ?? "Off: no background requests are made for you."
1107
- }),
1108
- automationTotals.credit === 0 && automationTotals.checkinCredit === 0 && automationTotals.bonusCredit === 0 && automationTotals.travelCredit === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1109
- className: "dsm-workbuddy-xdpool-auto-total",
1110
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1111
- className: "dsm-workbuddy-xdpool-auto-total-label",
1112
- children: t?.("row.autoToday") ?? "Today"
1113
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1114
- className: "dsm-workbuddy-xdpool-auto-total-list",
1115
- children: [
1116
- automationTotals.credit > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1117
- className: "dsm-workbuddy-xdpool-auto-total-row",
1118
- children: t?.("row.autoFromTasks", {
1119
- credit: automationTotals.credit,
1120
- energy: automationTotals.energy,
1121
- count: automationTotals.claimed
1122
- }) ?? `Tasks +${automationTotals.credit}`
1123
- }) : null,
1124
- automationTotals.checkinCredit > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1125
- className: "dsm-workbuddy-xdpool-auto-total-row",
1126
- children: t?.("row.autoFromCheckin", { credit: automationTotals.checkinCredit }) ?? `Check-in +${automationTotals.checkinCredit}`
1127
- }) : null,
1128
- automationTotals.bonusCredit > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1129
- className: "dsm-workbuddy-xdpool-auto-total-row",
1130
- children: t?.("row.autoFromBonus", { credit: automationTotals.bonusCredit }) ?? `Streak +${automationTotals.bonusCredit}`
1131
- }) : null,
1132
- automationTotals.travelCredit > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1133
- className: "dsm-workbuddy-xdpool-auto-total-row",
1134
- children: t?.("row.autoFromTravel", { credit: automationTotals.travelCredit }) ?? `Buddy +${automationTotals.travelCredit}`
1135
- }) : null
1136
- ]
1137
- })]
1138
- }),
1139
- status.automation.enabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1140
- className: "dsm-workbuddy-xdpool-auto-jobs",
1141
- children: AUTOMATION_JOBS.map((kind) => {
1142
- const job = automationJob(status, kind);
1143
- const hours = automationHours(status, kind);
1144
- const label = t?.(`row.autoJob_${kind}`) ?? kind;
1145
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1146
- className: "dsm-workbuddy-xdpool-auto-job",
1147
- children: [
1148
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1149
- className: "dsm-workbuddy-xdpool-auto-job-name",
1150
- children: label
1151
- }),
1152
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1153
- className: "dsm-workbuddy-xdpool-auto-job-when",
1154
- children: hours.length === 0 ? t?.("row.autoHourNone") ?? "not scheduled" : hours.map((hour) => `${String(hour).padStart(2, "0")}:00`).join(" · ")
1155
- }),
1156
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1157
- className: "dsm-workbuddy-xdpool-auto-job-last",
1158
- children: job?.lastRunDate === void 0 ? t?.("row.autoNever") ?? "not run yet" : `${job.lastRunDate} · ${job.ok}${job.failed > 0 ? `/${job.failed}` : ""}`
1159
- }),
1160
- job?.progress === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1161
- className: "dsm-workbuddy-xdpool-auto-job-note",
1162
- children: job.progress
1163
- }),
1164
- job?.detail === void 0 || job.detail.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1165
- className: "dsm-workbuddy-xdpool-auto-job-detail",
1166
- children: job.detail.join(" · ")
1167
- })
1168
- ]
1169
- }, kind);
1170
- })
1171
- }) : null
1172
- ]
1173
- }),
1174
- flash === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1175
- className: "dsm-workbuddy-xdpool-note",
1176
- children: flash
1284
+ }, kind);
1285
+ })
1286
+ }) : null
1287
+ ]
1288
+ }),
1289
+ flash === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1290
+ className: "dsm-workbuddy-xdpool-note",
1291
+ children: flash
1292
+ }),
1293
+ error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1294
+ className: "dsm-workbuddy-xdpool-error",
1295
+ children: t?.("row.error", { message: error }) ?? `Pool status unavailable: ${error}`
1296
+ }),
1297
+ accountCount === 0 && error === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1298
+ className: "dsm-workbuddy-xdpool-card dsm-workbuddy-xdpool-empty",
1299
+ children: [
1300
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1301
+ className: "dsm-workbuddy-xdpool-empty-title",
1302
+ children: t?.("row.regionEmptyTitle", { region: regionLabel }) ?? t?.("row.regionEmpty") ?? "No account yet"
1177
1303
  }),
1178
- error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1179
- className: "dsm-workbuddy-xdpool-error",
1180
- children: t?.("row.error", { message: error }) ?? `Pool status unavailable: ${error}`
1304
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1305
+ className: "dsm-workbuddy-xdpool-empty-steps",
1306
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1307
+ className: "dsm-workbuddy-xdpool-empty-steps-title",
1308
+ children: t?.("row.regionHowToTitle", { region: regionLabel }) ?? `How to sign in to the ${regionLabel} version`
1309
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ol", {
1310
+ className: "dsm-workbuddy-xdpool-empty-list",
1311
+ children: [
1312
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t?.("row.regionHowTo1") ?? "" }),
1313
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t?.("row.regionHowTo2") ?? "" }),
1314
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t?.("row.regionHowTo3") ?? "" }),
1315
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t?.("row.regionHowTo4") ?? "" })
1316
+ ]
1317
+ })]
1181
1318
  }),
1182
- accountCount === 0 && error === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1183
- className: "dsm-workbuddy-xdpool-empty",
1184
- children: [
1185
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1186
- className: "dsm-workbuddy-xdpool-empty-title",
1187
- children: t?.("row.regionEmptyTitle", { region: regionLabel }) ?? t?.("row.regionEmpty") ?? "No account yet"
1188
- }),
1189
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1190
- className: "dsm-workbuddy-xdpool-empty-steps",
1191
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1192
- className: "dsm-workbuddy-xdpool-empty-steps-title",
1193
- children: t?.("row.regionHowToTitle", { region: regionLabel }) ?? `How to sign in to the ${regionLabel} version`
1194
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ol", {
1195
- className: "dsm-workbuddy-xdpool-empty-list",
1196
- children: [
1197
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t?.("row.regionHowTo1") ?? "" }),
1198
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t?.("row.regionHowTo2") ?? "" }),
1199
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t?.("row.regionHowTo3") ?? "" }),
1200
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t?.("row.regionHowTo4") ?? "" })
1201
- ]
1202
- })]
1203
- }),
1204
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1205
- className: "dsm-workbuddy-xdpool-empty-note",
1206
- children: t?.("row.regionHowToNote") ?? ""
1207
- })
1208
- ]
1209
- }) : null,
1210
- accountCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1211
- className: "dsm-workbuddy-xdpool-accounts",
1212
- "aria-label": t?.("row.accountsTitle") ?? "Accounts",
1213
- children: [
1214
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1215
- className: "dsm-workbuddy-xdpool-accounts-head",
1216
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1217
- className: "dsm-workbuddy-xdpool-accounts-title",
1218
- children: t?.("row.accountsTitle") ?? "Accounts in the pool"
1219
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1220
- className: "dsm-workbuddy-xdpool-accounts-summary",
1221
- children: t?.("row.accountsSummary", {
1222
- count: accountCount,
1223
- cooling
1224
- }) ?? `${accountCount} account(s) · ${cooling} cooling`
1225
- })]
1226
- }),
1227
- (() => {
1228
- const active = status?.activeAccountId === void 0 ? void 0 : status.accounts.find((account) => account.id === status.activeAccountId);
1229
- if (active === void 0) return null;
1230
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1231
- className: "dsm-workbuddy-xdpool-current",
1232
- children: [
1233
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dsm-workbuddy-xdpool-current-dot" }),
1234
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1235
- className: "dsm-workbuddy-xdpool-current-label",
1236
- children: t?.("row.currentAccount") ?? "In use now"
1237
- }),
1238
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1239
- className: "dsm-workbuddy-xdpool-current-name",
1240
- children: active.label
1241
- }),
1242
- active.disabled === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1243
- className: "dsm-workbuddy-xdpool-current-note",
1244
- children: t?.("row.currentAccountDisabled") ?? "disabled — will switch on the next request"
1245
- }) : null
1246
- ]
1247
- });
1248
- })(),
1249
- status?.accounts.map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountBlock, {
1250
- account,
1251
- ...checkinBusyId === void 0 ? {} : { checkinBusyId },
1252
- onClaimCheckin: (accountId) => {
1253
- claimCheckin(accountId);
1254
- },
1255
- onSaveCreditReserve: (accountId, reserve) => saveCreditReserve(accountId, reserve),
1256
- onToggleDisabled: (accountId, disabled) => {
1257
- toggleAccountDisabled(accountId, disabled);
1258
- },
1259
- ...accountBusyId === void 0 ? {} : { accountBusyId },
1260
- t
1261
- }, account.id))
1262
- ]
1263
- }) : null,
1264
- (status?.models.length ?? 0) > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1265
- className: "dsm-workbuddy-xdpool-models",
1266
- "aria-label": t?.("row.modelsTitle") ?? "Models",
1267
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1268
- className: "dsm-workbuddy-xdpool-models-head",
1269
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1270
- className: "dsm-workbuddy-xdpool-models-heading",
1271
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1272
- className: "dsm-workbuddy-xdpool-models-title",
1273
- children: t?.("row.modelsTitle") ?? "Models"
1274
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1275
- className: "dsm-workbuddy-xdpool-models-summary",
1276
- children: t?.("row.modelsEnabledCount", {
1277
- enabled: enabledCount,
1278
- total: status?.models.length ?? 0
1279
- }) ?? `${enabledCount} / ${status?.models.length ?? 0} enabled`
1280
- })]
1281
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1282
- className: "dsm-workbuddy-xdpool-models-actions",
1283
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1284
- type: "button",
1285
- className: "dsm-btn dsm-btn-outline",
1286
- disabled: !modelsDirty || savingModels,
1287
- onClick: discardModels,
1288
- children: t?.("row.modelsDiscard") ?? "Discard"
1289
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1290
- type: "button",
1291
- className: "dsm-btn dsm-btn-primary",
1292
- disabled: !modelsDirty || savingModels || enabledCount === 0,
1293
- onClick: () => {
1294
- saveModels();
1295
- },
1296
- children: savingModels ? t?.("row.modelsSaving") ?? "Saving…" : t?.("row.modelsSave") ?? "Save"
1297
- })]
1298
- })]
1299
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1300
- className: "dsm-workbuddy-xdpool-model-list",
1301
- children: status?.models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelRow, {
1302
- model,
1303
- t,
1304
- draft: modelDraft[model.id] ?? {
1305
- enabled: model.enabled,
1306
- images: model.supportsImages
1307
- },
1308
- editable: modelsEditable,
1309
- onToggle: toggleModel,
1310
- onToggleImage: toggleModelImage,
1311
- onBudget: setModelBudget
1312
- }, model.id))
1319
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1320
+ className: "dsm-workbuddy-xdpool-empty-note",
1321
+ children: t?.("row.regionHowToNote") ?? ""
1322
+ })
1323
+ ]
1324
+ }) : null,
1325
+ accountCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1326
+ className: "dsm-workbuddy-xdpool-card dsm-workbuddy-xdpool-accounts",
1327
+ "aria-label": t?.("row.accountsTitle") ?? "Accounts",
1328
+ children: [
1329
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1330
+ className: "dsm-workbuddy-xdpool-accounts-head",
1331
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1332
+ className: "dsm-workbuddy-xdpool-accounts-title",
1333
+ children: t?.("row.accountsTitle") ?? "Accounts in the pool"
1334
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1335
+ className: "dsm-workbuddy-xdpool-accounts-summary",
1336
+ children: t?.("row.accountsSummary", {
1337
+ count: accountCount,
1338
+ cooling
1339
+ }) ?? `${accountCount} account(s) · ${cooling} cooling`
1313
1340
  })]
1314
- }) : null
1341
+ }),
1342
+ (() => {
1343
+ const active = status?.activeAccountId === void 0 ? void 0 : status.accounts.find((account) => account.id === status.activeAccountId);
1344
+ if (active === void 0) return null;
1345
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1346
+ className: "dsm-workbuddy-xdpool-current",
1347
+ children: [
1348
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dsm-workbuddy-xdpool-current-dot" }),
1349
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1350
+ className: "dsm-workbuddy-xdpool-current-label",
1351
+ children: t?.("row.currentAccount") ?? "In use now"
1352
+ }),
1353
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1354
+ className: "dsm-workbuddy-xdpool-current-name",
1355
+ children: active.label
1356
+ }),
1357
+ active.disabled === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1358
+ className: "dsm-workbuddy-xdpool-current-note",
1359
+ children: t?.("row.currentAccountDisabled") ?? "disabled — will switch on the next request"
1360
+ }) : null
1361
+ ]
1362
+ });
1363
+ })(),
1364
+ status?.accounts.map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountBlock, {
1365
+ account,
1366
+ ...checkinBusyId === void 0 ? {} : { checkinBusyId },
1367
+ onClaimCheckin: (accountId) => {
1368
+ claimCheckin(accountId);
1369
+ },
1370
+ onSaveCreditReserve: (accountId, reserve) => saveCreditReserve(accountId, reserve),
1371
+ onToggleDisabled: (accountId, disabled) => {
1372
+ toggleAccountDisabled(accountId, disabled);
1373
+ },
1374
+ onIgnoreAccount: (accountId) => {
1375
+ setAccountIgnored(accountId, true);
1376
+ },
1377
+ ...accountBusyId === void 0 ? {} : { accountBusyId },
1378
+ t
1379
+ }, account.id))
1315
1380
  ]
1316
- })
1317
- }) : null]
1381
+ }) : null,
1382
+ (status?.ignored.length ?? 0) > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1383
+ className: "dsm-workbuddy-xdpool-card dsm-workbuddy-xdpool-ignored",
1384
+ "aria-label": t?.("row.ignoredTitle") ?? "Removed accounts",
1385
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1386
+ className: "dsm-workbuddy-xdpool-ignored-head",
1387
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1388
+ className: "dsm-workbuddy-xdpool-ignored-title",
1389
+ children: t?.("row.ignoredTitle") ?? "Removed accounts"
1390
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1391
+ className: "dsm-workbuddy-xdpool-ignored-summary",
1392
+ children: t?.("row.ignoredSummary", { count: status?.ignored.length ?? 0 }) ?? `${status?.ignored.length ?? 0} account(s) no longer in the pool`
1393
+ })]
1394
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1395
+ className: "dsm-workbuddy-xdpool-ignored-list",
1396
+ children: (status?.ignored ?? []).map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1397
+ className: "dsm-workbuddy-xdpool-ignored-row",
1398
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1399
+ className: "dsm-workbuddy-xdpool-ignored-label",
1400
+ children: entry.label
1401
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1402
+ type: "button",
1403
+ className: "dsm-workbuddy-xdpool-ignored-restore",
1404
+ title: t?.("row.ignoredRestoreHint") ?? "Put this account back into the pool",
1405
+ disabled: accountBusyId === entry.id,
1406
+ onClick: () => {
1407
+ setAccountIgnored(entry.id, false);
1408
+ },
1409
+ children: t?.("row.ignoredRestore") ?? "Restore"
1410
+ })]
1411
+ }, entry.id))
1412
+ })]
1413
+ }) : null,
1414
+ (status?.models.length ?? 0) > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1415
+ className: "dsm-workbuddy-xdpool-card dsm-workbuddy-xdpool-models",
1416
+ "aria-label": t?.("row.modelsTitle") ?? "Models",
1417
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1418
+ className: "dsm-workbuddy-xdpool-models-head",
1419
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1420
+ className: "dsm-workbuddy-xdpool-models-heading",
1421
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1422
+ className: "dsm-workbuddy-xdpool-models-title",
1423
+ children: t?.("row.modelsTitle") ?? "Models"
1424
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1425
+ className: "dsm-workbuddy-xdpool-models-summary",
1426
+ children: t?.("row.modelsEnabledCount", {
1427
+ enabled: enabledCount,
1428
+ total: status?.models.length ?? 0
1429
+ }) ?? `${enabledCount} / ${status?.models.length ?? 0} enabled`
1430
+ })]
1431
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1432
+ className: "dsm-workbuddy-xdpool-models-actions",
1433
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1434
+ type: "button",
1435
+ className: "dsm-btn dsm-btn-outline",
1436
+ disabled: !modelsDirty || savingModels,
1437
+ onClick: discardModels,
1438
+ children: t?.("row.modelsDiscard") ?? "Discard"
1439
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1440
+ type: "button",
1441
+ className: "dsm-btn dsm-btn-primary",
1442
+ disabled: !modelsDirty || savingModels || enabledCount === 0,
1443
+ onClick: () => {
1444
+ saveModels();
1445
+ },
1446
+ children: savingModels ? t?.("row.modelsSaving") ?? "Saving…" : t?.("row.modelsSave") ?? "Save"
1447
+ })]
1448
+ })]
1449
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1450
+ className: "dsm-workbuddy-xdpool-model-list",
1451
+ children: status?.models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelRow, {
1452
+ model,
1453
+ t,
1454
+ draft: modelDraft[model.id] ?? {
1455
+ enabled: model.enabled,
1456
+ images: model.supportsImages
1457
+ },
1458
+ editable: modelsEditable,
1459
+ onToggle: toggleModel,
1460
+ onToggleImage: toggleModelImage,
1461
+ onBudget: setModelBudget
1462
+ }, model.id))
1463
+ })]
1464
+ }) : null
1465
+ ]
1318
1466
  });
1319
1467
  }
1320
1468
  /** One account block: label + status tag + meta + optional credit panels. */
1321
- function AccountBlock({ account, t, checkinBusyId, onClaimCheckin, accountBusyId, onToggleDisabled, onSaveCreditReserve, reserveBusyId }) {
1469
+ function AccountBlock({ account, t, checkinBusyId, onClaimCheckin, accountBusyId, onToggleDisabled, onIgnoreAccount, onSaveCreditReserve, reserveBusyId }) {
1322
1470
  const isDisabled = account.disabled === true;
1323
1471
  const isCooling = account.cooling === true;
1324
1472
  const cooldownUntil = account.cooldownUntil !== void 0 ? Date.parse(account.cooldownUntil) : void 0;
@@ -1351,6 +1499,16 @@ window.__ModuleLoader__.load({
1351
1499
  onToggleDisabled(account.id, !isDisabled);
1352
1500
  },
1353
1501
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dsm-workbuddy-xdpool-account-toggle-dot" }), isDisabled ? t?.("row.accountOff") ?? "Disabled" : t?.("row.accountInRotation") ?? "Enabled"]
1502
+ }),
1503
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1504
+ type: "button",
1505
+ className: "dsm-workbuddy-xdpool-account-ignore",
1506
+ title: t?.("row.accountIgnoreHint") ?? "Remove this account from the pool for good",
1507
+ disabled: accountBusyId === account.id,
1508
+ onClick: () => {
1509
+ onIgnoreAccount(account.id);
1510
+ },
1511
+ children: t?.("row.accountIgnore") ?? "Remove"
1354
1512
  })
1355
1513
  ]
1356
1514
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -1668,7 +1826,7 @@ window.__ModuleLoader__.load({
1668
1826
  className: "dsm-workbuddy-xdpool-model-copy",
1669
1827
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1670
1828
  className: "dsm-workbuddy-xdpool-model-name",
1671
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: model.name }), model.multiplier === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1829
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: model.name }), model.multiplier === void 0 || model.multiplier === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1672
1830
  className: "dsm-workbuddy-xdpool-model-name-rate",
1673
1831
  children: t?.("row.rate", { rate: model.multiplier.toFixed(2) }) ?? `${model.multiplier.toFixed(2)}x`
1674
1832
  })]
@@ -1732,6 +1890,50 @@ window.__ModuleLoader__.load({
1732
1890
  });
1733
1891
  }
1734
1892
  //#endregion
1893
+ //#region src/client/nav-icon.ts
1894
+ /**
1895
+ * Nav glyph for the WorkBuddy XD Pool settings page.
1896
+ *
1897
+ * The host's settings shell draws its own 16px `svg` in every nav row and the
1898
+ * `settings.section` registration contract projects only `id` / `order` /
1899
+ * `label` — there is no `icon` field to pass. A third-party page therefore has
1900
+ * to mark its row in the DOM and mask this artwork over the shell's glyph, the
1901
+ * same technique `dshmarket` uses (`installSettingsNavIcon`).
1902
+ *
1903
+ * Kept as a single monochrome path so `mask-image` + `currentColor` can tint it
1904
+ * with whatever the active theme uses for nav text: a filled mask cannot carry
1905
+ * its own palette, and a two-tone icon would render as a flat silhouette.
1906
+ *
1907
+ * @module dsh-workbuddy-xdpool/client/nav-icon
1908
+ */
1909
+ /**
1910
+ * The nav mark: a stack of three rounded "accounts" under a rotation arc.
1911
+ *
1912
+ * Reads as "a pool of accounts being cycled" at 16px, and — unlike a literal
1913
+ * droplet or cloud — stays legible when reduced to a single-color silhouette.
1914
+ * `fill-rule="evenodd"` cuts the interior notches out of the silhouette so the
1915
+ * three layers stay distinguishable at that size.
1916
+ */
1917
+ const POOL_NAV_ICON_SVG = [
1918
+ "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"16\" height=\"16\">",
1919
+ "<path fill=\"currentColor\" fill-rule=\"evenodd\" d=\"",
1920
+ "M12 2.6a3.1 3.1 0 1 1 0 6.2 3.1 3.1 0 0 1 0-6.2Zm0 1.7a1.4 1.4 0 1 0 0 2.8 1.4 1.4 0 0 0 0-2.8Z",
1921
+ "M6.6 8.9a2.6 2.6 0 1 1 0 5.2 2.6 2.6 0 0 1 0-5.2Zm0 1.6a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z",
1922
+ "M17.4 8.9a2.6 2.6 0 1 1 0 5.2 2.6 2.6 0 0 1 0-5.2Zm0 1.6a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z",
1923
+ "\"/>",
1924
+ "<path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\" stroke-linecap=\"round\"",
1925
+ " d=\"M4.4 17.2a8.6 8.6 0 0 0 15.2 0\" stroke-dasharray=\"2.6 2.2\"/>",
1926
+ "</svg>"
1927
+ ].join("");
1928
+ /**
1929
+ * The same artwork as a `mask-image` URL.
1930
+ *
1931
+ * `encodeURIComponent` keeps the `#`-free markup safe inside a `url("…")` in a
1932
+ * stylesheet, and `currentColor` is resolved by the mask's own element rather
1933
+ * than by the SVG, so the glyph follows the theme.
1934
+ */
1935
+ const POOL_NAV_ICON_MASK_URL = `data:image/svg+xml;utf8,${encodeURIComponent(POOL_NAV_ICON_SVG)}`;
1936
+ //#endregion
1735
1937
  //#region src/client/locales.ts
1736
1938
  /**
1737
1939
  * Plugin-card copy registered under the `settings.workbuddy-xdpool` locale
@@ -1741,6 +1943,7 @@ window.__ModuleLoader__.load({
1741
1943
  * @module dsh-workbuddy-xdpool/client/locales
1742
1944
  */
1743
1945
  const en = {
1946
+ "row.navLabel": "XD Pool",
1744
1947
  "row.title": "WorkBuddy XD Pool (dsh-workbuddy-xdpool)",
1745
1948
  "row.desc": "Route every WorkBuddy sign-in on this machine into DSH as one auto-failing-over model pool.",
1746
1949
  "row.expand": "Expand",
@@ -1770,6 +1973,13 @@ window.__ModuleLoader__.load({
1770
1973
  "row.accountOff": "Disabled",
1771
1974
  "row.accountToggleHint": "Enable this account (uncheck to keep it out of the pool)",
1772
1975
  "row.accountToggleError": "Could not switch the account: {message}",
1976
+ "row.accountIgnore": "Remove",
1977
+ "row.accountIgnoreHint": "Remove this account from the pool for good — its credential stops being read and it will not come back",
1978
+ "row.accountIgnoreError": "Could not change the ignore list: {message}",
1979
+ "row.ignoredTitle": "Removed accounts",
1980
+ "row.ignoredSummary": "{count} account(s) no longer in the pool",
1981
+ "row.ignoredRestore": "Restore",
1982
+ "row.ignoredRestoreHint": "Put this account back into the pool",
1773
1983
  "row.currentAccount": "In use now",
1774
1984
  "row.currentAccountDisabled": "switched off — the next request moves on",
1775
1985
  "row.cooling": "Cooling (rate-limited)",
@@ -1872,6 +2082,7 @@ window.__ModuleLoader__.load({
1872
2082
  "row.autoFromTravel": "Buddy travel +{credit} credits"
1873
2083
  };
1874
2084
  const zh = {
2085
+ "row.navLabel": "XD Pool",
1875
2086
  "row.title": "WorkBuddy 池(dsh-workbuddy-xdpool)",
1876
2087
  "row.desc": "把本机所有已登录的 WorkBuddy 账号并入 DSH,作为一个自动容错的模型池使用。",
1877
2088
  "row.expand": "展开",
@@ -1901,6 +2112,13 @@ window.__ModuleLoader__.load({
1901
2112
  "row.accountOff": "已停用",
1902
2113
  "row.accountToggleHint": "启用该账号(取消勾选则不参与池子)",
1903
2114
  "row.accountToggleError": "切换账号失败:{message}",
2115
+ "row.accountIgnore": "移出池子",
2116
+ "row.accountIgnoreHint": "把这个账号永久移出池子 —— 不再读取它的凭据,即使重新登录也不会回来",
2117
+ "row.accountIgnoreError": "修改忽略列表失败:{message}",
2118
+ "row.ignoredTitle": "已移出的账号",
2119
+ "row.ignoredSummary": "{count} 个账号已不在池中",
2120
+ "row.ignoredRestore": "恢复",
2121
+ "row.ignoredRestoreHint": "把这个账号放回池子",
1904
2122
  "row.currentAccount": "当前使用",
1905
2123
  "row.currentAccountDisabled": "已停用 —— 下次请求会换号",
1906
2124
  "row.cooling": "冷却中(被限流)",
@@ -2006,13 +2224,26 @@ window.__ModuleLoader__.load({
2006
2224
  //#region src/client/index.tsx
2007
2225
  /** Stable browser-plugin name. */
2008
2226
  const name = "dsh-workbuddy-xdpool-client";
2009
- /** Client services required by the Plugin configuration contribution. */
2010
- const inject = [
2011
- "slots",
2012
- "locale",
2013
- "settingsScope"
2014
- ];
2015
- /** Register card copy and the pool card under Plugin configuration. */
2227
+ /**
2228
+ * Client services required by the settings page.
2229
+ *
2230
+ * Deliberately only the two services present on BOTH host lines. The settings
2231
+ * surface differs by line — 0.1.5 provides `settingsScope`, 0.1.7 replaces it
2232
+ * with `configForms` — and cordis' dependency gate is hard: any inject entry the
2233
+ * running line does not provide keeps `apply` from ever running. Probing the one
2234
+ * that exists through `ctx.get()` (which returns undefined, never throws, for an
2235
+ * absent service) is what lets one build serve both lines.
2236
+ */
2237
+ const inject = ["slots", "locale"];
2238
+ /** Settings namespace the host-side section registers (shared with the entry). */
2239
+ const WORKBUDDY_POOL_SETTINGS_NS = "workbuddy-xdpool";
2240
+ /**
2241
+ * Host plugin entry id this bundle is mounted under in `cordis.patch.yml`.
2242
+ *
2243
+ * `configForms` is addressed by this id on the 0.1.7 line.
2244
+ */
2245
+ const WORKBUDDY_POOL_ENTRY_ID = "llm-workbuddy-xdpool";
2246
+ /** Register card copy and the pool page under Settings. */
2016
2247
  function apply(ctx) {
2017
2248
  try {
2018
2249
  const namespace = "settings.workbuddy-xdpool";
@@ -2021,19 +2252,127 @@ window.__ModuleLoader__.load({
2021
2252
  en
2022
2253
  }), "dsh-workbuddy-xdpool: settings copy");
2023
2254
  const t = ctx.locale.bind(namespace);
2024
- const settingsScope = ctx.settingsScope.bind({ namespace: "workbuddy-xdpool" });
2025
- ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
2026
- name: "settings.plugin.item",
2027
- key: "workbuddy-xdpool",
2028
- priority: 30,
2029
- inject: () => ({
2255
+ const softGet = (serviceName) => ctx.get(serviceName);
2256
+ const settingsScope = resolveSettingsScope(softGet);
2257
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
2258
+ name: "settings.section",
2259
+ id: "workbuddy-xdpool",
2260
+ order: 440,
2261
+ label: () => t("row.navLabel"),
2262
+ inject: () => settingsScope === void 0 ? { t } : {
2030
2263
  t,
2031
2264
  settingsScope
2032
- })
2265
+ }
2033
2266
  }, PoolCard));
2267
+ installNavIcon(ctx, () => t("row.navLabel"));
2034
2268
  } catch (error) {
2035
- console.error("[dsh-workbuddy-xdpool] client card failed to load (host provider unaffected):", error);
2269
+ console.error("[dsh-workbuddy-xdpool] client page failed to load (host provider unaffected):", error);
2270
+ }
2271
+ }
2272
+ /** Attribute carrying the nav-row marker this module installs. */
2273
+ const NAV_ICON_MARKER = "data-dsh-xdpool-nav-icon";
2274
+ /**
2275
+ * The settings nav rows, as the shell renders them. Scoped to the settings
2276
+ * dialog on purpose: the main sidebar has its own nav, and matching rows there
2277
+ * would stamp this glyph onto an unrelated control.
2278
+ */
2279
+ const NAV_ROW_SELECTOR = "[role=\"dialog\"] nav button";
2280
+ /**
2281
+ * Draw this page's own glyph in its Settings nav row.
2282
+ *
2283
+ * The `settings.section` contract carries no icon: the shell decides the glyph
2284
+ * from the section id and falls back to a gear for anything it does not know.
2285
+ * So the row is matched by its LABEL (the same thunk passed to the
2286
+ * registration, re-read on every pass so a locale switch is followed) and
2287
+ * marked; CSS then hides the shell svg and masks this artwork into the row.
2288
+ *
2289
+ * Re-scanned on DOM mutations because the shell re-renders the nav on locale
2290
+ * and theme changes, which replaces the row elements and drops the marker.
2291
+ *
2292
+ * No-op off the browser (the node-side probe imports this module for types).
2293
+ */
2294
+ function installNavIcon(ctx, resolveLabel) {
2295
+ if (typeof document === "undefined") return;
2296
+ ctx.effect(() => {
2297
+ const tag = document.createElement("style");
2298
+ tag.dataset.plugin = "dsh-workbuddy-xdpool";
2299
+ tag.dataset.pluginCss = "dsh-workbuddy-xdpool/settings-nav-icon";
2300
+ tag.textContent = [
2301
+ `[${NAV_ICON_MARKER}] > svg { display: none; }`,
2302
+ `[${NAV_ICON_MARKER}]::before {`,
2303
+ " content: '';",
2304
+ " flex: none;",
2305
+ " width: 16px;",
2306
+ " height: 16px;",
2307
+ " background-color: currentColor;",
2308
+ ` -webkit-mask-image: url("${POOL_NAV_ICON_MASK_URL}");`,
2309
+ ` mask-image: url("${POOL_NAV_ICON_MASK_URL}");`,
2310
+ " -webkit-mask-repeat: no-repeat;",
2311
+ " mask-repeat: no-repeat;",
2312
+ " -webkit-mask-position: center;",
2313
+ " mask-position: center;",
2314
+ " -webkit-mask-size: 16px 16px;",
2315
+ " mask-size: 16px 16px;",
2316
+ "}"
2317
+ ].join("\n");
2318
+ document.head.appendChild(tag);
2319
+ let disposed = false;
2320
+ let scheduled = false;
2321
+ const sync = () => {
2322
+ scheduled = false;
2323
+ if (disposed) return;
2324
+ const wanted = String(resolveLabel() ?? "").trim();
2325
+ if (wanted === "") return;
2326
+ for (const row of document.querySelectorAll(NAV_ROW_SELECTOR)) if (String(row.textContent ?? "").trim() === wanted) row.setAttribute(NAV_ICON_MARKER, "");
2327
+ else row.removeAttribute(NAV_ICON_MARKER);
2328
+ };
2329
+ const schedule = () => {
2330
+ if (scheduled || disposed) return;
2331
+ scheduled = true;
2332
+ queueMicrotask(sync);
2333
+ };
2334
+ sync();
2335
+ const observer = new MutationObserver(schedule);
2336
+ observer.observe(document.body, {
2337
+ childList: true,
2338
+ subtree: true,
2339
+ characterData: true
2340
+ });
2341
+ return () => {
2342
+ disposed = true;
2343
+ observer.disconnect();
2344
+ for (const row of document.querySelectorAll(`[${NAV_ICON_MARKER}]`)) row.removeAttribute(NAV_ICON_MARKER);
2345
+ tag.remove();
2346
+ };
2347
+ }, "dsh-workbuddy-xdpool: settings nav icon");
2348
+ }
2349
+ /**
2350
+ * Bind the settings form the card reads and writes, on whichever line is
2351
+ * running.
2352
+ *
2353
+ * 0.1.7 exposes `configForms.get(entryId)`, keyed by the HOST plugin entry id —
2354
+ * the id this bundle registers under in `cordis.patch.yml`, not the settings
2355
+ * namespace. 0.1.5 exposes `settingsScope.bind({ namespace })`, keyed by the
2356
+ * namespace the host-side `installSection` registered. Both controllers answer
2357
+ * the same two methods the card uses (`getSnapshot()`, `set(field, value)`), so
2358
+ * the card needs no per-line branch of its own.
2359
+ *
2360
+ * Returns undefined when neither service is present (a locked-down host): the
2361
+ * card then renders read-only, which is the documented degradation.
2362
+ */
2363
+ function resolveSettingsScope(softGet) {
2364
+ const forms = softGet("configForms");
2365
+ if (forms !== void 0) {
2366
+ let entryId = WORKBUDDY_POOL_ENTRY_ID;
2367
+ try {
2368
+ const namespaces = forms.describe().getSnapshot().view?.namespaces ?? [];
2369
+ const served = namespaces.find((entry) => entry.ns === WORKBUDDY_POOL_ENTRY_ID) ?? namespaces.find((entry) => /workbuddy-xdpool/.test(entry.ns));
2370
+ if (served !== void 0) entryId = served.ns;
2371
+ } catch {}
2372
+ return forms.get(entryId);
2036
2373
  }
2374
+ const scope = softGet("settingsScope");
2375
+ if (scope !== void 0) return scope.bind({ namespace: WORKBUDDY_POOL_SETTINGS_NS });
2037
2376
  }
2038
2377
  //#endregion
2039
2378
  exports.apply = apply;