# 📦 @goodandready/dsh-cron

<div align="center">

<h3>Планировщик cron-задач, фоновая автоматизация и выполнение сценариев агентом для DeepSeek Harness</h3>

<p align="center">
  <a href="https://www.npmjs.com/package/@goodandready/dsh-cron"><img src="https://img.shields.io/npm/v/@goodandready/dsh-cron.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-cron.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/Все_проекты_автора-goodandready.app-ff4500.svg?style=for-the-badge&logo=rocket&logoColor=white&labelColor=1a1a2e" alt="Все проекты автора"></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>

---

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

Автономным AI-агентам регулярно нужны повторяющиеся действия: утренние сводки, разбор трекеров задач, проверка доступности API, синхронизация баз данных, периодическая гигиена Git. Без штатного планировщика внутри харнесса приходится использовать внешние обёртки над crontab, сложные webhook-схемы или ручной запуск.

**`@goodandready/dsh-cron`** — нативный полноформатный плагин планирования и фоновой автоматизации для DeepSeek Harness. Он связывает стандартные cron-выражения и естественные интервалы с автономным исполнением агентами:

1. **Развитый визуальный менеджер задач** — кнопка в сайдбаре со сворачиваемым списком активных задач (следующий запуск или живой статус, с ограничением и запоминанием состояния) и полноценная панель: фильтры по типу, модели и каналу, пауза, немедленный запуск, дублирование, экспорт/импорт и создание задач.
2. **Интерактивный сценарий «Создать с DSH»** — опишите задачу словами, агент уточнит детали и оформит расписание.
3. **Автономный tool calling** — нативные инструменты `cron_*` позволяют агентам планировать собственные последующие действия прямо в диалоге.
4. **Надёжный планировщик и атомарное хранилище** — на базе `croner`: интервалы, разовые задачи с задержкой, атомарная запись, история запусков, учёт стоимости.
5. **Шесть рантаймов исполнения** — shell, Node.js, Python, HTTP/webhook, удалённый SSH и Docker, плюс переменные окружения на задачу, привязка workspace и изолированные git worktree для изменяющих код агентских задач.
6. **Многоканальная доставка с шаблонами** — один запуск расходится в Telegram, dsh-kanban, Discord, Slack, ntfy, Bark, PushPlus, голос (`dsh-tts`) и Gitea, с шаблонами сообщений `{переменные}` и секретами по имени credential в DSH.

---

## 🏗️ Архитектура

```mermaid
graph TD
    subgraph Client ["Клиентская поверхность (DSH UI)"]
        SidebarBtn["Кнопка-часы в сайдбаре<br/>(слот DSH Client UI)"]
        Overlay["Панель управления задачами<br/>(табы: Все, Активные, На паузе, Завершённые)"]
        CreateWithDSH["Диалог «Создать с DSH»<br/>(задача на естественном языке)"]
        ManualForm["Ручная форма задачи<br/>(рантайм, cron, таймаут, overlap, каналы)"]
        SettingsCard["Карточка настроек<br/>(каналы, шаблоны, credentials)"]
    end

    subgraph Server ["Серверная часть (Cordis и сервисы DSH)"]
        HttpRoutes["HTTP REST API<br/>(/dsh-cron/*)"]
        AgentTools["Шлюз tool calling<br/>(cron_create_task, cron_list_tasks, ...)"]
        Scheduler["Движок TaskScheduler<br/>(экземпляры Croner + таймеры one-shot)"]
        Store["Атомарный TaskStore<br/>(tasks.json, атомарная запись)"]
        AgentRunner["Диспетчер агентских сессий<br/>(запуск промпта выбранной моделью)"]
        Runtimes["Рантаймы исполнения<br/>(shell, node, python, http, ssh, docker)"]
        Notify["Маршрутизатор доставки<br/>(шаблоны + 9 каналов)"]
        Secrets["Credential-ссылки<br/>(DSH credentials / ENV)"]
    end

    SidebarBtn --> Overlay
    Overlay --> CreateWithDSH
    Overlay --> ManualForm
    SettingsCard --> HttpRoutes
    CreateWithDSH -->|POST /chat/start| HttpRoutes
    ManualForm -->|POST /tasks| HttpRoutes
    HttpRoutes --> Scheduler
    AgentTools --> Scheduler
    Scheduler --> Store
    Scheduler -->|Запуск по интервалу/one-shot| AgentRunner
    Scheduler --> Notify
```

---

## ✨ Возможности

### 1. Визуальный менеджер задач
Нажмите на иконку-часы в сайдбаре DSH (рядом с кнопкой новой сессии), чтобы открыть панель:
* **Табы фильтрации**: **Все**, **Активные**, **На паузе**, **Завершённые**.
* **Мгновенные действия**: немедленный запуск (**Запустить**), пауза/возобновление расписания, удаление с подтверждением.
* **Готовые шаблоны в один клик**: *Ежедневная сводка*, *Еженедельный обзор*, *Мониторинг дальнейших действий*.
* **История запусков**: в карточке задачи — время, длительность и статусы предыдущих запусков (успех / сбой / таймаут / пропуск / пропущен по простою), вывод и ошибки.
* **Сводная статистика**: активные задачи, всего запусков, израсходованные токены и оценочная стоимость в долларах.

### 2. Диалог «Создать с DSH»
Превратите естественный язык в задачу без подбора cron-синтаксиса:
1. Нажмите **Создать ⌄** ➔ **Создать с DSH**.
2. Опишите, что нужно автоматизировать (например: *«Проверяй открытые PR по будням в 9:00 и готовь черновики комментариев»*).
3. Плагин создаст отдельную агентскую сессию с системными инструкциями планировщика. Агент уточнит детали — LLM или NO-LLM shell-задача, точное cron-выражение, экономичная модель из доступных в вашей установке DSH, нужно ли «правило тишины» (алерт только при новых событиях или сбоях) — и создаст задачу через инструмент `cron_create_task` только после вашего подтверждения.

### 3. Инструменты агентов (tool calling)

