diff --git a/CLAUDE.md b/CLAUDE.md index 4d74e72..4bfc0e6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -48,7 +48,7 @@ ## Первая подсистема Исполнитель команд и цикл агента (спека `agent/spec.md`, `commands/spec.md`) — реализованы целиком: все типы команд, журнал, восстановление после перезапуска, CLI `run|check|selftest|prune`, самообновление через `run.py`. -Последний change — `../jira-hub/openspec/changes/archive/2026-10-15-issue-card-modal/` (агент v10: комментарии в состоянии; решения агента — `openspec/changes/archive/2026-10-15-agent-v10-comments/design.md`), план — `docs/superpowers/plans/2026-10-15-agent-v10-comments.md`. +Последний change — `../jira-hub/openspec/changes/bug-and-attachments/` (агент v11: тип «Ошибка», `ensure_attachment`, устойчивое дочитывание комментариев; решения агента — `openspec/changes/agent-v11-bug-attachments/design.md`), план — `docs/superpowers/plans/2026-10-15-bug-and-attachments-agent.md`. План и спека живут в `jira-hub`: `openspec/changes/bridge-core/` и `docs/superpowers/plans/2026-10-11-bridge-core-hub.md`; план агента лежит здесь: `docs/superpowers/plans/2026-10-11-bridge-core-agent.md`. ## Выкатка новой версии diff --git a/README.md b/README.md index e6f4965..b2c6808 100644 --- a/README.md +++ b/README.md @@ -190,11 +190,14 @@ python3 agent.py prune `finished`, ждёт не меньше 30 с и повторяет команду через проверку («проверь, потом сделай») до 3 раз. Счётчики суммируются. После исчерпания — `failed` «результат неизвестен (обрыв связи с Jira), повтор безопасен». При остановке по сигналу команду подхватит восстановление. -- **Версия.** Константа `AGENT_VERSION` в `agent.py` (сейчас 9) отправляется в каждом `claim`, в каждом отчёте +- **Версия.** Константа `AGENT_VERSION` в `agent.py` (сейчас 11) отправляется в каждом `claim`, в каждом отчёте (`agent_version`) и в `/agent/state`. Версия 4 — сравнение текстов без различия `\r\n`/`\n`, полная пауза при времени журнала в будущем, `claim` без оценки из журнала, `prune` под блокировкой. Версия 5 — самообновление через `run.py`. Версия 9 — код 73 при занятом журнале, `OSError` при подготовке — временный сбой, проверка формата публикации, источник ошибки хаба в `refresh` (номера 6–8 заняты живой проверкой самообновления). + Версия 10 — комментарии задач в состоянии `refresh`. Версия 11 — тип задачи из `issue_types` с ошибкой конфигурации + (тип `bug` — «Ошибка»), команда `ensure_attachment`, устойчивое дочитывание комментариев (M-1, M-2 из #30). + Агент v10 на `ensure_attachment` отвечает `failed` «неизвестный тип команды»; хаб после выкатки ставит `min_agent_version = 11`. Если хаб назначил `min_agent_version` выше, он пишет в `agent_status.warning`: «обновите агента»; команды при этом выдаются по-прежнему. diff --git a/openspec/changes/agent-v11-bug-attachments/.openspec.yaml b/openspec/changes/agent-v11-bug-attachments/.openspec.yaml new file mode 100644 index 0000000..0cb4765 --- /dev/null +++ b/openspec/changes/agent-v11-bug-attachments/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-16 diff --git a/openspec/changes/agent-v11-bug-attachments/design.md b/openspec/changes/agent-v11-bug-attachments/design.md new file mode 100644 index 0000000..3790a7b --- /dev/null +++ b/openspec/changes/agent-v11-bug-attachments/design.md @@ -0,0 +1,29 @@ +# agent-v11-bug-attachments — решения агента + +Спека и дизайн изменения — в `jira-hub/openspec/changes/bug-and-attachments/` (issue aep/jira-hub#43, aep/jira-agent#31). +Агентская часть — раздел «Агент v11» дизайна хаба и требования `agent` «Тип задачи и вложения» (ADDED) и +«Чтение состояния» (MODIFIED: M-1, M-2 из aep/jira-agent#30). + +Решения владельца 15.10.2026 (brainstorming jira-hub#43): файл приходит только из MCP; обязательные поля «Ошибки» — как у +«Задачи»; файл хранится в хабе, агент забирает его отдельным запросом `GET /agent/files/{sha256}` (base64 в JSON). + +## Decisions made without discussion + +Решения 1–8 записаны в `jira-hub/openspec/changes/bug-and-attachments/design.md` +(раздел «Decisions made without discussion»); здесь — только решения агентской части. + +| Маркер | Дата | Решение | Обоснование | Цена ошибки | +|---|---|---|---|---| +| R-20 | 2026-10-15 | Любой 404 от `GET /agent/files/{sha}` — `failed` «файл удалён с хаба, загрузите заново», без разбора кода `no_file` в теле | Спека hub-api: у маршрута 404 бывает только `no_file`; агент v11 публикуется после выкатки хаба с маршрутом (design «Выкатка», шаги 1 и 3) | Если хаб без маршрута (откат хаба) — вводящий в заблуждение текст «загрузите заново» вместо «нет маршрута»; повторная загрузка не поможет, причина видна по логу хаба | +| R-21 | 2026-10-15 | Таймаут `GET /agent/files` — обычный `timeout` агента (60 с), не поднимается до 120 с, как у загрузки в Jira | design Risks: круг папки для файла 10 МБ (≈13,4 МБ JSON) — секунды, «таймаут вызова агента 60 с с запасом»; таймаут — `ConnectionError` → повтор как обрыв связи с хабом | На медленной папке большой файл не успеет за 60 с: 4 попытки и `failed` «результат неизвестен»; лечится `timeout` в `agent.json` | +| R-22 | 2026-10-15 | Ответ хаба на файл с кодом 2xx, но не JSON — временный сбой («ответ на файл не JSON») без текста `HttpError` | Текст `Http` содержит начало тела, то есть base64 содержимого, а спека запрещает содержимое в журнале и логе; обрезанный ответ — сбой пути, как повреждение | Диагностика беднее: начала тела в ошибке нет, причину искать по логу хаба/реле | +| R-23 | 2026-10-15 | В `filename="…"` кавычка и переводы строк кодируются как у браузеров (`%22`, `%0D`, `%0A`); при поиске уже приложенного вложения сравниваются обе формы имени — исходная и «браузерная» | Хаб запрещает в имени только `/` и `\`; сырая кавычка ломает заголовок части; если Jira сохранит имя в закодированной форме, без второй формы каждый повтор команды создавал бы дубль | Вложение с такими символами в Jira может называться `…%22…`; другое вложение, чьё имя совпало с «браузерной» формой, будет принято за уже приложенное | +| R-24 | 2026-10-15 | Ответ Jira на загрузку не список или без `id` у первого элемента — `failed` «Jira не вернула id вложения в ответе на загрузку», без повтора | Спека: результат — `attachment_id` из ответа; повтор команды человеком найдёт вложение по имени и размеру и вернёт `done` с id | Если Jira на самом деле не сохранила вложение, `failed` без повтора требует ручного повтора команды | +| R-25 | 2026-10-15 | Загрузка вложения идёт через тот же повтор 429/503 по `Retry-After`, что и остальные вызовы Jira (`JiraClient._retry`) | 429/503 — запрос не обработан, повтор безопасен; спека «Ограничение темпа и 429» не делает исключений для записи | 503 от прокси после фактической записи даст второе вложение; вероятность низкая, лишнее удаляется руками | +| R-26 | 2026-10-15 | Тип задачи проверяется до поиска дубля в `ensure_issue`: без настройки типа — ни одного вызова Jira, даже если задача с той же темой уже есть | Спека: «`failed` без вызова Jira»; настройку могли убрать между постановкой и выдачей (design решение 6) | Повтор `ensure_issue` по уже созданной задаче после удаления настройки типа даёт `failed` вместо `done` с найденным ключом | +| R-27 | 2026-10-16 | Трейлер коммитов исполнения — `Co-Authored-By: Claude Sonnet 5.5 `, а не `Opus 5.5` из текста плана | Указание окружения об атрибуции приоритетнее плана; план исполняет Sonnet | Неверное имя модели в трейлере 4–5 коммитов; правится при rebase | + +## Наблюдения вне ревью + +- O-1: README к началу исполнения утверждал «сейчас 9», хотя `AGENT_VERSION` был 10 (v10 не обновила пункт); исправлено вместе с обновлением до 11. +- O-2: полный набор занимает ≈2 минуты (230 → 262 теста), дольше таймаута foreground-команды; прогоны `task-done` запускались в фоне. diff --git a/src/agent.py b/src/agent.py index 5d1d3e8..78240fa 100644 --- a/src/agent.py +++ b/src/agent.py @@ -33,7 +33,7 @@ import uuid from datetime import datetime, timezone from typing import Any, Callable, Dict, List, Optional -AGENT_VERSION = 10 +AGENT_VERSION = 11 EXIT_LOCKED = 73 # «журнал занят другим процессом»: для run.py это не сбой версии (design 8) EXIT_UPDATE = 75 # «обновление готово»: run.py подменит agent.py на agent.py.new HERE = os.path.dirname(os.path.abspath(__file__)) diff --git a/tests/test_folder.py b/tests/test_folder.py index 9b2e6b9..75b8221 100644 --- a/tests/test_folder.py +++ b/tests/test_folder.py @@ -182,8 +182,8 @@ class FolderIntegrationTest(unittest.TestCase): def cfg(self): return jira_cfg(self.fake, hub_url="folder:" + self.root, hub_token=self.hub.token) - def test_version_is_9(self): - self.assertEqual(agent.AGENT_VERSION, 10) + def test_version(self): + self.assertEqual(agent.AGENT_VERSION, 11) def test_runner_through_folder(self): self.fake.add("AURORA-1", duedate="2026-10-20")