## MODIFIED Requirements ### Requirement: Цикл выполнения Агент SHALL работать одним процессом: `claim` к хабу (долгий запрос) → при получении команды: досыпать до `pause_seconds` от последнего вызова Jira → запись «started» в журнал → выполнение по контракту `commands` → запись «finished» с результатом → отчёт хабу, повторяемый до подтверждения. Одна команда в полёте: новый `claim` агент SHALL отправлять, только когда у него нет выданной, но не завершённой и не отчитанной команды (на этом хаб строит повторную выдачу, см. `commands`). При изменении `settings_version` в ответе `claim` агент SHALL перечитать настройки до выполнения команды. #### Scenario: Остановка между командами - **WHEN** получен Ctrl-C во время выполнения команды - **THEN** текущая команда доделывается и отчёт отправляется, затем процесс завершается ### Requirement: Журнал на диске и восстановление Агент SHALL вести `journal.jsonl` (только дозапись) рядом с собой: строки `claimed {command_id, at}` (сразу после получения команды, до паузы), `started {command_id, at}`, `finished {command_id, status, result, error, jira_calls, rate_limited, at}`, `acked {command_id, at}`. При старте агент SHALL: отправить хабу все `finished` без `acked`; для `claimed`/`started` без `finished` — запросить команду у хаба по id и, если она в хабе всё ещё `claimed`, выполнить её заново по правилу «проверь, потом сделай» с паузой `pause_seconds` из ответа хаба, не записывая `claimed` повторно. Команда `python3 agent.py prune` SHALL удалять из журнала записи с `acked` старше 30 дней. #### Scenario: Падение после ответа Jira - **WHEN** агент упал между `POST /issue` и записью `finished`, затем перезапущен - **THEN** агент находит в журнале `started` без `finished`, запрашивает команду у хаба, находит созданную задачу поиском дубля и отчитывается `done` с её ключом; второй задачи нет #### Scenario: Восстановление без лишней записи - **WHEN** в журнале `claimed 40` без `finished`, хаб отдаёт команду 40 в `claimed` с `pause_seconds = 30` - **THEN** в журнал дописываются `started 40`, `finished 40`, `acked 40`; второй строки `claimed 40` нет; перед выполнением агент выдерживает 30 с от последнего вызова Jira ### Requirement: Ограничение темпа и 429 Агент SHALL помнить время последнего вызова Jira (любого) и не начинать команду раньше `pause_seconds` после него. При старте агент SHALL принять за время последнего вызова Jira самое позднее `at` в журнале (если оно не в будущем); при пустом журнале паузы перед первой командой нет. На HTTP 429 и 503 агент SHALL ждать `Retry-After` (иначе 60 с × номер попытки) и повторять тот же запрос до 5 раз, считая попытки в `rate_limited`; после 5 неудач — `failed`. Пауза между записями внутри одной команды — по зазору типа команды. #### Scenario: 429 с Retry-After - **WHEN** Jira ответила 429 с `Retry-After: 90` - **THEN** агент ждёт 90 с, повторяет тот же запрос; в отчёте `rate_limited = 1` #### Scenario: Перезапуск не сбивает темп - **WHEN** последняя запись журнала `finished` сделана 10 с назад, агент перезапущен и получил команду с `pause_seconds = 30` - **THEN** агент ждёт около 20 с перед первым вызовом Jira по команде ### Requirement: Доставка и запуск README агента SHALL описывать: копирование `agent.py` и `agent.example.json` через общую папку, заполнение `agent.json` (включая выбор транспорта в `hub_url`), `check`, запуск в терминале и через `nohup` с логом, опциональный launchd-plist, `prune`. Версия агента SHALL быть в константе файла (3 — с восстановлением темпа после перезапуска) и в каждом отчёте; хаб предупреждает в `agent_status`, если версия ниже минимальной из настроек. #### Scenario: Устаревший агент - **WHEN** агент версии 1 выходит на связь, а в настройках `min_agent_version = 2` - **THEN** `agent_status.warning` = «обновите агента», команды выдаются по-прежнему