| Инструмент | Описание |
|:---|:---|
| `cron_create_task` | Создаёт задачу: `title`, `schedule`, `prompt`, `fallbackModel` (одна повторная попытка на сильной модели при сбое), опционально `type` (`llm`/`script`/`node`/`python`/`http`/`ssh`/`docker`/`skill`/`workflow`), `delivery`, `provider`, `model`, `channels`, `template`, `notifyTelegram`, `onlyOnFailure`, `timeoutSeconds`, `overlapPolicy`, `kanbanMode` |
| `cron_schedule_task` | Псевдоним `cron_create_task` для совместимости с существующими промптами |
| `cron_list_tasks` | Список задач со статусами, временем следующего запуска, токенами и стоимостью |
| `cron_pause_task` | Приостанавливает расписание без удаления конфигурации |
| `cron_resume_task` | Возобновляет приостановленное расписание |
| `cron_delete_task` | Полностью удаляет задачу и её историю |
| `cron_run_task` | Немедленный внеплановый запуск |
| `cron_get_task` | Полная конфигурация одной задачи, включая поля, которых нет в списке |
| `cron_update_task` | Изменяет существующую задачу на месте (whitelisted-поля, та же валидация, что у HTTP-маршрута); модели предписано сперва подтверждать с пользователем изменения, исполняющие код |

Пример вызова модели в диалоге:

```
cron_create_task({
  "title": "Утренняя сводка",
  "schedule": "0 8 * * 1-5",
  "prompt": "Подготовь короткую утреннюю сводку активных задач и открытых тикетов.",
  "type": "llm",
  "delivery": "isolated"
})
```

### 4. Синтаксис расписаний
На базе `croner`: стандартные 5-полевые cron-выражения и дружелюбные алиасы:

* `0 9 * * 1-5` — по будням в 09:00
* `*/15 * * * *` — каждые 15 минут
* `0 0 * * 0` — каждое воскресенье в полночь
* `every 10m` / `every 2h` / `every 30s` — естественные интервалы
* алиасы `daily` / `hourly` / `weekdays`, а также стандартные `@hourly` / `@daily` / `@weekly` / `@monthly` / `@yearly` и `@every 30m`
* **Часовые пояса** — для задачи можно указать IANA-зону (например, `Europe/Berlin`); без неё расписание живёт в серверном времени
* **Разовые задачи**: `at: 2026-09-05T15:00:00Z` (точный ISO-таймстемп) или относительные задержки `in 20m` / `in 2h` (принимаются и русские варианты вроде `через 15 минут`). После единственного запуска задача автоматически переходит в `completed` и отображается на табе **Завершённые**.

### 5. Надёжность исполнения
* **Автоповторы** — `maxRetries` и база `retryBackoffMs` на задачу: упавшие запуски (error/timeout) повторяются с экспоненциальной задержкой, счётчик сбрасывается после успеха.
* **Misfire-политики** — что делать с пропущенным за время простоя запуском: `skip` (по умолчанию — записать пропуск), `runOnce` (выполнить один раз с опозданием) или `catchUpAll` (выполнить и зафиксировать пропуск). Пропущенный one-shot при `skip` уходит в `completed` без выполнения.
* **Лимит параллельности** — настройка `maxConcurrent` ограничивает число одновременных запусков; лишние помечаются `skipped` с причиной.
* **Живой индикатор** — в списке задач пульсирует статус и идёт таймер текущего запуска.

### 6. Рантаймы исполнения
Каждая задача выбирает собственный рантайм; не-LLM рантаймы не используют модель и не тратят токены:

* **Shell** (`script`) — команда или скрипт через shell харнесса, с `env` и `cwd`.
* **Node.js** (`node`) и **Python** (`python`) — запуск сниппета с указанием интерпретатора (`nodePath`, `pythonPath`); для Python определяется виртуальное окружение проекта.
* **HTTP** (`http`) — GET/POST/… по URL с собственными заголовками и телом; статус и вывод ответа попадают в историю запуска.
* **SSH** (`ssh`) — выполнение команды на удалённом хосте через профиль `dsh-remote-workspace` (`sshProfileId`) или отдельные поля host/key.
* **Docker** (`docker`) — выполнение команды в контейнере образа (`dockerImage`).
* **Переменные окружения** — карта `env` на задачу (в UI — строки KEY VALUE) для внешних рантаймов; секретам здесь не место.
* **Workspace и worktree** — привязка задачи к workspace харнесса (`workspaceId`) и, для изменяющих код агентских задач, запуск в изолированном git worktree (`worktree`, `keepWorktree`).

### 7. Экономия: fallback-модель
Задача может идти на дешёвой модели по умолчанию и всё же завершиться на сильной: задайте `fallbackModel` (и при необходимости `fallbackProvider`), и сбойный запуск (`error` или `timeout`) один раз повторится на этой модели, прежде чем включится обычный retry с задержкой. В истории видно, какая модель произвела результат и был ли использован fallback; расход и стоимость обеих попыток суммируются; переменная шаблона `{model}` подставляет модель, завершившую запуск. Fallback доступен только агентским типам (`llm`, `skill`, `workflow`).

### 8. Интеграция сессий и права
* **Permission-пресеты на задачу** — `default`, `read-only`, `workspace-write` или `full` применяются к сессии агента перед запуском промпта.
* **Автоархивация сессий** — изолированные cron-сессии архивируются после запуска (best-effort), не засоряя список чатов.
* **История → сессия** — каждый LLM-запуск хранит свою сессию; открыть диалог можно прямо из записи истории.

### 9. Тишина по правилу
У задачи с выводом может быть **правило тишины**, написанное словами («молчи, если ни один раздел не занят больше 80%»). На успешном запуске дешёвая модель сверяет вывод с правилом, и отчёт пропускается, если вердикт — молчать; причина сохраняется в истории запуска. Работает fail-open: нет правила, нет модели, сбой вызова или нечитаемый ответ — отчёт доставляется. Настройка `silentRuleModel` задаёт модель для проверки.

### 10. Диагностика сбоев
Агентские задачи могут заказывать диагноз: с включённым `inspectOnFailure` сбойный запуск (`error` или `timeout`) вместе с промптом задачи и обрезанным выводом читает модель, и в историю запуска попадают короткий диагноз и конкретная правка промпта. В записи истории есть кнопка, подставляющая эту правку в форму редактирования — автоматически ничего не применяется. Модель задаётся настройкой `inspectorModel`, в шаблонах доступна переменная `{diagnosis}`. Недоступная модель оставляет сбойный запуск ровно таким, каким он был.

### 11. Каналы доставки и шаблоны сообщений
Отчёт о завершённом запуске уходит во все каналы, выбранные для задачи — Telegram, dsh-kanban, Discord, Slack, ntfy, Bark, PushPlus, голос через `dsh-tts` и issue в Gitea:

* **Перенос задач** — экспорт всей конфигурации в версионированный JSON и импорт с предварительной сводкой; импортированные задачи приходят на паузе.
* **Каналы на задачу** — отметьте каналы в форме задачи; явный выбор перекрывает legacy-переключатели `notifyTelegram`/`kanbanMode`, а пустой выбор возвращается к ним.
* **Изоляция сбоев** — недоступный канал фиксируется в логе планировщика, остальные каналы получают отчёт; сломанный webhook не поглощает доставку целиком.
* **Шаблоны сообщений** — глобальный шаблон, переопределения по каналам или шаблон на задачу с переменными `{title} {id} {status} {output} {error} {duration} {schedule} {time} {tokens} {cost}`. Неизвестные плейсхолдеры остаются как есть, для сбойных запусков по умолчанию используется шаблон ошибки.
* **`onlyOnFailure`** — глобально или на задачу: успешные запуски молчат, уходят только `error`/`timeout`.
* **Креденшелы по ссылке** — токены webhook'ов и токен Telegram вводятся как ИМЯ credential в DSH (`botTokenRef`, `ntfyTokenRef`, `pushplusTokenRef`, `giteaTokenRef`); значение резолвится в момент отправки через credentials-сервис DSH с фолбэком на переменную окружения и никогда не проходит через настройки плагина. Webhook-URL и ключ устройства Bark содержат секрет внутри, поэтому хранятся в настройках плагина, но всегда отдаются в браузер замаскированными, а замаскированное значение из UI никогда не перезаписывает сохранённое.
* **Таймаут доставки** — каждый запрос канала ограничен (`deliveryTimeoutMs`, по умолчанию 15000 мс, задаётся в панели настроек или `settings.yaml`), каналы отправляются параллельно: недоступный endpoint фиксируется как сбой и не задерживает остальные каналы и следующий тик расписания. Ограничение действует на весь обработчик канала, включая резолв credential'ов, который не поддерживает abort-сигнал.
* **Telegram** — Markdown-отчёт со статусными значками (✅ / ❌), длительностью, описанием расписания и monospace-блоком вывода; динамические значения экранируются. Креденшелы можно ввести напрямую или унаследовать из секции `dsh-messenger-gateway` вашего DSH `settings.yaml` (best-effort).
* **Discord / Slack** — доставка через webhook: Discord получает embed с цветом по статусу запуска, Slack — обычный текст.
* **ntfy / Bark / PushPlus** — мобильные пуши: тема/ключ устройства и опциональный bearer-токен; у Bark заголовок и текст идут в пути запроса, у PushPlus endpoint настраивается (self-hosted прокси).
* **Голос** — `dsh-tts` озвучивает отчёт через свой HTTP-маршрут (`ttsBaseUrl`, по умолчанию `http://127.0.0.1:3080`).
* **Gitea** — создаёт issue с отчётом (`giteaBaseUrl`, `giteaRepo`, credential токена); сбойные запуски помечаются метками `cron`, `bug`, `alert`.
* **Кнопка проверки** — проверьте доставку в Telegram до запуска критичных задач.

