# 📦 @goodandready/dsh-key-rotation

<div align="center">

<h3>Прозрачная ротация API-ключей, предиктивный контроль лимитов и межпровайдерный каскадный failover для DeepSeek Harness</h3>

<p align="center">
  <a href="https://www.npmjs.com/package/@goodandready/dsh-key-rotation"><img src="https://img.shields.io/npm/v/@goodandready/dsh-key-rotation.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
  <a href="LICENSE"><img src="https://img.shields.io/github/license/GooDAnDReaDY/dsh-key-rotation.svg?style=for-the-badge&color=10b981&labelColor=064e3b" alt="license"></a>
  <a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/DSH-Plugin-8b5cf6.svg?style=for-the-badge&labelColor=2e1065" alt="DSH Plugin"></a>
  <a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node-20%2B-f59e0b.svg?style=for-the-badge&labelColor=451a03" alt="Node version"></a>
</p>

<!-- Обязательная кнопка перехода на витрину всех проектов -->
<p align="center">
  <a href="https://goodandready.app/"><img src="https://img.shields.io/badge/🌐_DSH_Hub-goodandready.app-ff4500.svg?style=for-the-badge&labelColor=1a1a2e" alt="GoodAndReady Showcase"></a>
</p>

<p align="center">
  <a href="README.md"><b>🇬🇧 English</b></a> •
  <a href="README.ru.md"><b>🇷🇺 Русский</b></a> •
  <a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
</p>

<table align="center">
  <tr>
    <td align="center">
      ⭐ <strong>Если вам нравится этот плагин, поставьте ему звезду на GitHub</strong> — это покажет мне, что плагин вам полезен, и будет мотивировать меня развивать его дальше.
      <br><br>
      🐛 <strong>Если вы нашли баг или хотите предложить новый функционал</strong>, создайте issue на GitHub на любом языке — я рассмотрю ваше предложение и реализую полезные идеи в одной из следующих версий плагина.
    </td>
  </tr>
</table>

</div>

---

### 🚀 Что нового в v0.8.11 (обновление в один клик и quality gate)

