# 📦 @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)"]
        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
```

---

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

### 🐝 Роевая декомпозиция задач и мультиагентный запуск (Swarm Delegation) (#350)
- **Управляемый пул воркеров**: Настройка `concurrencyLimit` (1..10, по умолчанию 3) для параллельного выполнения независимых подзадач без перегрузки системы.
- **Отказоустойчивая декомпозиция**: Ошибки отдельных воркеров изолированы; рой объединяет успешные результаты и корректно фиксирует частичные сбои.
- **Автоматический синтез**: Сведение результатов всех воркеров в структурированный Markdown-дайджест или через выделенную модель-синтезатор.
- **Метрики и история роя**: Сохранение количества успешных/сбойных задач, времени выполнения и детальных логов каждого воркера.

### 🔍 Просмотр Git Diff и интерактивное подтверждение патчей (#351)
- **Автоматический захват диффов**: Задачи в Git-каталогах автоматически сохраняют список файлов, сводку изменений и унифицированный diff (до 256 КиБ).
- **1-Click Discard**: Безопасный откат незакоммиченных изменений в один клик (`git reset --hard HEAD && git clean -fd`).
- **1-Click Apply**: Повторное наложение сохранённого патча на рабочий каталог (`git apply --whitespace=fix`).
- **Интерактивный просмотр**: Цветовая подсветка строк diff в карточке истории запусков.

### 🔀 Решающие узлы и условные развилки IF/ELSE (#352)
- **Маршрутизация по условию**: Задачи могут задавать `branchRules` для проверки результата (`exitCode`, `output`, `output.json.<path>`, `status`) через операторы (`equals`, `notequals`, `contains`, `gt`, `gte`, `lt`, `lte`, `regex`, `exists`).
- **Ветка Else**: Резервная задача `elseTask` запускается, если ни одно из правил не совпало.
- **Защита от взаимных блокировок**: Невыбранные ветви автоматически регистрируются со статусом `skipped`, предотвращая зависание барьеров синхронизации (`joinPolicy: 'all_success'`).
- **Визуализация в графе**: DAG Graph View отображает бейджи решающих узлов (`🔀 Decision`) и условия на соединительных линиях.

### 🩺 Автономный Доктор самовосстановления (Self-Healing Doctor) (#353)
- **Классификация сбоев**: Автоматическое распознавание 8 типов ошибок (`syntax_error`, `dependency_missing`, `rate_limit`, `auth_failed`, `schema_change`, `timeout`, `network_error`, `runtime_error`).
- **Unified Diff**: Наглядный просмотр диффа предлагаемого исправления до его применения.
- **1-Click применение и откат**: API эндпоинты `/doctor/apply` и `/doctor/revert` с сохранением до 10 ревизий в `task.revisions`.
- **Безопасная авто-починка**: При включённом `autoHeal: true` патчи с высокой уверенностью (`high`) применяются автоматически с лимитом не чаще 1 раза в сутки.

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

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

### 2. Встроенный schedule ядра DSH vs dsh-cron

В состав DeepSeek Harness входит легковесное встроенное расширение `@deepseek-ai/dsh-schedule` для базовых напоминаний в чате. Сравнительная таблица помогает выбрать правильный инструмент:

| Возможность | Встроенный `schedule` ядра (`@deepseek-ai/dsh-schedule`) | `@goodandready/dsh-cron` |
|:---|:---|:---|
| **Основное назначение** | Напоминания и таймеры, доставляемые обратно в текущий диалог пользователя | Автономный фоновый раннер и оркестратор запланированных задач |
| **Контекст исполнения** | Текущая активная сессия чата | Изолированные выделенные агентские сессии или фоновые процессы |
| **Рантаймы** | Только ход в диалоге (LLM-промпт) | 9 рантаймов: `llm`, `script` (bash/sh), `node`, `python`, `http` (REST/webhook), `ssh`, `docker`, `skill`, `workflow` |
| **Инструменты модели** | `schedule_create`, `schedule_list`, `schedule_delete` | Единый инструмент `cron` (action: `create`, `list`, `get`, `update`, `pause`, `resume`, `run`, `delete`) |
| **Размер схемы инструментов** | ~1.5k символов | ~1.5k символов (консолидация 9 инструментов в 1, экономия ~12k символов контекста) |
| **Каналы доставки** | Только текущий чат | Мультиканальность: Telegram, Discord, Slack, Webhook, Kanban, ntfy, Bark, PushPlus, Голос (TTS), Gitea |
| **Изоляция кода** | Отсутствует | Одноразовые или сохраняемые git worktree (`worktree: true`) |
| **Лимиты расходов и токенов** | Отсутствуют | Защита от перерасхода: `costLimitUsd`, `dailyCostLimitUsd`, `tokenLimit` с автопаузой |
| **Надёжность и мониторинг** | Отсутствуют | Повторы с экспоненциальным backoff, failure inspector, heartbeat-пинги (Dead Man's Snitch / Better Uptime) |
| **Тихое правило (Silent Rule)** | Отсутствует | Полная тишина при отсутствии изменений (ноль шума и спама в каналы) |
| **Управление задачами** | Базовый список / удаление | Полноценный UI-менеджер, история запусков, логи, метрики, ручной запуск, импорт/экспорт, синхронизация с конфигом |

### 3. Инструмент агента (`cron`)

Автономные агенты управляют расписаниями через единый компактный инструмент `cron`:

| Действие (action) | Описание | Основные параметры |
|:---|:---|:---|
| `create` | Создаёт фоновую задачу или периодический процесс | `title`, `schedule`, `prompt`, `type`, `model`, `channels`, `delivery` и др. |
| `list` | Список задач со статусами, временем следующего запуска, токенами и стоимостью | `status` (`'all'`, `'active'`, `'paused'`, `'completed'`) |
| `get` | Полная конфигурация одной задачи по её идентификатору | `id` |
| `update` | Изменяет существующую задачу на месте (переключение в код требует `confirmCodeSwitch: true`) | `id`, поля патча |
| `pause` | Приостанавливает расписание без удаления конфигурации | `id` |
| `resume` | Возобновляет приостановленное расписание | `id` |
| `run` | Немедленный внеплановый запуск задачи | `id` |
| `delete` | Полностью удаляет задачу и её историю | `id` |

> [!NOTE]
> **Оптимизация контекста и миграция**: Ранее 9 отдельных схем инструментов занимали ~13.6k символов в каждом ходе модели. Консолидация в один инструмент `cron` сократила этот объём на ~88% до ~1.5k символов. Легаси-имена инструментов (`cron_create_task`, `cron_schedule_task`, `cron_list_tasks` и т.д.) отклоняются с понятной подсказкой использовать `cron` с соответствующим `action`. Для простых напоминаний в диалоге модель ориентируется на встроенный `schedule_create`.

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

```json
cron({
  "action": "create",
  "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`, разрешаемому через службу `workspaceRegistry`) или пути; созданная для прогона сессия агента регистрируется в ведомости этого workspace (`attachSession`), поэтому появляется в группе проекта вместо «неразмещённых». Запуск в изолированном git worktree (`worktree`, `keepWorktree`).
* **Блокировка областей записи (Scope Locks)** — указание `writeScopes` (например `src/`, `docs/`) для защиты от конфликтов при одновременной работе агентов над одним проектом; пересекающиеся задачи безопасно ждут в очереди освобождения путей.
* **Контроль изменений входных данных (Input Hash)** — настройка `inputHashPaths` (`skipIfUnchanged`) вычисляет SHA-256 файлов/папок перед запуском; если входные файлы не менялись, запуск пропускается (`skipped (inputs unchanged)`), экономя 100% токенов и бюджета.

### 7. Экономия: fallback-модель и защита бюджета (Burn Guard)
* **Fallback-модель** — задача может идти на дешёвой модели по умолчанию и всё же завершиться на сильной: задайте `fallbackModel` (и при необходимости `fallbackProvider`), и сбойный запуск (`error` или `timeout`) один раз повторится на этой модели, прежде чем включится обычный retry с задержкой. В истории видно, какая модель произвела результат и был ли использован fallback; расход и стоимость обеих попыток суммируются; переменная шаблона `{model}` подставляет модель, завершившую запуск. Fallback доступен только агентским типам (`llm`, `skill`, `workflow`).
* **Защита бюджета токенов и расходов (Burn Guard)** — предотвращение неконтролируемых трат через индивидуальные лимиты задачи: `costLimitUsd` (общий лимит расходов в USD), `dailyCostLimitUsd` (суточный лимит за последние 24 часа в USD) и `tokenLimit` (лимит суммарных токенов). При превышении любого порога выполнение прекращается, задача автоматически переводится в паузу с фиксацией `pausedReason` (`cost_limit_exceeded`, `daily_cost_limit_exceeded`, `token_limit_exceeded`), а во все активные каналы отправляется тревожное оповещение.
* **Учёт токенов и расходов на событиях сессии** — считывание фактического расхода токенов напрямую из событий потока сессии DSH (`assistant/message`, `assistant/chunk` с типом usage, `assistant/attempt`), с разделением некэшированного ввода, чтений кэша, генерации и оплаченных сбойных попыток провайдера при повторах.
* **Скользящий суточный реестр (24h Cost Ledger)** — суточный лимит `dailyCostLimitUsd` опирается на независимый реестр расходов задачи за последние 24 часа, сохраняемый в `store.json`. Расходы сохраняются при ротации архива истории (свыше 100 запусков) и восстанавливаются после перезапуска службы.

### 8. Интеграция сессий и права
* **Извлечение реального ответа ассистента и статуса хода** — выделение событий только текущего хода (без подмешивания истории прошлых ходов долговременных сессий), сборка итогового текста сообщения ассистента и валидация признака завершения (`turn/end` с ошибкой или прерыванием) для безошибочной классификации сбоев, цепочек задач и выполнения директив.
* **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`, запускается свежий.
* **Прямая передача тиков расписания политикам наложения** — тики Croner поступают без подавления на уровне планировщика (`Croner protect: false`), гарантируя одинаковую работу политик `skip` (с записью в историю), `queue` (отложенный запуск) и `replace` (чистый abort) как для cron-расписания, так и для ручных вызовов.
* **Сохранение контекста очереди** — глобальная очередь конкурентности и очередь наложения сохраняют полный контекст выполнения (`chainDepth`, `prevOutput`, `prevTaskId`, `prevStatus`, `prevCostUsd`), предотвращая потерю параметров цепочек под нагрузкой.

Если сервис был выключен в момент планового запуска, при старте в истории появится запись `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`, `$DSH_PREV_TASK_ID`, `$DSH_PREV_STATUS` для shell/script задач и через подстановку `{{prev.output}}` (или `{{prevOutput}}`), `{{prev.taskId}}`, `{{prev.status}}` в LLM-промптах. Кроме того, в промптах поддерживаются динамические runtime-переменные: `{{date}}`, `{{time}}`, `{{datetime}}`, `{{timestamp}}`, `{{year}}`, `{{month}}`, `{{day}}`, `{{taskId}}`, `{{taskName}}` и `{{runCount}}`. Глубина рекурсии строго ограничена для защиты от зацикливания.
- **Структурированные действия 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`.
- **Безопасные preflight-проверки shell и диска**: Поддержка типов проверок `command` и `shell` с принципом fail-closed при синтаксических ошибках или сбоях проверки. Проверка диска поддерживает явные пути и пороговые значения (например, `/:10%`, `/data:500MB`).
- **Полное сохранение параметров надёжности**: Создание и редактирование задач через HTTP POST и агентский инструмент `cron_create_task` полностью сохраняют все 13 полей надёжности и управления сессиями (`agentPreset`, `targetSessionId`, `targetSessionReset`, `onSuccess`, `onFailure`, `heartbeatIntervalSeconds`, `gracePeriodSeconds`, `preflightType`, `preflightTarget`, `priority`, `concurrencyGroup`, `selfHealingCommand`, `autoDiagnose`, `fallbackProvider`, `fallbackModel`, `silentRule`, `inspectOnFailure`).

---

### 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` и исключены из автоматической архивации (`workspaceRegistry.archiveSession()`), поэтому они остаются доступны для чтения и прямого диалога в веб-интерфейсе 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% консистентность использования канонического репозиторного набора меток и фильтрации задач.
- **Самовосстановление стилей при динамических перерисовках DOM (#263 / GH-4)**: Отслеживает `<head>` и `<html>` через `MutationObserver`, резервный интервал 2с и событие `visibilitychange` (`startStyleSelfHeal`), мгновенно восстанавливая узел `<style id="dsh-cron-styles">` при перерисовке заголовка хостом, смене темы или очистке соседними модулями.

---

### 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: 2             # максимум параллельных запусков (по умолчанию 2, 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` сообщает живость, число активных задач и время последнего запуска для ваших собственных сторожей.
* **Оповещения о пропущенных heartbeat в режиме `onlyOnFailure`** — Пропущенные heartbeat (`status === 'missed'`) распознаются как полноценное состояние сбоя во всех фильтрах отправки (`channels.js`, `telegram.js`, `integrations.js`), гарантируя своевременную отправку тревог в Telegram, Discord, Webhook и Kanban при отсутствии отчётов от внешних служб.

### 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.
* **Удалённый доступ по Bearer-токену** — Внешние клиенты с любого разрешённого IP могут создавать и обновлять задачи, отправлять heartbeat-сигналы (`/dsh-cron/api/heartbeat/:id`) и запрашивать предпросмотр расписания (`/dsh-cron/api/schedule/preview`) с валидным `Authorization: Bearer <token>`.

```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` | `2` | Лимит параллельных запусков; лишние помечаются `skipped` (по умолчанию 2, 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-токена |
| `telegramAllowedChatIds` | `string` | `""` | Список chat ID или user ID Telegram через запятую, авторизованных для интерактивных команд бота |
| `apiToken` | `string` | `""` | Bearer-токен внешней поверхности `/dsh-cron/api/*`. Секретное поле, отдаётся замаскированным; пусто отключает поверхность (503), неверное значение — 401 |

Примечания:

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

---

## 🔌 HTTP API

Все эндпоинты обслуживаются веб-сервером DSH под `/dsh-cron/`. Эндпоинты защищены строгой проверкой источника (`isTrustedRequest`): удалённые не-loopback клиенты требуют аутентификацию токеном (`Authorization: Bearer <token>` или `x-dsh-cron-token`), а браузерные запросы проходят строгую валидацию совпадения `Host` и `Origin` с блокировкой `Origin: null` и ограничением `Sec-Fetch-Site` (`same-origin` или `none`). Маршруты heartbeat-ping строго требуют метод `POST`. В ответах `GET` чувствительные данные задач (`env`, `httpHeaders`, `httpBody`) маскируются как `'[REDACTED]'`, а при обновлении задач переданные плейсхолдеры `'[REDACTED]'` сохраняют оригинальные сохранённые секреты. Для создания `script`-задач по HTTP дополнительно требуется заголовок `x-dsh-cron-confirm: script`. Максимальный размер тела запроса ограничен 1 МБ.

| Метод | Путь | Описание |
|:---|:---|:---|
| `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) |
| `POST` | `/dsh-cron/webhook/:id` | Входящий HTTP Webhook: запуск задачи по событию с проверкой HMAC SHA-256 / токена, дебаунсом и подстановкой JSON payload в промпт (#348) |
| `POST` | `/dsh-cron/api/webhook/:id` | Внешний эндпоинт входящего вебхука под токеном (#348) |

---

### 32. Входящие HTTP Webhook триггеры и естественно-языковой парсер расписаний (v0.3.11, #348, #349)

- **Входящие Webhook триггеры (`/dsh-cron/webhook/:id`)**: Моментальный запуск задач по внешним событиям (GitHub, Gitea, CI/CD, сторонние сервисы).
  - **Аутентификация и безопасность**: Поддержка HMAC SHA-256 подписи (`x-hub-signature-256`, `x-signature-sha256`) через `webhookSecret`, индивидуальных токенов задачи (`webhookToken`) исключительно через заголовки (`x-dsh-cron-webhook-token`, `x-webhook-token`, `Authorization: Bearer`), а также мастер-токена `apiToken`. Приём токенов из query-параметров (`?token=`) строго заблокирован для предотвращения утечки в логи прокси и историю браузера (#358).
  - **Fail-closed защита API и RCE Guard (#357)**: Все маршруты плагина проверяются через DSH `ctx.connection.requestRejection(req)` для допуска браузерных сессий DSH, а также требуют валидный Bearer-токен или локальный loopback адрес. Запросы без валидной аутентификации отсекаются с 401/403 до парсинга тела, предотвращая несанкционированное создание задач и удалённое выполнение кода.
  - **Глубокая интерполяция payload**: Шаблоны `{webhook.body.path.to.key}`, `{webhook.headers.key}`, `{webhook.query.param}` и `{webhook.body}` автоматически раскрываются в промпте задачи.
  - **Проброс в окружение**: Скриптовым и shell-задачам передаются переменные `DSH_CRON_WEBHOOK_BODY` и `DSH_CRON_WEBHOOK_HEADERS`.
  - **Дебаунс от штормов**: Настраиваемый `webhookDebounceMs` (по умолчанию 500 мс) защищает от дублирующих всплесков событий.
- **Естественно-языковой парсер расписаний (`lib/nl-schedule.js`)**: Умное распознавание расписаний на русском и английском языках в чате, туле агента и API:
  - Интервалы: `"каждые 15 минут"`, `"every 2 hours"`, `"каждые 4 часа"`.
  - Время суток: `"каждый день в 9:00"`, `"every morning at 8am"`, `"в полночь"`, `"в полдень"`.
  - Будни и выходные: `"по будням в 10:00"`, `"every weekday at 9:00"`, `"по выходным в 12:00"`, `"on weekends at noon"`.
  - Дни недели: `"каждую пятницу в 17:00"`, `"every Monday at 10am"`, `"по вторникам и четвергам в 14:00"`.
  - Относительные отложенные задачи: `"через 15 минут"`, `"in 2 hours"`, `"через 3 дня в 10:00"`.

---

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

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

```bash
npm test
```

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

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

---

## 📄 Лицензия

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

### Надёжность таймеров, интервалов и групп параллелизма (0.2.39)

- **Защита от 32-битного переполнения таймера**: Одноразовые задачи, запланированные более чем на 24.85 дня вперёд (превышающие лимит 32-битного целого `setTimeout` в 2 147 483 647 мс в Node.js), выполняются через порционный таймер и не запускаются раньше времени.
- **Сохранение первоначального дедлайна относительных задач**: Задачи вида `in 30m` или `через 2 часа` сохраняют первоначальный целевой таймстемп при перезапусках сервера, перезагрузке хранилища, паузе/возобновлении и изменении полей задачи (title, tags, channels). Новый дедлайн вычисляется только при явном изменении поля расписания.
- **Строгая валидация и нормализация интервалов**: Недопустимые минутные интервалы свыше 59 минут (например, `every 90m`) отклоняются на входе с кодом HTTP 400 без создания неработающей активной задачи. Кратные интервалы (например, `every 120m` -> `every 2h`, `every 24h` -> `every 1d`) корректно приводятся к валидным cron-паттернам.
- **Единый безопасный лимит maxConcurrent**: Схема Config и планировщик TaskScheduler теперь согласованно используют лимит по умолчанию `2` (0 остаётся явным выбором безлимитного режима).
- **Изоляция пулов через concurrencyGroup**: Поле `concurrencyGroup` теперь строго контролируется при допуске задач и опустошении очереди (по умолчанию 1 параллельный запуск на именованную группу). Задачи из разных групп исполняются параллельно в пределах общего `maxConcurrent`, а задачи с политикой `queue` стартуют сразу при освобождении слота своей группы.


### Событийные триггеры пробуждения (v0.3.33)

Реактивный запуск задач агента по системным событиям:
- **Порог количества файлов (`file_count`)**: запуск при накоплении `>= N` файлов в целевой директории.
- **Файл-флаг / маркер (`marker_file`)**: запуск при появлении файла готовности (например, `ready.flag`) с автоматическим удалением (`consumeMarker`).
- **Завершение процесса (`process_exit`)**: отслеживание PID или файла `.pid` и запуск постобработки сразу после завершения фонового процесса.
Контекст передается в переменные промпта (`{{trigger.type}}`, `{{trigger.fileCount}}`) и окружения (`DSH_CRON_WAKE_TYPE`).

### Ограничение времени и окна допустимой работы (v0.3.34)

- **Time-Boxing (`timeBoxMinutes`)**: Лимит общего времени выполнения задачи в минутах с мягким прерыванием по таймауту.
- **Окна работы (`workWindow`)**: Запуск тяжелых задач только в разрешенные часы (например, `22:00-07:00`) или дни недели.
- **Политика вне окна (`outsidePolicy`)**: `skip` (пропуск с записью статуса) или `defer` (откладывание до открытия окна).

### Ежедневный дайджест и сводные отчеты (v0.3.35)

- **Тип задачи `type: "digest"`**: Настройка периодических отчетов (например, каждое утро в 09:00), агрегирующих количество запусков, сбои, токены и затраты в USD.
- **REST API**: Эндпоинты `GET /dsh-cron/digest` для просмотра статистики и `POST /dsh-cron/digest/send` для отправки в каналы.
- **Интеграция с каналами**: Доставка форматированного отчета в Telegram, Feishu или вебхуки.

### Cron-as-Code (`.dsh/cron.yaml`)
Декларируйте расписания задач прямо в репозитории проекта. `dsh-cron` автоматически обнаруживает и синхронизирует конфигурацию из `.dsh/cron.yaml`, `.dsh/cron.yml` или `.dsh/cron.json`:
```yaml
tasks:
  - id: nightly-audit
    title: Ночной аудит безопасности
    schedule: "0 2 * * *"
    command: npm audit
    timeBoxMinutes: 15
```
Задачи из репозитория изолированы и не перезаписывают ручные задачи, созданные в UI.

### Контекстная панель в сессиях чата (Quick Action Bar)
Управляйте регламентными задачами прямо из активного диалога DSH (`conversation.session.header.actions`):
- **Превращение сессии в регламентную задачу (`+ В крон`)**: сохраняйте текущий диалог или промпт как регулярную задачу с предзаполненными параметрами в один клик.
- **Индикация статуса и быстрый запуск**: отслеживайте статус выполнения привязанных к сессии фоновых задач и запускайте их немедленно прямо из заголовка чата.