### 12. Интеграция с Kanban и учёт стоимости
* **Автоматические карточки Kanban** — при `kanbanMode` = `on_failure` или `always` плагин создаёт карточки в `dsh-kanban` (`on_failure` → *Backlog* при `error`/`timeout`; `always` → *Done*/*Backlog* по завершении).
* **Счётчик токенов и стоимости** — потребление токенов (ввод, вывод, чтения из кэша) учитывается по запускам и задачам с оценкой в USD по встроенной таблице цен и сводной панелью аналитики.

### 13. Политики наложения и таймаут выполнения

* **Таймаут (`timeoutSeconds`)** — по достижении лимита shell-процесс немедленно завершается через abort-сигнал, а агентская сессия закрывается, чтобы не расходовать токены. По умолчанию `1800` (30 минут).
* **Политика наложения (`overlapPolicy`)** — что делать, когда тик срабатывает при ещё активном предыдущем запуске:
  * **`skip`** (по умолчанию): накладывающийся запуск отбрасывается, в истории появляется запись `skipped`;
  * **`queue`**: следующий запуск ставится в очередь и стартует по завершении активного;
  * **`replace`**: активный запуск прерывается через `AbortController`, запускается свежий.

Если сервис был выключен в момент планового запуска, при старте в истории появится запись `missed` — пробелы в истории остаются видимыми.

### 21. Пакет производительности и изоляции процессов (v0.2.9, #134)
- **Изоляция дерева процессов**: Shell и Script задачи запускаются в отдельной группе процессов (POSIX `detached: true`); при отмене или таймауте сигнал `-child.pid SIGTERM -> SIGKILL` завершает всё дерево, исключая зомби-процессы.
- **Троттлинг параллелизма**: Безопасный лимит `maxConcurrent = 2` по умолчанию предотвращает всплески нагрузки на CPU и RAM.
- **Повторы транзиентных сбоев**: Экспоненциальный backoff для ошибок 429 и 5xx (до 3 попыток).
- **Сетевая и UI-оптимизация**: `GET /dsh-cron/tasks` поддерживает `ETag` и `304 Not Modified`; адаптивный опрос UI (30с в фоне, 8с на активной вкладке).
- **Ротация истории и архив**: В памяти удерживается до 100 последних запусков на задачу, остальные архивируются в `tasks-history-archive.json`.
- **Рецепт автономного PR-ревьюера (#33)**: Готовый шаблон в Template Hub и тумблер `prReviewerEnabled`.

### 22. Автоматизация, цепочки задач и наблюдаемость (v0.2.10, #137)
- **Двухсторонний интерактивный Telegram**: Кнопки действий под уведомлениями (`🚀 Run Now`, `⏸️ Pause`, `📋 Last Output`), вебхук `POST /dsh-cron/telegram/webhook` с валидацией прав по Chat ID и откликом `answerCallbackQuery`.
- **Цепочки задач и конвейеры**: Триггеры `onSuccess` и `onFailure` для связывания задач. Передача вывода родительской задачи в переменную `$DSH_PREV_OUTPUT` (для shell) и `{{prevOutput}}` (для LLM). Ограничение глубины (максимум 5 уровней) против зацикливания.
- **Структурированные действия LLM**: Парсер директив модели (`trigger_task`, `notify`, `create_issue`) под опцией `llmActionsEnabled: false`.
- **Архивация и задержка в UI**: REST API `/dsh-cron/tasks/:id/archive` с пагинацией и статистика `/stats`. Бейджи латентности на карточках задач (<5с зелёный, <30с жёлтый, ≥30с красный).
- **Расширенные Prometheus-метрики**: Gauge `dsh_cron_concurrent_running`, счетчики токенов и стоимости в USD на задачу.

### 23. Расширенная надёжность, самовосстановление, Heartbeat и UX (v0.2.11, #139)
- **Мониторинг тишины (Heartbeat / Dead Man's Snitch)**: Эндпоинты `/dsh-cron/heartbeat/:id` и `/dsh-cron/api/heartbeat/:id` для приёма внешних пингов от бэкапов и демонов. При отсутствии пинга в пределах `heartbeatIntervalSeconds` + `gracePeriodSeconds` фиксируется статус `missed`, рассылается тревога и запускается `onFailure`.
- **Pre-flight проверки (условный запуск)**: Предварительная проверка HTTP-статуса 2xx, exit-кода команды или свободного места на диске. При непрохождении задача переходит в `skipped` без траты токенов LLM.
- **Dry-Run и симулятор расписания**: Тестовый запуск `POST /dsh-cron/tasks/:id/dry-run` и кнопка `🧪 Dry Run` в UI без записи в историю и без отправки в каналы; расчет следующих тиков через `POST /dsh-cron/schedule/preview`.
- **Очереди с приоритетами**: При достижении лимита параллелизма задачи упорядочиваются по полю `priority` (1 — наивысший, 10 — низший).
- **Команды самоисцеления и авто-диагностика (Self-Healing)**: Автоматический запуск компенсирующей команды `selfHealingCommand` при падении задачи; опция `autoDiagnose` для генерации AI-диагностики причин сбоя.
- **Интерактивный архив логов в UI**: Модальное окно просмотра истории с пагинацией и полным выводом логов, визуальные ссылки конвейеров `➜ onSuccess` и `↳ onFailure`.

---

### 24. Автоматическое подключение пресетов агента и инструментов (#141 / GH-1, добавлено в v0.2.12)
- **Автоматическое монтирование пресета агента**: Запланированные автономные `llm`-задачи и интерактивные запуски агента теперь автоматически определяют и подключают пресет агента системы (по умолчанию используется стандартный пресет пользователя через `presets.mount(agentCtx, preset.id)` внутри хука `setup`). Автономные сессии по расписанию получают полный доступ к инструментам (файлы, рабочее окружение, терминал и т.д.) вместо изолированного чата без инструментов.
- **Индивидуальный пресет для задачи**: Для каждой задачи можно явно задать идентификатор `agentPreset` в веб-интерфейсе, через REST API или в декларативных задачах профиля (например, `coding`, `system`, `minimal`). Если поле не заполнено, автоматически применяется пресет по умолчанию из настроек харнесса.
- **Безопасная деградация**: Если сервис `agentPresets` недоступен или указан несуществующий пресет, планировщик выводит информативное предупреждение и штатно продолжает выполнение модели без аварийной остановки задачи.

---

### 25. Долговременные сессии и непрерывность контекста (`targetSessionId`, добавлено в v0.2.13, #143)
- **Непрерывный контекст диалога**: Для задач можно задать `targetSessionId`. При наличии этого идентификатора планировщик возобновляет существующую сессию через `agents.resume()` вместо создания одноразовой сессии (`cron-exec-${id}-${uuid}`) на каждом тике. Агент сохраняет память предыдущих ходов и может ссылаться на ранее обнаруженные данные и выводы.
- **Защита от переполнения контекста и ротация (`targetSessionReset`)**: Чтобы контекстное окно и расход токенов не разрастались бесконечно при частых запусках, предусмотрены политики автоматической ротации:
  - `never`: единая непрерывная сессия без сброса.
  - `daily`: ежедневная автоматическая ротация (`<id>-YYYY-MM-DD`).
  - `weekly`: еженедельная автоматическая ротация (`<id>-YYYY-Www`).
  - Шаблоны дат: в `targetSessionId` поддерживается плейсхолдер `{{date}}`, который автоматически заменяется на текущую дату `YYYY-MM-DD`.
- **Видимость в списке чатов DSH**: Долговременные сессии не помечаются как `ephemeral`/`internal` и исключены из автоматической архивации (`sessions.archive()`), поэтому они остаются доступны для чтения и прямого диалога в веб-интерфейсе DSH.
- **Совместимость с пресетами и инструментами**: При возобновлении сессии автоматически подключаются инструменты пресета `agentPreset`, гарантируя доступ к терминалу, файлам и командам.
- *Благодарность*: концепция вдохновлена разработкой [@RaulLazaro](https://github.com/RaulLazaro).

---

### 26. Пакет надежности, отказоустойчивости и самовосстановления (v0.2.14, #145)
- **Автоматический сброс бюджета повторов**: Исправлена «амнезия повторов». Когда задача исчерпывает лимит попыток (`maxRetries`), счётчик `attempts` автоматически обнуляется, поэтому следующий плановый запуск по расписанию получает полный бюджет повторов с нуля. Любой регулярный или ручной запуск гарантированно начинает выполнение со сброшенным счётчиком попыток.
- **Устранение «зомби»-задач в очереди**: Задачи, приостановленные через интерфейс/API или удалённые, мгновенно вычищаются из очереди ожидания параллелизма (`this.queue`). При освобождении слотов очереди неактивные или удалённые задачи безопасно пропускаются.
- **Ограничение архива истории**: В длительно работающих инсталляциях с высокочастотными cron-задачами файл архива `tasks-history-archive.json` теперь надёжно ограничен последними 1 000 запусками на задачу, предотвращая неконтролируемый рост диска и синхронные задержки сериализации JSON.
- **Аварийное восстановление и авто-бэкап хранилища**: `TaskStore` автоматически поддерживает атомарную резервную копию `tasks.json.bak` при каждом успешном сохранении. В случае сбоя или повреждения файла хранилище делает снимок `tasks.json.corrupted.<timestamp>` для диагностики и бесшовно восстанавливается из резервной копии.
- **Самовосстановление при переполнении контекстного окна**: Если в долговременной сессии (`targetSessionId`) очередной ход агента завершается ошибкой переполнения контекста модели (`context_length_exceeded`), раннер распознаёт переполнение, архивирует исчерпанную сессию, автоматически выполняет ротацию на свежую сессию и прозрачно повторяет выполнение без срыва задачи.
- **Корректное завершение дерева процессов в Windows**: На платформе Windows отмена или таймаут внешних скриптовых задач теперь вызывают `taskkill /pid <pid> /T /F`, гарантируя полное уничтожение всех дочерних процессов и оболочек без зависания зомби-процессов в системе.

---

### 27. Ограничение высоты модальных окон и гигиена пакета (v0.2.15, #153, #148, #151, #152)
- **Ограничение по высоте экрана и липкий подвал**: Модальные окна (включая создание, редактирование и готовые шаблоны-рецепты) теперь строго ограничены высотой экрана `max-height: min(90vh, calc(100vh - 36px))` с плавным внутренним скроллом. Подвал окна с кнопками («Отмена», «Сохранить», «Создать») закреплён через `position: sticky`, гарантируя постоянную доступность кнопок действий при любой длине формы и любом разрешении экрана.
- **Защита оверлея от вылетов**: Оверлей модального окна получил безопасные отступы и свойство `overflow-y: auto`, исключая срезание контента при flexbox-центрировании на компактных дисплеях.
- **Оптимизация размера npm-пакета**: Удалены дублирующие файлы документации из списка дистрибуции npm, снизив вес архива более чем на 32 kB, а распакованный размер — на 102 kB.
- **Соответствие манифеста клиента Cordis**: В `package.json` явно задекларированы зависимости инжекции `locale` и `slots` в секции `dsh.client.inject`.

---



### 31. Пакет ужесточения стандартов качества и CI (v0.2.19, #149, #152, #155, #156, #162)
- **Устранение пустых catch (#156)**: Реализован модуль `lib/best-effort.js` по единому стандарту с безопасным выполнением синхронных/асинхронных операций и опциональным логированием. Все 63 пустых catch-блока в планировщике, раннере, хранилище и клиентских фрагментах устранены.
- **Автоматический CI и локальный гейт Preflight (#162)**: Добавлены workflows непрерывной интеграции (`.gitea/workflows/ci.yml` и `.github/workflows/ci.yml`), проверяющие синтаксис, тесты и запускающие механический аудит `scripts/ci-preflight.mjs` с блокировкой любых отклонений.
- **Модернизация токенов темы (#149)**: Оставшиеся цвета `rgba(...)` в модальных окнах и карточках переведены на современный стандарт `color-mix(in srgb, var(--токен) N%, transparent)`.
- **Декларация зависимостей в манифесте (#152)**: В `package.json` поле `dsh.client.inject` переведено на полные имена пакетов Cordis (`@deepseek-ai/dsh-client-locale`, `@deepseek-ai/dsh-client-ui-slots`).
- **Активация экспортов в рабочем коде (#155)**: Функции `findDestructiveRecipe`, `shouldNotifyTask` и переменные `TEMPLATE_VARIABLES` подключены в активные цепочки работы с рецептами, роутинга уведомлений и рендеринга шаблонов.

---

### 30. Локализация автообновления на английский и китайский языки (v0.2.18, #160)
- **Локализация карточки обновления (#160)**: Добавлены нативные переводы для всех 10 ключей модуля автообновления (`updater.title`, `updater.btnCheck`, `updater.checking`, `updater.btnUpdate`, `updater.updating`, `updater.desc`, `updater.current`, `updater.available`, `updater.upToDate`, `updater.success`) в словари английского (`en`) и китайского (`zh`) языков в `lib/client-src/10-locales.js`. В соответствии со стандартами ядра DSH плагин поставляется с EN и ZH словарями, а русская локализация обслуживается через системный плагин `dsh-russian-lang`.

---

### 29. Модульная декомпозиция клиента и стандартизация тем оформления (v0.2.17, #149, #150)
- **Модульная архитектура клиентской части (#150)**: Монолитный файл `lib/client.js` (~3950 строк) декомпозирован на 14 независимых специализированных модулей в каталоге `lib/client-src/` (каждый строго <= 580 строк). Сборка `lib/client.js` выполняется легковесным скриптом `scripts/build-client.mjs` перед запуском тестов и сборки. Исходные фрагменты исключены из npm-дистрибутива через `"files": ["lib/*.js", ...]`.
- **Семантические токены тем DSH (#149)**: Инлайн-стили тегов типа задач (`onSuccess`, `onFailure`, `heartbeat`, `targetSession`, `preflight`) переведены на выделенные CSS-классы `.dsh-cron-tag-*` на базе переменных темы `--dsh-cron-*`. Оверлей модальных окон переведен на адаптивную маску `var(--dsw-alias-bg-mask, rgba(0, 0, 0, 0.75))`, а анимация пульсации избавлена от захардкоженных значений RGBA.
- **Аудит меток репозитория (#95)**: Подтверждена 100% консистентность использования канонического репозиторного набора меток и фильтрации задач.

---

### 28. Модуль самообновления в 1 клик и устойчивость к ошибкам (v0.2.16, #147, #155, #156)
- **Самообновление плагина (#147)**: Добавлен встроенный модуль обновления (`lib/updater.js`) с маршрутом `/api/dsh-cron/update` и карточкой в настройках Web UI. Автоматически опрашивает npm registry, корректно сравнивает semver-версии (включая пререлизы) и обновляет `@goodandready/dsh-cron` на лету через CLI DSH без необходимости входа по SSH. Запросы POST защищены проверкой источника (`rejectCrossOrigin`).
- **Устранение скрытых сбоев (#156)**: Ликвидированы пустые `catch`: незавершённые откаты транзакций импорта логируются с уровнем `warn`, причины fallback для динамических модулей ядра фиксируются в отладочном журнале, а при сбое открытия сессии пользователь получает понятное уведомление.
- **Гигиена кода и экспортов (#155)**: Удалены устаревшие неиспользуемые сущности (`CHANNEL_LABELS`, `makeInspectAsk`), снят лишний модификатор `export` с 16 внутренних функций и констант, а метод `supportsSilentRule` напрямую подключен в боевой пайплайн проверки тихих правил.

---

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

```bash
dsh plugin --profile web add @goodandready/dsh-cron
```

Перезапустите DeepSeek Harness и обновите страницу в браузере.

---

## ⚙️ Конфигурация (`settings.yaml`)

Конфигурацию можно задать в `settings.yaml` или интерактивно через карточку настроек плагина в DSH:

```yaml
# settings.yaml
dsh-cron:
  botToken: ""                 # токен Telegram Bot API (секретное поле)
  chatId: ""                   # ID чата Telegram для отчётов
  notifyTelegram: false        # глобально отправлять отчёты о всех задачах
  onlyOnFailure: false         # отправлять отчёты только при сбоях
  kanbanBaseUrl: "http://127.0.0.1:3000"  # базовый URL HTTP API dsh-kanban
  defaultTimezone: ""          # IANA-зона по умолчанию (пусто = серверное время)
  maxConcurrent: 0             # максимум параллельных запусков (0 = без лимита)
  heartbeatUrl: ""             # URL dead man's snitch, пингуется по интервалу
  heartbeatIntervalSec: 0      # интервал heartbeat-пинга в секундах (0 = выключено)
  # --- каналы доставки ---
  botTokenRef: ""              # ИМЯ credential для токена Telegram-бота
  template: ""                 # глобальный шаблон сообщения, напр. "⏰ {title} — {status}"
  channelTemplates: {}         # переопределения шаблонов по каналам
  deliveryTimeoutMs: 15000     # таймаут доставки на канал; медленный канал = сбой, остальные не ждут
  discordWebhookUrl: ""        # webhook Discord
  slackWebhookUrl: ""          # incoming webhook Slack
  ntfyUrl: "https://ntfy.sh"   # сервер ntfy; ntfyTopic / ntfyTokenRef
  ntfyTopic: ""
  ntfyTokenRef: ""
  barkServerUrl: "https://api.day.app"  # сервер Bark; barkKey — ключ устройства
  barkKey: ""
  pushplusUrl: "https://www.pushplus.plus/send"  # pushplusTokenRef
  pushplusTokenRef: ""
  ttsBaseUrl: "http://127.0.0.1:3080"   # базовый URL dsh-tts
  giteaBaseUrl: ""             # giteaRepo = owner/repo, giteaTokenRef = ИМЯ credential
  giteaRepo: ""
  giteaTokenRef: ""
  # --- внешний REST API (#54) ---
  apiToken: ""                 # bearer-токен внешнего префикса /dsh-cron/api/* (маскируется; пусто = 503)
```

### 14. Мониторинг heartbeat (dead man's switch)
* Задайте `heartbeatUrl` и `heartbeatIntervalSec` в настройках плагина — планировщик будет пинговать этот адрес по расписанию, и внешний монитор сообщит, когда пинги прекратятся.
* Встроенный эндпоинт `GET /dsh-cron/heartbeat` сообщает живость, число активных задач и время последнего запуска для ваших собственных сторожей.

### 15. Задачи из конфига профиля (#50)
Долгоживущие эксплуатационные задачи можно объявлять в конфиге профиля, а не пересоздавать руками в интерфейсе. Владелец объявленных задач — файл конфига: при каждом старте плагина они создаются или обновляются, а задача, исчезнувшая из файла, удаляется.

Добавьте список `jobs` в секцию плагина конфига профиля (`cordis.patch.yml`):

```yaml
dsh-cron:
  jobs:
    - id: nightly-backup
      title: Nightly backup
      schedule: "0 3 * * *"
      type: script
      prompt: "bash /path/to/backup.sh"
      channels: ["telegram"]
      timeoutSeconds: 3600
    - id: morning-digest
      title: Morning digest
      schedule: "0 8 * * 1-5"
      type: llm
      prompt: "Prepare a brief morning digest of active tasks."
      provider: my-provider
      model: provider-id/model-id
```

* Обязательные поля записи: `id`, `title`, `schedule`; типам, у которых полезная нагрузка — это промпт (`script`, `node`, `python`, `ssh`, `docker`, `llm`, `skill`, `workflow`), нужен ещё непустой `prompt`. `http` — исключение: цель задаётся `httpUrl` (или `prompt`).
* Остальные поля задачи проходят как есть с той же валидацией, что и в API: `channels`, `model`, `provider`, `fallbackModel`, `silentRule`, `inspectOnFailure`, `timezone`, `timeoutSeconds`, `template`, `env`, `cwd` и рантайм-поля (`nodePath`, `pythonPath`, `httpUrl`, `httpMethod`, `httpHeaders`, `httpBody`, `sshProfileId`, `sshTarget`, `dockerImage`, `workspaceId`, `worktree`, `keepWorktree`, `skillName`, `workflowName`).
* Объявленные задачи помечаются как **управляемые конфигом**; в панели вместо действий правки и удаления выводится метка источника.
* Правка, пауза, возобновление, переключение и удаление конфиг-задачи отклоняются с `409` в панели и по API, и создание-обновление через `POST /dsh-cron/tasks` с существующим `id` конфиг-задачи отклоняется так же — источник правды файл конфига. **Запустить сейчас** остаётся доступным.
* Задача с тем же `id`, созданная через UI, API или инструмент агента, никогда не перезаписывается: запись пропускается, конфликт пишется в лог.
* Код-исполняющие типы активируются как обычные объявленные задачи, но при старте плагин пишет предупреждение в лог — путь исполнения кода, добавленный правкой конфига, остаётся видимым.
* Записи валидируются по одной с указанием индекса (`config.jobs[i]: …`); одна плохая запись пропускается и не может остановить остальные задачи или профиль.

### 16. Внешний REST API (`/dsh-cron/api/*`, #54)
Внешние системы (CI, cron хоста, `curl`) могут управлять планировщиком без открытия панели. Это единственная поверхность за bearer-токеном; маршруты панели остаются локальными и защищёнными от cross-origin.

Токен задаётся настройкой плагина `apiToken` (маскируется, как любой секрет). Аутентификация и ошибки:
* токен не задан → вся поверхность отвечает `503`;
* нет заголовка `Authorization: Bearer <token>` или токен неверный → `401`; сравнение постоянное по времени.

| Метод | Путь | Описание |
|:---|:---|:---|
| `GET` | `/dsh-cron/api/tasks` | Список задач (фильтры `status` / `query`, как в панели) |
| `GET` | `/dsh-cron/api/tasks/:id` | Чтение одной задачи |
| `POST` | `/dsh-cron/api/tasks` | Создание задачи или обновление существующей при наличии `id` |
| `DELETE` | `/dsh-cron/api/tasks/:id` | Удаление задачи |
| `POST` | `/dsh-cron/api/tasks/:id/run` | Принудительный немедленный запуск |

Операции переиспользуют обработчики панели, поэтому гейт `x-dsh-cron-confirm: script` для код-исполняющих типов и отказ `409` для конфиг-задач действуют здесь так же, как в UI.

```bash
BASE="http://127.0.0.1:3080"
TOKEN="<API_TOKEN>"

# список
curl -s -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks"

# создание или обновление, если в теле есть id
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"id":"cleanup","title":"Cleanup","schedule":"0 4 * * *","prompt":"Remove stale temporary files."}' \
  "$BASE/dsh-cron/api/tasks"

# принудительный запуск
curl -s -X POST -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks/cleanup/run"

# удаление
curl -s -X DELETE -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks/cleanup"

# код-исполняющей задаче нужен ещё заголовок подтверждения
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "x-dsh-cron-confirm: script" \
  -H "Content-Type: application/json" \
  -d '{"title":"Disk check","schedule":"0 * * * *","type":"script","prompt":"df -h"}' \
  "$BASE/dsh-cron/api/tasks"
```

### 17. Метрики Prometheus (#53)
`GET /dsh-cron/metrics` отдаёт текст в формате Prometheus, поэтому планировщик можно снимать scrape'ом без новых зависимостей:

* `dsh_cron_tasks_total{status}` — число задач по статусам (gauge).
* `dsh_cron_task_last_duration_seconds{task}` — длительность последнего завершённого запуска задачи в секундах (gauge).
* `dsh_cron_runs_total{status}` — завершённые запуски с момента старта процесса плагина (counter); статусы `success`, `error`, `timeout`, `skipped`, `missed`.
* `dsh_cron_run_records` — число записей о запусках, хранимых в памяти (gauge).

В экспозицию попадают только счётчики, статусы и длительности; промпты, вывод запусков и конфигурация задач в неё не входят.

```yaml
scrape_configs:
  - job_name: dsh-cron
    static_configs:
      - targets: ["127.0.0.1:3080"]
    metrics_path: /dsh-cron/metrics
```

### 18. Строгая проверка каналов (#121)
Создание или обновление задачи с неизвестным идентификатором канала теперь отклоняется с `400`, а виновники перечисляются в ответе:

```json
{ "ok": false, "error": "Unknown channel ids: email_ping", "unknownChannels": ["email_ping"] }
```

Changed in v0.2.7: раньше неизвестный идентификатор молча отбрасывался, поэтому клиент с опечаткой получал `ok: true` и задачу, которая никуда не доставляет.

Импорт намеренно остаётся терпимым (файл может быть из старой версии): неизвестные идентификаторы отбрасываются у импортируемой задачи, но перечисляются в ответе (`unknownChannels`) и пишутся в лог планировщика, а не исчезают молча.

### 19. Проверка после установки (#126)
У `deploy.sh` есть режим только-проверки уже установленного профиля, ничего не устанавливающий:

```bash
bash deploy.sh verify [exact-version]
```

Он проверяет, что профиль сообщает нужную версию (по умолчанию — версия из `package.json`), аутентифицируется в web UI, затем скачивает клиентский бандл и убеждается, что имя пакета в нём присутствует.

Зачем это нужно: web-профиль может стоять за плагином аутентификации и отвечать `401` на анонимный запрос, а клиентский бандл плагина отдаётся только по точному combined-URL вида `??` из аутентифицированного индекса — голый `/plugins/<name>/client.js` отвечает `404`. Поэтому проверка сначала строит аутентифицированную сессию.

Переменные окружения проверки: `DSH_WEB_BASE` (по умолчанию `http://127.0.0.1:3080`), `DSH_WEB_TOKEN` (токен; если не задан, скрипт берёт последний из журнала юнита), `DSH_WEB_UNIT` (по умолчанию `dsh-web.service`). Секретов в скрипте нет.

### 20. Внутренняя разбивка: разбор расписания и постановка (#97)
Только для разработчиков, поведение не меняется. `parseScheduleExpression` разбит на маленькие функции с тем же порядком ветвей — `parseAtExpression`, `parseRelativeOneShot`, `parseIntervalExpression`, `parseAliasExpression`, `parseCronExpression`, — а `scheduleTask` — на `clearScheduled`, `scheduleOneShot` и `scheduleCron`. Прежний набор тестов прошёл без правок, добавлены точечные тесты на приоритет ветвей и ошибки.

### Параметры

| Параметр | Тип | По умолчанию | Описание |
|:---|:---|:---|:---|
| `botToken` | `string` | `""` | Токен Telegram Bot API. Если пусто, плагин пытается унаследовать бота, настроенного для `dsh-messenger-gateway` в настройках DSH (best-effort). Секретное поле: в интерфейсе отображается только замаскированное значение |
| `chatId` | `string` | `""` | ID чата Telegram для отчётов. Пустое значение — откат к первому разрешённому чату `dsh-messenger-gateway` |
| `notifyTelegram` | `boolean` | `false` | Глобальный выключатель доставки отчётов в Telegram |
| `onlyOnFailure` | `boolean` | `false` | Глобальный режим «только при сбоях» (`error`/`timeout`) |
| `kanbanBaseUrl` | `string` | `"http://127.0.0.1:3000"` | Базовый URL HTTP API `dsh-kanban` для автоматических карточек |
| `defaultTimezone` | `string` | `""` | IANA-зона по умолчанию для расписаний; пусто = серверное время |
| `maxConcurrent` | `number` | `0` | Лимит параллельных запусков; лишние помечаются `skipped` (0 = без лимита) |
| `heartbeatUrl` | `string` | `""` | URL dead man's snitch, пингуемый каждый `heartbeatIntervalSec`, пока жив планировщик |
| `heartbeatIntervalSec` | `number` | `0` | Интервал heartbeat-пинга в секундах (0 = выключено) |
| `botTokenRef` | `string` | `""` | Имя credential DSH с токеном Telegram-бота; резолвится при отправке (фолбэк: `botToken` → настройки messenger-gateway → переменная окружения `CRON_TELEGRAM_BOT_TOKEN`) |
| `template` | `string` | `""` | Глобальный шаблон сообщения с плейсхолдерами `{title}`/`{status}`/`{duration}`/…; пусто = встроенный текст |
| `channelTemplates` | `object` | `{}` | Переопределения шаблонов по каналам (`telegram`, `discord`, …) |
| `deliveryTimeoutMs` | `number` | `15000` | Таймаут доставки на канал; более медленный endpoint фиксируется как сбой и не задерживает остальные каналы и следующий тик |
| `discordWebhookUrl` / `slackWebhookUrl` | `string` | `""` | Webhook-URL каналов Discord и Slack |
| `ntfyUrl` / `ntfyTopic` / `ntfyTokenRef` | `string` | `"https://ntfy.sh"` / `""` / `""` | Сервер ntfy, тема и опциональное имя credential токена (`Authorization: Bearer …`) |
| `barkServerUrl` / `barkKey` | `string` | `"https://api.day.app"` / `""` | Сервер Bark и ключ устройства (ключ, заголовок и текст идут в пути запроса) |
| `pushplusUrl` / `pushplusTokenRef` | `string` | `"https://www.pushplus.plus/send"` / `""` | Endpoint PushPlus (переопределяется для self-hosted прокси) и имя credential токена |
| `ttsBaseUrl` | `string` | `"http://127.0.0.1:3080"` | Базовый URL плагина `dsh-tts` для голосовых объявлений |
| `giteaBaseUrl` / `giteaRepo` / `giteaTokenRef` | `string` | `""` | Канал Gitea: базовый URL, `owner/repo` и имя credential API-токена |
| `apiToken` | `string` | `""` | Bearer-токен внешней поверхности `/dsh-cron/api/*`. Секретное поле, отдаётся замаскированным; пусто отключает поверхность (503), неверное значение — 401 |

Примечания:

* История запусков ограничена **50 записями на задачу** (фиксировано); в записи хранится до 4000 символов вывода.
* Задачи выполняются в **локальном часовом поясе сервера**, если для задачи не указана своя IANA-зона; cron-выражения вычисляет `croner` по часам хоста.
* Задачи сохраняются в каталоге данных DSH (`cron/tasks.json`) и переживают перезапуск; пропущенные one-shot запуски обнаруживаются при старте.

---

## 🔌 HTTP API

Все эндпоинты обслуживаются веб-сервером DSH под `/dsh-cron/`. Чтение открыто локальному интерфейсу; **мутирующие эндпоинты отклоняют cross-origin запросы** и принимают тела до 1 МБ. Для создания `script`-задач по HTTP дополнительно требуется заголовок `x-dsh-cron-confirm: script`, который подделанный межсайтовый запрос приложить не может.

| Метод | Путь | Описание |
|:---|:---|:---|
| `GET` | `/dsh-cron/tasks` | Список задач; параметры `status` (`all/active/paused/completed`), `query` (подстрока). Возвращает задачи, шаблоны рекомендаций и сводную статистику |
| `POST` | `/dsh-cron/tasks` | Создание или обновление задачи (при наличии `id` — обновление). Обязательны `title`, `schedule`, `prompt` |
| `GET` | `/dsh-cron/tasks/:id/history` | История запусков, `?limit=20` |
| `POST` | `/dsh-cron/tasks/:id/run` | Немедленный ручной запуск |
| `POST` | `/dsh-cron/tasks/:id/pause` | Пауза расписания |
| `POST` | `/dsh-cron/tasks/:id/resume` | Возобновление расписания |
| `POST` | `/dsh-cron/tasks/:id/toggle` | Переключение активна/на паузе |
| `POST` | `/dsh-cron/tasks/:id/duplicate` | Копия задачи в статусе «на паузе»: настройки копируются, история и счётчики сбрасываются |
| `GET` | `/dsh-cron/recipes` | Встроенный каталог рецептов: готовые мониторинговые пресеты по категориям, все только на чтение |
| `GET` | `/dsh-cron/tasks/export` | Версионированный JSON только с конфигурацией задач — без истории и счётчиков. Каналы ссылаются на credential по имени, но введённые вручную `env` и HTTP-заголовки задачи являются частью конфигурации и попадают в файл |
| `POST` | `/dsh-cron/tasks/import` | Проверяет документ и применяет его стратегией `add`, `replace` или `skip`; поддерживает `dryRun`. Импортированные задачи всегда приходят **на паузе** — восстановление не сработает само |
| `PATCH` | `/dsh-cron/tasks/:id` | Частичное обновление (только whitelisted-поля: `title`, `schedule`, `prompt`, `type`, `delivery`, `provider`, `model`, настройки уведомлений/таймаута/overlap/kanban, `status`, `oneShot`) |
| `DELETE` | `/dsh-cron/tasks/:id` | Удаление задачи |
| `GET` | `/dsh-cron/models` | Список LLM-провайдеров; `?provider=<id>` — модели |
| `POST` | `/dsh-cron/chat/start` | Старт агентской сессии «Создать с DSH» с инструкциями планировщика |
| `GET` | `/dsh-cron/settings` | Настройки для клиента (токен замаскирован) |
| `POST` | `/dsh-cron/settings` | Обновление настроек интеграций (через службу настроек) |
| `GET` | `/dsh-cron/heartbeat` | Probe живости: число активных задач, время последнего запуска |
| `POST` | `/dsh-cron/telegram/test` | Тестовое сообщение в Telegram |
| `POST` | `/dsh-cron/kanban/test` | Тестовая карточка в Kanban |
| `*` | `/dsh-cron/action/:id/:action` | Legacy-алиас действий над задачей (`run`, `toggle`, `delete`, `history`) |
| `GET` | `/dsh-cron/metrics` | Текст в формате Prometheus: счётчики задач и запусков — без промптов и вывода (#53) |
| `GET` / `POST` | `/api/dsh-cron/update` | Самообновление плагина в 1 клик: запрос версии в реестре и обновление на лету (#147) |
| `GET` / `POST` | `/dsh-cron/api/tasks` | Внешняя поверхность под токеном: список / создание-обновление (#54) |
| `GET` / `DELETE` | `/dsh-cron/api/tasks/:id` | Внешняя поверхность под токеном: чтение / удаление (#54) |
| `POST` | `/dsh-cron/api/tasks/:id/run` | Внешняя поверхность под токеном: принудительный запуск (#54) |

---

## 🧪 Тестирование и локальная проверка (Preflight)

Запуск автоматического набора тестов (разбор расписаний, движок планировщика, атомарное хранилище, HTTP-хелперы, уведомления и контракт инструментов):

```bash
npm test
```

Локальный запуск гейта стандартов качества (синтаксис, отсутствие пустых catch, соответствие токенам тем оформления, чистота состава npm-пакета и защита от утечек):

```bash
node scripts/ci-preflight.mjs
```

---

## 📄 Лицензия

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