@goodandready/dsh-key-rotation 0.8.62 → 0.8.63

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.63 - 2026-10-16
4
+
5
+ ### Added
6
+ - **Configurable Switch to Disable Chat Header Status Chip (#493 / GitHub #25)**:
7
+ - Added `showChatHeaderChip` boolean setting (defaults to `true` for 100% backward compatibility) in `SettingsSchema` and runtime configuration.
8
+ - Added user-facing toggle in the Settings card ("Chat Header Status Chip" / "聊天头部状态按钮") under rotation settings.
9
+ - `KeyRotationHeaderChip` (`conversation.session.header.utilities`) dynamically subscribes to settings and checks server health state, cleanly returning `null` when disabled without taking up conversational header toolbar space.
10
+ - Additive field `showChatHeaderChip` exposed in `/dsh-key-rotation/status` and `/dsh-key-rotation/health`.
11
+
3
12
  ## 0.8.62 - 2026-10-16
4
13
 
5
14
  ### Added
package/README.md CHANGED
@@ -378,6 +378,7 @@ dsh-key-rotation:
378
378
  | `circuitBreakerOpenMs` | `number` | `30000` | How long the circuit stays open (ms). |
379
379
  | `circuitBreakerHalfOpenProbes` | `number` | `1` | Probe requests allowed in half-open state. |
380
380
  | `verboseLogging` | `boolean` | `false` | Per-request rotation logs (noisy; off by default). |
381
+ | `showChatHeaderChip` | `boolean` | `true` | Show or hide the pool status button in conversation header toolbar (#25). |
381
382
  | `concurrencyLimit` | `number` | `0` (disabled) | Max concurrent in-flight streams per key (0 = unlimited). |
382
383
  | `quotaResetWindow` | `object` | `null` | Calendar reset alignment for both provider-reported quota cooldowns and local per-model token budgets (`midnight_utc`, `midnight_pst`, `rolling_24h`). |
383
384
  | `cascade` | `array` | `[]` | Fallback provider chain when primary pool is completely exhausted. |
package/README.ru.md CHANGED
@@ -409,6 +409,7 @@ MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
409
409
  | `circuitBreakerOpenMs` | number | `30000` | Длительность open, мс |
410
410
  | `circuitBreakerHalfOpenProbes` | number | `1` | Проб в half-open |
411
411
  | `verboseLogging` | boolean | `false` | Подробные логи rotation (шумно) |
412
+ | `showChatHeaderChip` | boolean | `true` | Показывать кнопку статуса пула в шапке чата (#25) |
412
413
 
413
414
  Status API (v0.8.0): у каждого провайдера `circuit`, в корне ответа `meta.expectedClones` и `meta.notifyQueue`.
414
415
 
package/README.zh.md CHANGED
@@ -378,3 +378,4 @@ MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
378
378
  | `circuitBreakerThreshold` | number | `5` | 连续失败阈值 |
379
379
  | `circuitBreakerOpenMs` | number | `30000` | 打开时长 ms |
380
380
  | `circuitBreakerHalfOpenProbes` | number | `1` | 半开探测次数 |
381
+ | `showChatHeaderChip` | boolean | `true` | 在会话界面顶部工具栏显示密钥池状态按钮 (#25) |
package/lib/client.js CHANGED
@@ -224,6 +224,8 @@ window.__ModuleLoader__.load({
224
224
  routingStrategySticky: 'Sticky (preserves prompt cache)',
225
225
  proactiveGuardLabel: 'Proactive Rate-Limit Guard',
226
226
  proactiveGuardHint: 'Pre-emptively pause keys when remaining quota or Retry-After header indicates limit before hitting 429',
227
+ showChatHeaderChipLabel: 'Chat Header Status Chip',
228
+ showChatHeaderChipDesc: 'Show pool status chip in conversation header',
227
229
  selfHealingLabel: 'Auto-Unbreak Interval (min)',
228
230
  selfHealingHint: 'Periodic free model check to revive broken keys (0 to disable)',
229
231
  updateCheck: 'Check for updates',
@@ -422,6 +424,8 @@ window.__ModuleLoader__.load({
422
424
  routingStrategySticky: '粘性模式 (优先保持提示词缓存)',
423
425
  proactiveGuardLabel: '前瞻性限流防护',
424
426
  proactiveGuardHint: '当响应中的剩余配额或 Retry-After 头提示即将超限时,预先冷却密钥以防触发 429',
427
+ showChatHeaderChipLabel: '聊天头部状态按钮',
428
+ showChatHeaderChipDesc: '在会话界面顶部工具栏显示密钥池状态按钮',
425
429
  selfHealingLabel: '自愈检查间隔 (分钟)',
426
430
  selfHealingHint: '定期轻量探测以自动恢复损坏密钥 (0 为禁用)',
427
431
  updateCheck: '检查更新',
@@ -2596,6 +2600,16 @@ window.__ModuleLoader__.load({
2596
2600
  }),
2597
2601
  h('span', { key: 'label', style: { fontSize: '13px', color: 'var(--dsw-alias-label-secondary)' } }, t('proactiveGuardLabel')),
2598
2602
  ]))
2603
+ ),
2604
+ h('div', { className: 'krot-grid-2', style: { marginTop: '10px' } },
2605
+ field(t('showChatHeaderChipLabel'), h('label', { style: { display: 'flex', alignItems: 'center', gap: '8px', height: '36px', cursor: 'pointer' } }, [
2606
+ h('input', {
2607
+ key: 'input', type: 'checkbox',
2608
+ checked: val.showChatHeaderChip ?? true,
2609
+ onChange: (e) => setField((cur) => ({ ...cur, showChatHeaderChip: e.target.checked })),
2610
+ }),
2611
+ h('span', { key: 'label', style: { fontSize: '13px', color: 'var(--dsw-alias-label-secondary)' } }, t('showChatHeaderChipDesc')),
2612
+ ]))
2599
2613
  )
2600
2614
  );
2601
2615
 
@@ -2886,6 +2900,7 @@ window.__ModuleLoader__.load({
2886
2900
  const t = resolveT(props);
2887
2901
  const [snap, setSnap] = React.useState(null);
2888
2902
  const [open, setOpen] = React.useState(false);
2903
+ const [, setTick] = React.useState(0);
2889
2904
  const ref = React.useRef(null);
2890
2905
 
2891
2906
  React.useEffect(() => {
@@ -2895,7 +2910,7 @@ window.__ModuleLoader__.load({
2895
2910
  fetch('/dsh-key-rotation/health', { headers: { accept: 'application/json' }, credentials: 'same-origin', signal: (typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function') ? AbortSignal.timeout(8000) : undefined })
2896
2911
  .then((r) => (r.ok ? r.json() : null))
2897
2912
  .then((d) => { if (alive) setSnap(d); })
2898
- .catch(() => {});
2913
+ .catch((e) => { void e; });
2899
2914
  };
2900
2915
  load();
2901
2916
  const id = setInterval(load, 5000);
@@ -2908,6 +2923,13 @@ window.__ModuleLoader__.load({
2908
2923
  };
2909
2924
  }, []);
2910
2925
 
2926
+ React.useEffect(() => {
2927
+ if (!props?.settingsSource?.subscribe) return;
2928
+ return props.settingsSource.subscribe(() => {
2929
+ setTick((c) => c + 1);
2930
+ });
2931
+ }, [props?.settingsSource]);
2932
+
2911
2933
  React.useEffect(() => {
2912
2934
  if (!open) return;
2913
2935
  const onDocClick = (e) => {
@@ -2917,6 +2939,16 @@ window.__ModuleLoader__.load({
2917
2939
  return () => document.removeEventListener('click', onDocClick);
2918
2940
  }, [open]);
2919
2941
 
2942
+ let settingsVal = null;
2943
+ try {
2944
+ const sSnap = props?.settingsSource?.getSnapshot?.()?.service?.getSnapshot?.();
2945
+ settingsVal = sSnap?.value;
2946
+ } catch (e) {
2947
+ void e;
2948
+ }
2949
+ if (settingsVal && settingsVal.showChatHeaderChip === false) return null;
2950
+ if (snap && snap.showChatHeaderChip === false) return null;
2951
+
2920
2952
  const poolsObj = (snap && snap.pools) || {};
2921
2953
  const poolEntries = Object.entries(poolsObj);
2922
2954
  const total = poolEntries.reduce((a, [, p]) => a + (p.total || 0), 0);
@@ -2952,7 +2984,6 @@ window.__ModuleLoader__.load({
2952
2984
  ) : null
2953
2985
  );
2954
2986
  }
2955
-
2956
2987
  function apply(ctx) {
2957
2988
  const settingsSource = createSettingsSource(ctx);
2958
2989
  ctx.effect(() => ctx.locale.register(NS, { en, zh }), 'dsh-key-rotation: dictionaries');
@@ -2963,7 +2994,7 @@ window.__ModuleLoader__.load({
2963
2994
  try {
2964
2995
  ctx.slots.inject('conversation.session.header.utilities', () =>
2965
2996
  ctx.slots.register(
2966
- { name: 'conversation.session.header.utilities', id: 'dsh-key-rotation-header-chip', order: 6, locale: NS },
2997
+ { name: 'conversation.session.header.utilities', id: 'dsh-key-rotation-header-chip', order: 6, locale: NS, inject: () => ({ ctx, settingsSource }) },
2967
2998
  KeyRotationHeaderChip,
2968
2999
  ));
2969
3000
  } catch { /* slot not available in this build */ }
package/lib/index.js CHANGED
@@ -119,6 +119,7 @@ const SettingsSchema = Schema.object({
119
119
  earlyWarningKeyThreshold: Schema.number().default(0.25),
120
120
  warnBelowHealthy: Schema.number().default(0),
121
121
  latencySloMs: Schema.number().default(0),
122
+ showChatHeaderChip: Schema.boolean().default(true),
122
123
  providers: Schema.array(Schema.object({
123
124
  provider: Schema.string().required(),
124
125
  keys: Schema.array(Schema.string()).default([]),
@@ -350,6 +351,7 @@ export function apply(ctx, config = {}) {
350
351
  proactiveRateLimitGuard: cfg.proactiveRateLimitGuard ?? true,
351
352
  selfHealingIntervalMinutes: cfg.selfHealingIntervalMinutes ?? 30,
352
353
  routingStrategy: cfg.routingStrategy ?? 'round-robin',
354
+ showChatHeaderChip: cfg.showChatHeaderChip !== false,
353
355
  switchNotifyThrottleMs: cfg.switchNotifyThrottleMs ?? 60000,
354
356
  warnBelowHealthy: cfg.warnBelowHealthy ?? 0, latencySloMs: cfg.latencySloMs ?? 0,
355
357
  index, pools,
package/lib/ops-status.js CHANGED
@@ -178,6 +178,7 @@ ctx.effect(() => ctx.webServer.register({
178
178
  }
179
179
  json(res, 200, sanitizeSnapshot({
180
180
  providers,
181
+ showChatHeaderChip: runtime.showChatHeaderChip !== false,
181
182
  // #266/#263 operational extras (additive)
182
183
  meta: {
183
184
  expectedClones: [...(runtime.expectedClones ?? [])],
@@ -248,7 +249,7 @@ ctx.effect(() => ctx.webServer.register({
248
249
  }
249
250
  }
250
251
  const isDegraded = exhaustedAny || circuitOpenAny;
251
- json(res, 200, { status: isDegraded ? 'degraded' : 'ok', pools, exhaustedAny, latency: latencyHistogram.snapshotAll(), quota: typeof quotaStore?.snapshot === 'function' ? quotaStore.snapshot() : null });
252
+ json(res, 200, { status: isDegraded ? 'degraded' : 'ok', showChatHeaderChip: runtimeH.showChatHeaderChip !== false, pools, exhaustedAny, latency: latencyHistogram.snapshotAll(), quota: typeof quotaStore?.snapshot === 'function' ? quotaStore.snapshot() : null });
252
253
  },
253
254
  }), 'dsh-key-rotation: health');
254
255
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-key-rotation",
3
- "version": "0.8.62",
3
+ "version": "0.8.63",
4
4
  "packageManager": "pnpm@10.33.2",
5
5
  "description": "Per-provider API key rotation for DeepSeek Harness: a key pool per provider, auto-created clone routes, and switching to the next key on quota/rate-limit errors. Includes a Settings section (Key Rotation) to edit the key pools, cooldown and switch codes.",
6
6
  "keywords": [