- **Обновление плагина в карточке настроек**: текущая/последняя версия и кнопка установки без выхода из DSH (#307).
- **bestEffort вместо пустых catch**: осознанные побочные эффекты логируются на debug, а не глотаются (#315).
- **Цвета клиента только из токенов темы** и чистка production-path тестов (#311, #314).
- **Публикационный набор без служебных файлов агентов** (#308).

## ⚡ Обзор и решаемая проблема

### 🚀 Что нового в версии 0.8.10 (Надёжность стриминга и очистка памяти)
- **Устранение утечек счётчика соединений**: Гарантированное освобождение слота конкурентности в `try ... finally`, исключающее блокировку ключей при успешном завершении стрима или прерывании клиентом.
- **Повтор при сетевых сбоях проверки**: 1 автоматический повтор при кратковременных сокетных ошибках в `probeModels` перед дисквалификацией ключа.
- **Очистка устаревших ключей**: Периодическая автоматическая очистка удалённых ключей из внутренних карт состояния пула при регулярном цикле `sweep`.

### 🚀 Что нового в версии 0.8.9 (Развитие маршрутизации и UI)
- **Превентивная защита от лимитов (Proactive Rate-Limit Guard)**: Автоматическая пауза ключей по заголовкам `x-ratelimit-remaining-*` и `Retry-After` до наступления ошибки 429.
- **Авто-восстановление (Self-Healing / Auto-Unbreak)**: Периодический фоновый опрос эндпоинта `/models` для автоматического снятия статуса `broken` без расхода чат-токенов.
- **Маршрутизация по задержке (Latency-Aware Routing)**: Выбор стратегии балансировки: `round-robin`, `least-loaded` (по активным запросам) и `lowest-latency` (по p95 задержки).
- **Обновление интерфейса в стиле `dsh-clinebot`**: Пакетное тестирование всех ключей («Test All Keys») с прогрессом, аккордеон живого потока событий ротации (`.krot-event-stream`) и таймер обратного отсчета сброса квот.
- **Встроенная поддержка двух языков**: Готовые словари интерфейса на английском (`en`) и китайском (`zh`).

### 🛠️ Что нового в версии 0.8.0 (Стабильность)
- **🔌 Автомат выключения провайдера**: после N подряд ошибок контур открывается, запросы быстро завершаются (`CIRCUIT_OPEN`); half-open пробы восстанавливают сервис.
- **🕒 Монотонные часы**: cooldown/breaker считаются по процессным часам, NTP не ломает оставшееся время.
- **📮 Неблокирующие webhook**: очередь с backoff — `rotate()` не ждёт HTTP.
- **🧱 Атомарная запись файлов**: повреждённый JSON не затирает прежнее состояние.
- **🧹 GC clone-маршрутов**: осиротевшие авто-роуты убираются из runtime-набора.
- **🧭 Таксономия ошибок**: явная классификация switch/surface/soft (408/425/429/5xx, сокеты, gRPC).
- **📡 Status**: `circuit` у провайдера + `meta.expectedClones` / `meta.notifyQueue`.
- **🧪 Smoke**: сценарий 429 → следующий ключ → успех.

### 🛠️ Что нового в версии 0.7.33 (Хотфикс и повышение стабильности)
- **🔍 Исправление проверки ключей**: Функция `resolveBaseUrl` теперь сопоставляет ref ключа с его пулом, восстанавливая работу живого тестирования моделей.
- **🛡️ Защита от зацикливания каскада**: Устранена возможность переполнения стека при взаимных кольцевых цепочках failover.
- **🕒 Точный сброс квот по PST**: Исправлен знак часового смещения UTC-8 для корректного сброса лимитов в полночь по тихоокеанскому времени.
- **🧹 Очистка таймеров в жизненном цикле**: Таймеры `canaryTimer` и `selfHealTimer` переведены на эффекты Cordis, исключая утечки при горячей перезагрузке.
- **⚡ Сброс зависших блокировок**: Алгоритм `pickLeastLoaded` теперь учитывает протухшие блокировки при выборе наименее нагруженного ключа.
- **🌐 Полная китайская локализация**: Добавлен словарь `zh` в веб-интерфейс настроек для соблюдения стандарта трёх языков (EN/RU/ZH).

### 🚀 Что нового в версии 0.7.31
- **⚡ $O(1)$ Накопитель TokenBucket**: Оптимизация математики лимитов до $O(1)$ по времени и без аллокаций памяти с адаптивной синхронизацией по заголовкам.
- **🛡️ Разделение Soft / Hard сбоев**: Кратковременные сетевые сбои (502/503/таймауты) получают короткий 10-секундный кулдаун без штрафного удвоения.
- **⏳ Затухание штрафов (Penalty Decay)**: Стабильно работающие ключи автоматически снижают штрафной множитель 1 раз в час.
- **🎲 Джиттер кулдауна**: Случайный разброс $\pm 12.5\%$ времени разблокировки предотвращает наплыв запросов на апстрим.
- **🎯 Адресное зондирование модели**: Опция целевого микро-пробинга моделей в Canary Prober.
- **📊 Перцентили задержки TTFT (p50 / p95 / p99)**: Расчет высокоточных перцентилей задержки первого токена в метриках здоровья.
- **🔔 Дайджест вебхук-алертов**: Группировка серии быстрых сбоев за 5-секундное окно в единый сводный отчет для Telegram, Discord и Slack.
- **🧹 30-дневная компактизация**: Автоматическая очистка истории старше 30 дней для защиты от утечек памяти.
- **✨ Оптимистичный UI и фильтры**: Мгновенный отклик кнопок сброса и статус-пилюли (`Все`, `Готовы`, `В кулдауне`, `С ошибками`) над списком ключей.

При активной работе автономных агентов, параллельном запуске субагентов и циклических вызовах инструментов запросы неизбежно упираются в ограничения провайдеров (ошибки HTTP 429 Too Many Requests, исчерпание суточных квот или временные сбои на стороне апстрима). В стандартной конфигурации DeepSeek Harness исчерпание одного API-ключа полностью блокирует цепочку рассуждений агента, ломает сохранённое состояние диалога (Replay State) и требует ручного вмешательства администратора.

**`dsh-key-rotation`** реализует отказоустойчивую корпоративную архитектуру **пулов API-ключей с предиктивным контролем лимитов (Token Bucket) и каскадным переключением на резервных провайдеров**, нативно интегрированную в микроядро Cordis.

В отличие от внешних прокси-маршрутизаторов, подменяющих идентификаторы моделей, `dsh-key-rotation` работает на уровне перехвата `ctx.credentials.resolve` и хука `llm/stream`:
* **Идентичность провайдера остаётся неизменной**: Внутреннее Replay-состояние агента `pi-ai` и контекст инструментов остаются на 100% консистентными.
* **Предиктивный Token Bucket**: Перегруженные ключи пропускаются **до** выполнения сетевого запроса, устраняя задержку на сетевой ретрай.
* **Балансировка Least-Connections**: Запросы равномерно распределяются по свободным ключам с контролем параллелизма (`maxConcurrency`).
* **Автономное самовосстановление и каскад**: Просроченные кулдауны простаивающих ключей снимаются автоматически, а при полном исчерпании пула запрос бесшовно передаётся запасному провайдеру.

---

## 🏗️ Архитектура и жизненный цикл запроса

```mermaid
graph LR
    subgraph ClientLayer ["Уровень клиента и агента"]
        UserMsg["Сообщение агента / пользователя"] --> Adapter["Адаптер модели pi-ai"]
    end

    subgraph RotationEngine ["Ядро dsh-key-rotation"]
        Adapter --> StreamHook["Перехватчик llm/stream"]
        StreamHook --> BucketCheck{"Token Bucket\nПроверка RPM / TPM"}
        BucketCheck -->|В пределах нормы| ConcurrencyCheck{"Трекер параллелизма\nLeast-Connections"}
        BucketCheck -->|Лимит исчерпан| NextKey1["Выбор следующего здорового ключа"]
        ConcurrencyCheck -->|Слот свободен| KeyResolver["ctx.credentials.resolve"]
        ConcurrencyCheck -->|Слот занят| NextKey1
        
        KeyResolver --> ActiveKey["Активный ключ (в работе)"]
        
        ActiveKey -.->|HTTP 429 / Quota / Ошибка| Failover["Мгновенный failover"]
        Failover --> BackoffCalc["Экспоненциальный бэкофф и карантин"]
        Failover --> NextKey2["Повтор со следующим ключом (без потери токенов)"]
        Failover -.->|Все ключи в кулдауне| CascadeEngine["Межпровайдерный каскад"]
        
        BackoffCalc --> QuotaWindow["Календарный сброс / Полночь UTC/PST"]
        BackoffCalc --> SelfHeal["Self-Heal sweep простаивания"]
        SelfHeal -->|Кулдаун истёк| PoolReady["Возврат в пул готовых ключей"]
    end

    subgraph UpstreamLayer ["Эндпоинты провайдеров"]
        ActiveKey --> UpstreamAPI["Основной API провайдера"]
        CascadeEngine --> FallbackAPI["Резервный API провайдера"]
    end

    style ClientLayer fill:#1e1e2e,stroke:#89b4fa,stroke-width:2px,color:#cdd6f4
    style RotationEngine fill:#181825,stroke:#cba6f7,stroke-width:2px,color:#cdd6f4
    style UpstreamLayer fill:#11111b,stroke:#a6e3a1,stroke-width:2px,color:#cdd6f4
```

---

## ✨ Исчерпывающий разбор возможностей

### 🔄 1. Прозрачная ротация и мгновенный Failover
* **Сохранение сессии агента**: Ротация подменяет только физический API-токен, оставляя неизменным ID провайдера. Исключает падения `INVALID_REPLAY_STATE` в мульти-инструментальных сценариях.
* **Бесшовный повтор до отдачи чанков**: Если ключ падает с ошибкой до начала генерации первого токена, запрос прозрачно перенаправляется на следующий доступный ключ.
* **Полный спектр кодов переключения**: Авто-переключение при ошибках `QUOTA`, `RATE_LIMIT`, `SERVER`, `TIMEOUT`, `TRANSPORT`, `EMPTY_RESPONSE`, `UNKNOWN_MODEL`, `AUTH` и `INVALID`.
* **Интеллектуальный парсер текста ошибок**: Регулярные выражения (`SWITCHABLE_MESSAGE_PATTERN`) распознают текстовые сообщения об исчерпании лимитов, которые SDK провайдеров выбрасывают как неструктурированные исключения.
* **Защита нестриминговых вызовов**: Синхронные операции (эмбеддинги, батч-оценки) защищены хуком `agent/request-error`.

### ⏱️ 2. Предиктивный Token Bucket и контроль параллелизма
* **Token Bucket / Leaky Bucket (`lib/bucket.js`)**: Скользящее минутное окно отслеживания запросов (`rpmLimit`) и токенов (`tpmLimit`). Блокирует исчерпанный ключ **до** отправки запроса в сеть, исключая потерю времени на сетевой 429.
* **Балансировщик Least-Connections (`lib/concurrency.js`)**: Отслеживает активные in-flight соединения на каждом ключе (`inFlight`). Равномерно распределяет параллельную нагрузку и соблюдает лимит `maxConcurrency`.
* **Авто-очистка зависших блокировок**: При аварийном разрыве сетевых соединений счетчики соединений автоматически очищаются через 5 минут.

### 🛡️ 3. Автономное самовосстановление и каскадный Failover
* **Межпровайдерный каскад (`lib/cascade.js`)**: При исчерпании всех ключей выбранного провайдера запрос автоматически каскадируется на настроенного резервного провайдера (`cascade: [{ provider, model }]`).
* **Sandbox-пробы ключей (`lib/sandbox.js`)**: По запросу выполняется `/models`-проба ключа перед возвратом в ротацию; простаивающие кулдауны снимаются self-heal sweep.
* **Календарный сброс квот (`lib/quota-window.js`)**: Учитывает окна сброса суточных квот провайдеров (`midnight_utc`, `midnight_pst`, `rolling_24h`), снимая карантин ровно в момент обновления лимитов у апстрима.
* **Экспоненциальный бэкофф (`lib/pool.js`)**: Повторные сбои на ключе прогрессивно увеличивают время кулдауна (базовое → ×2 → ×4 → максимум ×8).

### 🎯 4. Маршрутизация по моделям
* **Модельные подпулы (`lib/pool.js`)**: Назначение выделенных ключей под конкретные модели (например, отдельные ключи для тяжелых reasoning-моделей и дешевые ключи для утилит).
* **Тегирование ключей**: Метки приоритета (`production`, `background`, `eval`) для разделения квот между интерактивными и фоновыми задачами.

### 📊 5. Телеметрия, аналитика и интерактивные вебхуки
* **Интерактивные вебхуки (`lib/webhook.js`)**: Отправка форматированных алертов с кнопками действий в **Telegram** (Inline Keyboards), **Discord** (Action Rows) и **Slack** (Block Kit). Администратор может сбросить кулдаун или отключить провайдер прямо из мессенджера.
* **Отчеты об использовании и расходах (`lib/usage-report.js`)**: Учет суточного числа запросов и расчетной стоимости по каждому ключу с экспортом в CSV/JSON (`GET /dsh-key-rotation/usage-report`).
* **Гистограмма задержек SLO (`lib/histogram.js`)**: Измерение времени до первого токена (TTFT) и расчет индекса здоровья пула (`0..100`).

---

## 🖥️ Панель управления в Web GUI

Управление доступно в разделе **Настройки → Ротация ключей** или через быстрый виджет в шапке.

| Элемент интерфейса | Описание |
|---|---|
| **Виджет в шапке / статусбаре** | Компактный статус: 🟢 `Все в норме` \| 🟡 `Есть ключи в кулдауне` \| 🔴 `Пул исчерпан` с быстрым всплывающим меню. |
| **Матрица здоровья (1-Click Matrix)** | Интерактивная таблица "Health Matrix" с параллельным зондированием всех ключей, моделей и отображением задержки TTFT. |
| **Быстрое добавление ключей** | Добавление ключа в 1 клик с автогенерацией имени (`<PROVIDER>_API_KEY`, `_2`, `_3`) и маскировкой. |
| **Живые бейджи статуса** | Индикаторы: `Используется`, `Готов`, `Остывает` (с живым таймером обратного отсчета) и `Не найден`. |
| **Приоритет ключей** | Кнопки <kbd>↑</kbd> и <kbd>↓</kbd> для настройки точного порядка перебора в пуле. |
| **Чекбоксы кодов ошибок** | Наглядные переключатели условий срабатывания ротации. |
| **Детектор утечек секретов** | Валидация форматов токенов (`lib/keycheck.js`) и защита от случайной вставки приватных SSH/RSA-ключей. |
| **Импорт из `.env`** | Массовая загрузка пар `KEY=value` из файлов конфигурации. |
| **Отмена действий (5 сек)** | Всплывающая плашка отмены при случайном удалении ключа или пула. |
| **График активности** | Наглядная статистика запросов и динамики использования ключей. |

---

## 🔒 Безопасность и хранение секретов

* **Никаких открытых секретов в конфигурации**: В настройках плагина хранятся только имена переменных окружения (например, `PROVIDER_API_KEY`).
* **Защищённое хранилище хоста**: Реальные значения ключей сохраняются в `$DSH_HOME/.credentials.yaml` сервисом `Credentials`.
* **Маскировка в браузере (5 символов)**: Браузер получает только последние 5 символов ключа для визуального отличия.
* **Ограничение Loopback**: Все управляющие маршруты (`GET /status`, `PUT /key`, `POST /reset`, `POST /test-matrix`) строго проверяют loopback-происхождение запроса (`isTrustedBridgeRequest`).

---


### 🔁 8. Обновление плагина в один клик

В карточке настроек есть раздел **Updater**:

1. **Проверить обновления** — `GET /api/dsh-key-rotation/update` возвращает `currentVersion`, `latestVersion`, `updateAvailable`, `canAutoUpdate` (только метаданные версии).
2. **Обновить сейчас** — `POST` ставит точную последнюю npm-версию штатным `dsh plugin add`. Запрос принимается только с loopback и совпадающим same-origin `Origin`/`Host`; иначе `403`.
3. После установки UI предлагает **перезапустить DSH**.

Без `--force`, без raw shell и установки из worktree/DEV. Обновление — только по явному нажатию.

## 📦 Установка

```bash
# Установка через менеджер плагинов DSH (профиль web):
dsh plugin --profile web add @goodandready/dsh-key-rotation

# Или напрямую из GitHub:
dsh plugin --profile web add github:GooDAnDReaDY/dsh-key-rotation
```

> [!IMPORTANT]
> После установки перезапустите веб-сервис DeepSeek Harness и обновите вкладку браузера:
> ```bash
> systemctl --user restart dsh-web
> ```

---

## ⚙️ Пример конфигурации (`settings.yaml`)

```yaml
dsh-key-rotation:
  switchCodes:
    - QUOTA
    - RATE_LIMIT
    - SERVER
    - TIMEOUT
    - TRANSPORT
    - EMPTY_RESPONSE
    - UNKNOWN_MODEL
    - AUTH
  cooldownMs: 60000
  circuitBreakerEnabled: true
  circuitBreakerThreshold: 5
  circuitBreakerOpenMs: 30000
  circuitBreakerHalfOpenProbes: 1
  concurrencyLimit: 5
  quotaResetWindow:
    type: midnight_utc
    hour: 0
  cascade:
    - provider: backup-provider-id
      model: your-backup-model-id
  webhookUrl: "https://api.telegram.org/bot<TOKEN>/sendMessage?chat_id=<CHAT_ID>"
  providers:
    - provider: your-primary-provider
      rpmLimit: 60
      tpmLimit: 100000
      keys:
        - PRIMARY_API_KEY
        - PRIMARY_API_KEY_2
        - PRIMARY_API_KEY_BACKUP
    - provider: secondary-provider
      keys:
        - SECONDARY_API_KEY
        - SECONDARY_API_KEY_2
```

### Таблица параметров

| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
| `switchCodes` | `string[]` | `[QUOTA, RATE_LIMIT, ...]` | Список кодов ошибок, инициирующих немедленный переход на следующий ключ. |
| `cooldownMs` | `number` | `60000` (1 мин) | Базовая длительность нахождения ключа в карантине (в мс). |
| `concurrencyLimit` | `number` | `0` (отключено) | Лимит одновременных активных запросов на ключ (0 = без ограничений). |
| `quotaResetWindow` | `object` | `null` | Календарное расписание сброса квот (`midnight_utc`, `midnight_pst`, `rolling_24h`). |
| `cascade` | `array` | `[]` | Цепочка резервных провайдеров при исчерпании всех ключей основного пула. |
| `webhookUrl` | `string` | `""` | URL вебхука для интерактивных алертов в Telegram, Discord или Slack. |
| `providers` | `array` | `[]` | Список определений пулов `{ provider, keys, rpmLimit, tpmLimit, modelPools }`. |

---

## 🔌 Справочник HTTP Bridge API

Все служебные маршруты требуют локальной авторизации (`127.0.0.1` / `::1`) и проверки Same-Origin:

| Маршрут | Метод | Описание |
|---|---|---|
| `/dsh-key-rotation/status` | `GET` | Текущий снимок состояния здоровья, активных ключей и кулдаунов. |
| `/dsh-key-rotation/config` | `GET` / `PUT` | Чтение и изменение активных параметров ротации и пулов провайдеров. |
| `/dsh-key-rotation/key` | `PUT` / `DELETE` | Добавление, обновление или удаление ключей в хранилище и пуле. |
| `/dsh-key-rotation/reset` | `POST` | Мгновенный сброс всех кулдаунов и возврат ключей в статус `ready`. |
| `/dsh-key-rotation/test-matrix` | `POST` | Запуск параллельного тестирования всей матрицы ключей и моделей. |
| `/dsh-key-rotation/usage-report` | `GET` | Получение сводного отчета использования в формате JSON или CSV (`?format=csv`). |
| `/dsh-key-rotation/webhook-callback`| `POST` | Обработка интерактивных действий от кнопок в Telegram/Slack. |

---

## 📄 Лицензия

MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)

### v0.7.39
- **Самоисцеление ключей и фиксация lastUsedAt**: Исправлено чтение меток времени в `healIdleCooldowns`, использующее теперь карту `pool.state.lastUsedAt`. В `credentials.resolve` добавлено сохранение точного времени каждого обращения к ключу, что активирует вывод времени использования в панели мониторинга и корректное самоисцеление ключей после простоя.
- **Оптимизация обращений к buildRuntime**: Устранены повторные вызовы `buildRuntime()` в процедурах периодической очистки, маршруте `/status` и блоке обработки исчерпания пула.
- **Стабилизация CI и устранение утечек таймеров**: Фоновые таймеры дебаунса и очистки теперь отвязываются (`unref`) от цикла событий Node.js, предотвращая зависание раннера. Удалены тесты устаревшего модуля инцидентов.

### v0.7.38
- **Оптимизация горячего пути стриминга**: Устранены 4 избыточных вызова `buildRuntime()` при обработке завершающего чанка в `rotate()` за счёт повторного использования снимка `runtime0`.
- **Парсинг заголовков лимитов без лишних аллокаций**: Оптимизирована функция `extractRateLimit()` — однократный проход по объекту с проверкой длины ключей без постоянных аллокаций строк `.toLowerCase()` и `.toUpperCase()`.
- **Мемоизация строки даты ISO**: Дата текущего дня (`todayIso`) теперь вычисляется ровно один раз на запрос, исключая создание нескольких дублирующих объектов `Date` для `costDays` и `usageDays`.
- **Итеративный подсчет totalUsage**: В эндпоинте статуса `/dsh-key-rotation/status` расчёт суммарного потребления переведён на прямой цикл без аллокации промежуточного массива `[...values()]`.
- **Очистка устаревших записей уведомлений**: При удалении пулов провайдеров из конфигурации автоматически вычищаются связанные ключи из карт троттлинга уведомлений (`budgetNotifiedAt`, `lowHealthNotifiedAt`).

### v0.7.37
- **Изоляция контекста запросов через AsyncLocalStorage**: Полная привязка активного ключа (`pickedRef`), времени старта и попыток к асинхронному контексту вызова через `node:async_hooks`. Устранена гонка, при которой параллельные запросы могли ошибочно штрафовать здоровый ключ соседа.
- **Failover при исключениях в потоке до первого чанка**: Устранено аварийное прерывание потока при транспортных сбоях (например, выброс HTTP 429 до отправки заголовков). Теперь блок перехвата проверяет `isSwitchableError` и прозрачно переключает поток на запасной ключ, если клиенту ещё не было отдано полезных данных.
- **Чистый локальный список кандидатов в rotate()**: Устранена прямая мутация разделяемого массива `pool.weightedRefs` во время выполнения ротации.
- **Автоматический вывод из карантина при успехе проверки**: Успешная проверка ключа через песочницу в настройках (`/dsh-key-rotation/test`) моментально сбрасывает флаги карантина (`failedUntil` и `brokenUntil`).
- **Регулярная компактизация памяти**: Функция `compactUsage(pool, 30, now)` подключена в 30-секундный фоновый таймер пула, предотвращая утечки памяти при непрерывной многомесячной работе.
- **Точные метрики задержки при параллельных вызовах**: Переход с глобальной переменной времени старта на контекстный замер `startMs` гарантирует достоверность p50/p95 latency под нагрузкой.

### v0.7.36
- **Глубокий рефакторинг и деблоатинг**: Удалены 6 неиспользуемых модулей оверинжиниринга (`shadow`, `incident`, `agent-budget`, `region`, `canary`, `maintenance`) и сопутствующие устаревшие эндпоинты.
- **Мемоизация buildRuntime**: Устранено глубокое клонирование и повторный парсинг схемы на каждом токене/чанге стрима.
- **Атомарная ротация указателя**: Устранено состояние гонки при параллельных вызовах инструментов — указатель смещается сразу при выборе кандидата.
- **Точная детекция ошибок**: Прямая проверка кодов HTTP (`429`, `401`, `403`, `5xx`) и gRPC (`RESOURCE_EXHAUSTED`, `UNAVAILABLE`) с фоллбеком на регулярные выражения.
- **Понятные сообщения при исчерпании пула**: Информативное уведомление с таймером восстановления ближайшего ключа.
- **Энергоэффективный опрос (Smart Polling)**: Автоматическая приостановка фонового опроса при неактивной вкладке браузера (`document.visibilityState`).

### v0.7.35
- **Очистка жизненного цикла**: Патч `credentials.resolve` и слушатели событий `ctx.on` (`llm/stream`, `agent/request-error`) переведены в скоупы `ctx.effect` с автоматическим восстановлением функций и отпиской при выгрузке плагина (#238, #239).
- **Роли секретов в схеме**: Полям `incidentGitHubToken` и `webhookActionToken` в схеме `Config` присвоена роль `.role('secret')` для маскирования в UI (#237).
- **Архитектура настроек**: Добавлена нативная интеграция со снимками `settingsScope` в карточке настроек с безопасным фоллбеком на HTTP-мост (#235).
- **Распределение нагрузки и модальные окна (Changed in v0.8.5)**: Диаграмма распределения нагрузки (.krot-load-chart), модальные подтверждения (.krot-modal) и джиттер кулдаунов при 429/5xx (#283, #284).
- **Нативная дизайн-система (Changed in v0.8.3)**: Унификация со стилем `dsh-clinebot`: модульные карточки секций, плитки оперативной телеметрии пулов, капсульные бейджи и системные CSS-токены DSH (#281).
- **Локализация (Changed in v0.8.2)**: Исходные строки только на английском (`en`). Русский и китайский приходят через core `props.t` / translation-плагины. Фолбек активной локали: snapshot → первый `navigator.languages` → `en`. Свой `settings.section` и встроенные `ru`/`zh`-таблицы удалены (#236, #275, #277).
- **Удаление мёртвого кода**: Удалена неиспользуемая функция `mountDashboard` после перехода на header chip (#240).

### Параметры circuit breaker (Changed in v0.8.0)

| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
| `circuitBreakerEnabled` | boolean | `true` | Включить/выключить breaker |
| `circuitBreakerThreshold` | number | `5` | Число подряд ошибок до открытия |
| `circuitBreakerOpenMs` | number | `30000` | Длительность open, мс |
| `circuitBreakerHalfOpenProbes` | number | `1` | Проб в half-open |
| `verboseLogging` | boolean | `false` | Подробные логи rotation (шумно) |

Status API (v0.8.0): у каждого провайдера `circuit`, в корне ответа `meta.expectedClones` и `meta.notifyQueue`.

