@spec-box/sdd 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +52 -3
  2. package/assets/hosts/claude/agent.md +1 -1
  3. package/assets/hosts/claude/project.md +3 -0
  4. package/assets/hosts/codex/AGENTS.md +7 -0
  5. package/assets/hosts/codex/agent.md +10 -0
  6. package/assets/hosts/codex/headless-result.md +2 -0
  7. package/assets/roles/challenger.md +9 -2
  8. package/assets/roles/planner.md +4 -2
  9. package/assets/roles/reviewer.md +2 -0
  10. package/assets/roles/tester.md +2 -0
  11. package/assets/schema/sbox-answer.schema.json +2 -2
  12. package/assets/skills/sbox-run.md +34 -14
  13. package/dist/adapters/host/claude/index.js +13 -19
  14. package/dist/adapters/host/claude/index.js.map +1 -1
  15. package/dist/adapters/host/codex/index.js +61 -0
  16. package/dist/adapters/host/codex/index.js.map +1 -0
  17. package/dist/adapters/host/index.js +9 -2
  18. package/dist/adapters/host/index.js.map +1 -1
  19. package/dist/adapters/runner/codex.js +31 -7
  20. package/dist/adapters/runner/codex.js.map +1 -1
  21. package/dist/cli/commands/host.js +3 -4
  22. package/dist/cli/commands/host.js.map +1 -1
  23. package/dist/cli/commands/models.js +25 -0
  24. package/dist/cli/commands/models.js.map +1 -0
  25. package/dist/cli/commands/protocol.js +4 -2
  26. package/dist/cli/commands/protocol.js.map +1 -1
  27. package/dist/cli/main.js +2 -0
  28. package/dist/cli/main.js.map +1 -1
  29. package/dist/core/change.js +5 -3
  30. package/dist/core/change.js.map +1 -1
  31. package/dist/core/config.js +8 -6
  32. package/dist/core/config.js.map +1 -1
  33. package/dist/core/model-policy.js +54 -0
  34. package/dist/core/model-policy.js.map +1 -0
  35. package/dist/core/packet.js +7 -3
  36. package/dist/core/packet.js.map +1 -1
  37. package/dist/core/phases.js +14 -3
  38. package/dist/core/phases.js.map +1 -1
  39. package/dist/core/report.js +23 -5
  40. package/dist/core/report.js.map +1 -1
  41. package/dist/core/result.js +2 -3
  42. package/dist/core/result.js.map +1 -1
  43. package/dist/core/run.js +11 -8
  44. package/dist/core/run.js.map +1 -1
  45. package/dist/core/runner.js +0 -23
  46. package/dist/core/runner.js.map +1 -1
  47. package/docs/design.md +38 -13
  48. package/package.json +1 -1
package/README.md CHANGED
@@ -28,7 +28,7 @@ sbox change new add-search --title "Поиск по каталогу" --request
28
28
  sbox next --change add-search # пакет для первой роли (researcher)
29
29
  ```
30
30
 
31
- Дальше цикл ведёт скилл `/sbox-run` в Claude Code: он вызывает `sbox next`, запускает субагента нужной роли, сдаёт его ответ через `sbox report` и останавливается на гейтах. Гейты решает человек: `sbox approve <gate>` или `sbox reject <gate> --comment "..."`.
31
+ Дальше цикл ведёт скилл `/sbox-run` в Claude Code или `$sbox-run` в Codex: он вызывает `sbox next`, запускает субагента нужной роли, сдаёт его ответ через `sbox report` и останавливается на гейтах. Гейты решает человек: `sbox approve <gate>` или `sbox reject <gate> --comment "..."`.
32
32
 
33
33
  ## Что где лежит
34
34
 
@@ -73,7 +73,45 @@ sbox change rate <id> --score 4 # оценка результат
73
73
  sbox prompt show project-docs # промпт для заполнения .sbox/project/*.md под адаптер проекта
74
74
  ```
75
75
 
76
- Модели и усилие по ролям задаются в `runner.models` и `runner.efforts` (по умолчанию opus только для planner и challenger, effort medium); после правки выполните `sbox host install --target claude`. Среды: `claude` через Claude Agent SDK (нужен `ANTHROPIC_API_KEY` для CI; локально годится вход Claude Code), `codex` через `codex exec` (в конфиге `runner.codex.executable`, например бинарник из ChatGPT.app). Репозиторий: `repo.adapter: github` с токеном в `GITHUB_TOKEN`; `local` только коммитит.
76
+ Модель и effort задаются по профилям отдельно в `runner.claude.profiles` и `runner.codex.profiles`; после правки выполните `sbox host install --target claude`. Среды: `claude` через Claude Agent SDK (нужен `ANTHROPIC_API_KEY` для CI; локально годится вход Claude Code), `codex` через `codex exec` (в конфиге `runner.codex.executable`, например бинарник из ChatGPT.app). Репозиторий: `repo.adapter: github` с токеном в `GITHUB_TOKEN`; `local` только коммитит.
77
+
78
+ ## Профили моделей
79
+
80
+ Одна политика выбора для headless и материалов Claude Code: роль и оценка сложности определяют профиль `simple`, `medium` или `complex`, затем выбранный раннер подставляет пару `model` + `effort`.
81
+
82
+ ```yaml
83
+ runner:
84
+ default: codex
85
+ roleProfiles: # необязательная фиксация профиля роли
86
+ planner: complex
87
+ challenger: complex
88
+ reviewer: complex
89
+ claude:
90
+ profiles:
91
+ simple: { model: claude-sonnet-5, effort: low }
92
+ medium: { model: claude-sonnet-5, effort: medium }
93
+ complex: { model: claude-opus-5, effort: high }
94
+ codex:
95
+ profiles:
96
+ simple: { model: gpt-5.6-terra, effort: low }
97
+ medium: { model: gpt-5.6-terra, effort: medium }
98
+ complex: { model: gpt-5.6-sol, effort: high }
99
+ ```
100
+
101
+ Пример показывает встроенные значения. Профили можно задавать частично: остальные поля получают дефолты. Доступность конкретной модели и поддержка effort зависят от установленного раннера и учётной записи.
102
+
103
+ По умолчанию planner/challenger/reviewer используют complex, distiller — simple, остальные — medium. Оценка планировщика `complexity.implementation` переключает tester/implementer: простая → simple, обычная → medium, высокая → complex. Оценка complexity.review сохраняется для аудита, но не ослабляет независимое ревью: reviewer остаётся complex. При возвратах оценка может повышаться. Явный `roleProfiles.<роль>` фиксирует профиль и имеет приоритет над автоматическим выбором. Размер small/normal/large определяет артефакты и не меняет профиль сам по себе.
104
+
105
+ ```bash
106
+ sbox models --json # обе среды, профили по ролям
107
+ sbox models --change add-search --json # с учётом сложности изменения
108
+ sbox next --change add-search --runner claude --brief --json
109
+ sbox host install --target claude # обновить определения агентов
110
+ ```
111
+
112
+ `next` возвращает `execution` с раннером, профилем, моделью, effort и именем агента выбранного хоста. `/sbox-run` использует это имя; устанавливаются варианты `sbox-<роль>-simple|medium|complex` и базовый `sbox-<роль>`. При изменении профиля/модели/effort предыдущая сессия не продолжается. Для Codex генерируются `.codex/agents/*.toml`, устанавливается общий скилл в `.agents/skills/sbox-run` и добавляется управляемый раздел `AGENTS.md`. Пользовательский `.codex/config.toml` не меняется. После установки откройте новую сессию Codex в доверенном проекте. Если клиент не поддерживает выбор пользовательских агентов, скилл использует `sbox run --runner codex --max-runs 1` с теми же настройками.
113
+
114
+ **Миграция:** общие `runner.models`, `runner.efforts`, `runner.defaultEffort` удалены и вызывают понятную ошибку конфигурации. Перенесите настройки в профили нужного раннера, при необходимости задайте roleProfiles, удалите старые поля и переустановите материалы хоста. Автоматический перенос не выполняется: старая таблица не указывает, какому раннеру принадлежит модель.
77
115
 
78
116
  ## Браузер для проверки интерфейса
79
117
 
@@ -139,6 +177,8 @@ sbox-wiki validate --json
139
177
 
140
178
  Валидация проверяет типы метаданных, уникальность id, локальные ссылки и якоря заголовков; ссылки из блоков кода не учитываются. Внешние URL не проверяются, ссылки за пределы wiki запрещены; HTML-ссылки и пользовательские HTML-якоря не поддерживаются. Код выхода 1 означает ошибку; отсутствие summary/read_when — предупреждение. Скилл `sbox-wiki` устанавливается для Claude и Codex через `sbox host install`; `sbox doctor` использует ту же проверку wiki.
141
179
 
180
+ Аудит плана обязателен: `plan → [гейт plan] → challenge → cover`. Challenger проверяет артефакты против запроса и кода, ищет пропущенные сценарии и возвращает блокирующие замечания планировщику. После исправления повторяются гейт и аудит; отчёты сохраняются в `evidence/challenge-N.md`. Человеческие гейты зависят от автономности, аудит — нет. Изменения, уже прошедшие планирование до обновления, продолжаются с текущей фазы; при возврате в plan проходят новый аудит.
181
+
142
182
  ## Состояние
143
183
 
144
184
  Готово (этапы 1 и 2): конфиг и раскладка `.sbox/`, жизненный цикл изменения с гейтами, возвратами и бюджетом (`parked`), ревизия и lock `change.yaml`, пакеты и receipt запусков, приём отчётов с проверками (артефакты, дельты, задачи, защищённые тесты, дрейф change-set, disposition и `delivery_narrative` ревьюера), запечатывание change-set после реализации, адаптер spec-box, категории документации и структурный `doctor`, материалы для Claude Code, адаптеры сред Claude и Codex с надзором и одним транспортным повтором, `run`/`stop`/`watch`, адаптер GitHub с идемпотентной доставкой и каналом гейтов через комментарии, отчёты тестов jest/vitest/playwright и покрытие, шаблоны CI и Dockerfile.
@@ -149,4 +189,13 @@ sbox-wiki validate --json
149
189
 
150
190
  Готово также: папка `runs/` не переносится в архив (ответы ролей и квитанции остаются в истории ветки, `delivery_narrative` и запуски в `change.yaml`; `archive.runs: true` сохраняет её), `sbox-browser` для проверки интерфейса (сессии с Chrome через `puppeteer-core`, необязательная установка браузера, вход человеком с постоянным профилем, снимок дерева доступности для агентов, перенос состояния входа), проверка браузера в `sbox doctor`.
151
191
 
152
- Не готово: дискавери и правила (этап 3), Arcadia и Codex-материалы для хоста (этап 4), межрепозиторный протокол (этап 5), роутер и дистилляция wiki (этап 6), продолжение сессий Codex, семантический `doctor --deep`.
192
+ Не готово: дискавери и правила (этап 3), Arcadia (этап 4), межрепозиторный протокол (этап 5), роутер и дистилляция wiki (этап 6), семантический `doctor --deep`.
193
+
194
+ Для Codex в существующем проекте:
195
+
196
+ ```bash
197
+ sbox host install --target codex
198
+ sbox models --runner codex
199
+ ```
200
+
201
+ В новой сессии вызовите `$sbox-run`. Headless-раннер сохраняет ID сессии и продолжает её при возврате к той же роли с теми же настройками; старые клиенты без нужных возможностей resume запускают новую сессию. При первоначальной настройке доступен `sbox init --host codex`. Агент возвращает полный отчёт оркестратору; тот сохраняет его и передаёт CLI. Headless-вариант без интерактивного клиента: `sbox run --runner codex --change <id>`.
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: sbox-{{role}}
2
+ name: {{name}}
3
3
  description: {{description}}
4
4
  tools: {{tools}}
5
5
  model: {{model}}
@@ -0,0 +1,3 @@
1
+ ## Изменения через @spec-box/sdd
2
+ Продуктовые изменения ведутся инструментом @spec-box/sdd: `sbox change new <id> --title "..." --request "..."`, затем скилл `/sbox-run`.
3
+ Не редактируй артефакты в .sbox/changes вручную и не меняй истину спецификаций напрямую: только дельты через роли.
@@ -0,0 +1,7 @@
1
+ ## Изменения через @spec-box/sdd
2
+
3
+ Когда пользователь просит выполнить изменение через sbox, используй `$sbox-run` из `.agents/skills/sbox-run/SKILL.md`. Создай изменение через `sbox change new`, если оно ещё не создано. CLI управляет фазами, гейтами, профилями моделей и возвратами.
4
+
5
+ Оркестратор делегирует роли последовательно агентам из `execution.agent` ответа `sbox next --runner codex --brief --json`. Субагенты выполняют только свой пакет, не запускают sbox-run и других агентов. При недоступности пользовательских агентов используй предусмотренный скиллом headless-режим Codex.
6
+
7
+ Скиллы `sbox-contract`, `sbox-wiki`, `sbox-browser`, `sbox-approve` лежат в `.agents/skills`. Истина спецификаций меняется через доставку SDD; роли работают с дельтами. Гейты решает человек. Установка материалов не разрешает доставку и публикацию.
@@ -0,0 +1,10 @@
1
+ Ты выполняешь роль {{role}} инструмента @spec-box/sdd. Во входном сообщении путь к JSON-пакету (`packet`). Прочитай его целиком и выполняй задачу только в пределах ownership. Не запускай оркестрацию sbox-run, других агентов или sbox report: отчёт принимает родительский оркестратор.
2
+
3
+ Прочитай применимые скиллы роли по путям:
4
+ {{skills}}
5
+
6
+ {{body}}
7
+
8
+ ## Передача результата в Codex
9
+
10
+ Полный ответ в Markdown с завершающим блоком `# sbox-result` верни в последнем сообщении. Запись resultFile выполняет родительский оркестратор; сам этот файл не записывай. Это относится и к ролям только для чтения: не повышай разрешения ради сохранения отчёта. Меняй лишь разрешённые артефакты роли. Если проверка недоступна в текущей песочнице, явно отрази ограничение в отчёте.
@@ -0,0 +1,2 @@
1
+ Последнее сообщение верни строго как JSON-объект по схеме вывода: { "markdown": <полный ответ в Markdown>, "result": <содержимое блока sbox-result как объект> }.
2
+ Файл resultFile записывает раннер. Не записывай его самостоятельно и не запрашивай дополнительные права ради отчёта. Соблюдай ownership роли для остальных файлов.
@@ -12,8 +12,16 @@ description: "Аудитор плана @spec-box/sdd. Вызывается то
12
12
 
13
13
  ## Этапы
14
14
 
15
+ Фаза `challenge` выполняется после гейта plan и до тестов при любом размере изменения и уровне автономности. Учитывай ответы человека из feedback. Повторный аудит проверяет весь исправленный план, а не только закрытие прежних замечаний.
16
+
17
+ Сопоставь запрос и текущее поведение кода с дельтами, дизайном и задачами. Ищи сценарии, пропущенные одновременно во всех артефактах. Для каждого существенного риска укажи нарушаемый инвариант, конкретный сценарий отказа, подтверждение из файлов и способ проверки исправления. Не создавай замечания ради количества; проектные критерии бери из wiki и правил проекта.
18
+
19
+ Отдельно оцени неопределённость и последствия ошибки. Малое число строк не делает изменение безопасным. Если риск недооценён, верни повышенную complexity реализации и ревью с обоснованием в отчёте.
20
+
15
21
  Проверь: блокеры (шаг, который не может выполниться как написано); отсутствующие решения; необоснованные допущения (процитируй); расползание объёма; пробелы приёмки (поведение без шага или проверки); пробелы валидации; нарушения правил проекта; более простое решение с теми же критериями (или «нет»).
16
22
 
23
+ Блокирующая находка требует доработки: верни «заблокировано» с категорией «артефакт». CLI вернёт планировщику и повторит аудит после исправления. Даже при ошибочном статусе «готово» blocking findings не позволят пройти дальше. Полный ответ сохраняет CLI в evidence; сам меняй только resultFile.
24
+
17
25
  ## Содержимое resultFile
18
26
 
19
27
  ```markdown
@@ -32,6 +40,5 @@ description: "Аудитор плана @spec-box/sdd. Вызывается то
32
40
  # sbox-result
33
41
  status: готово | заблокировано
34
42
  blocker: { category: артефакт | нет, artifact: design, message: "" }
35
- findings:
36
- - { level: blocking, text: "" }
43
+ findings: [] # обязательно; замечания: { level: blocking | non-blocking, file: "путь", text: "сценарий, доказательство, проверка" }
37
44
  ```
@@ -27,7 +27,9 @@ description: "Планировщик @spec-box/sdd: proposal, дельты сп
27
27
  2. Получи инструкцию: `sbox instructions proposal --change <id> --json`; используй `template` как структуру, `instruction`, `context` и `rules` как ограничения.
28
28
  3. Запиши `proposal.md` по `resolvedOutputPath`.
29
29
  4. Сверь proposal с запросом и с «Разбором запроса» из evidence. Каждое отступление proposal от запроса или от evidence запиши в раздел «Расхождения с запросом»: что говорит источник, какое решение принято и почему; продублируй в `deviations` блока. Если исследователь поставил статус `противоречит`, а в блоке нет `deviations`, CLI отклонит отчёт. Расхождения человек увидит на гейте proposal.
30
- 5. Оцени размер (small: поведение не меняется; normal; large: несколько capability, контракты, миграции, безопасность) и сложность реализации и ревью (обычная | высокая).
30
+ 5. Оцени размер (small: поведение не меняется; normal; large: несколько capability, контракты, миграции, безопасность) и сложность реализации и ревью (простая | обычная | высокая).
31
+ Для complexity оцени реализацию и ревью отдельно: «простая» — локальная правка по ясному образцу без новых контрактов; «обычная» — несколько связанных изменений с понятным решением; «высокая» — новые контракты, существенная неоднозначность, миграция или сложные инварианты. Размер изменения и сложность — разные характеристики. Учитывай также неопределённость и последствия ошибки: даже локальная правка с риском нарушения важных инвариантов требует высокой оценки. Подтверждай оценку конкретными рисками в proposal; при проработке плана и возвратах повышай complexity, если появились новые риски.
32
+
31
33
  6. Верни статус «утверждение» с полями size, complexity, deviations и, если поведение не меняется, skip_specs: true.
32
34
 
33
35
  ### Фаза plan
@@ -58,7 +60,7 @@ description: "Планировщик @spec-box/sdd: proposal, дельты сп
58
60
  status: готово | утверждение | заблокировано
59
61
  blocker: { category: пользователь | внешний | нет, message: "" }
60
62
  size: small | normal | large # только на фазе propose
61
- complexity: { implementation: обычная | высокая, review: обычная | высокая } # только на фазе propose
63
+ complexity: { implementation: простая | обычная | высокая, review: простая | обычная | высокая } # на propose; на plan при повышении оценки
62
64
  skip_specs: false # только на фазе propose
63
65
  deviations: # только на фазе propose: отступления proposal от запроса или evidence
64
66
  - { subject: запрос | evidence, text: "", decision: "", reason: "" }
@@ -14,6 +14,8 @@ description: "Ревьюер @spec-box/sdd: тесты против специф
14
14
  - Опровергнутый факт из evidence или раннего артефакта записывай в журнал: `sbox log add --change <id> --tag CODE "<факт с путём>"`; evidence не правь. Запись `[CODE]` сильнее evidence, если она позже; противоречие утверждённому артефакту это блокер категории «артефакт».
15
15
  - Отвечай только по шаблону «Содержимое resultFile».
16
16
 
17
+ - Проверь, не пропущен ли важный сценарий одновременно в спецификации и тестах: сопоставь их с исходным запросом и затронутыми инвариантами кода. Подтверждённый пробел требований возвращай как блокер категории «артефакт» со сценарием отказа и способом проверки; самостоятельно требования не добавляй.
18
+
17
19
  ## Вход
18
20
 
19
21
  Пакет от CLI с полем `phase`: `tests_review` (ревью тестов до реализации) или `review` (ревью реализации). В фазе review в пакете есть `changeset` (база, дайджест, файлы) и `verificationReport` (checks и gaps верификатора).
@@ -14,6 +14,8 @@ description: "Тестировщик @spec-box/sdd: тесты по сценар
14
14
  - Опровергнутый факт из evidence или раннего артефакта записывай в журнал: `sbox log add --change <id> --tag CODE "<факт с путём>"`; evidence не правь. Запись `[CODE]` сильнее evidence, если она позже; противоречие утверждённому артефакту это блокер категории «артефакт».
15
15
  - Отвечай только по шаблону «Содержимое resultFile».
16
16
 
17
+ - Проверь, не пропущен ли важный сценарий одновременно в спецификации и тестах: сопоставь их с исходным запросом и затронутыми инвариантами кода. Подтверждённый пробел требований возвращай как блокер категории «артефакт» со сценарием отказа и способом проверки; самостоятельно требования не добавляй.
18
+
17
19
  ## Вход
18
20
 
19
21
  Пакет от CLI: дельты `specs/`, `design.md` (контракты, структура UI, идентификаторы), `testing.md`, `conventions.md`, существующие тесты проекта. При возврате — `feedback` с findings ревьюера или блокером реализатора.
@@ -24,8 +24,8 @@
24
24
  "complexity": {
25
25
  "type": "object",
26
26
  "properties": {
27
- "implementation": { "type": "string", "enum": ["обычная", "высокая"] },
28
- "review": { "type": "string", "enum": ["обычная", "высокая"] }
27
+ "implementation": { "type": "string", "enum": ["простая", "обычная", "высокая"] },
28
+ "review": { "type": "string", "enum": ["простая", "обычная", "высокая"] }
29
29
  }
30
30
  },
31
31
  "questions": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "priority": { "type": "string", "enum": ["P0", "P1", "P2"] }, "text": { "type": "string" } } } },
@@ -1,24 +1,44 @@
1
1
  ---
2
2
  name: sbox-run
3
- description: Выполнить следующий шаг изменения @spec-box/sdd — запросить пакет у CLI, выполнить роль субагентом и сдать отчёт. Используй, когда пользователь просит продвинуть изменение, запустить sbox, выполнить следующий шаг.
3
+ description: Выполнить изменение @spec-box/sdd в Claude Code или Codex — получить следующий шаг CLI, запустить роль и сдать отчёт. Используй при просьбе продвинуть изменение, запустить sbox или выполнить следующий шаг.
4
4
  metadata:
5
- hosts: [claude]
5
+ hosts: [claude, codex]
6
6
  ---
7
7
 
8
8
  # sbox-run
9
9
 
10
- Ты оркестратор: не исследуешь проект и не решаешь задачу сам. Роли выполняют субагенты `sbox-<role>`.
10
+ Ты оркестратор: роли выполняют задачу, CLI управляет состоянием. Делегируй роли последовательно; не исследуй проект и не меняй продуктовый код или артефакты планирования сам. Исключение для Codex — дословное сохранение ответа роли в resultFile.
11
11
 
12
- 1. Определи изменение: из аргумента, из контекста или `sbox change list --json`. Если активных изменений несколько и не ясно, какое брать, спроси пользователя.
13
- 2. Запроси следующий шаг: `sbox next --change <id> --brief --json`. Никогда не запрашивай `next` без `--brief` и не читай packet.json сам: пакет нужен субагенту, а не тебе.
14
- 3. По полю `kind` ответа:
15
- - `role`: если субагент этой роли уже запускался в этой сессии, продолжи его тем же сообщением через SendMessage (он помнит проект и артефакты, повторное чтение не нужно); иначе запусти нового субагента `sbox-<role>`. Сообщение: `packet: <path из поля packetFile>`. Дождись ответа. Затем сдай отчёт: `sbox report --change <id> --role <role> --json`. Покажи пользователю строку `summary` из ответа дословно и ошибки из `diagnostics`; больше ничего не добавляй. Сообщение субагента не пересказывай и не превращай в выводы: его утверждения проверяет CLI, а решение принимает человек на гейте. Если CLI вернул ошибки (например, не создан артефакт или изменены защищённые файлы), передай их тому же субагенту одним сообщением и повтори отчёт; не больше двух повторов. Повторный отчёт для уже принятого ответа безопасен: CLI вернёт `alreadyApplied: true` без нового запуска.
16
- - `gate`: покажи пользователю пути артефактов из ответа и список `conflicts` дословно (расхождения между запросом, evidence и proposal с решениями планировщика). Если в ответе есть `questions`, покажи их и попроси ответить: ответы передаются при утверждении командой из поля `answer` (`--answer Q1=<вариант>`). Затем спроси: «Утвердить <gate>? Ответьте «да» или замечанием». «да» → `sbox approve <gate> --change <id> --by <user>`; замечание → `sbox reject <gate> --change <id> --comment "<текст>"`. Мелкие правки формулировок пользователь может внести в артефакт сам и ответить «да»: отклонение запускает цикл доработки роли. Затем вернись к шагу 2.
17
- - `deliver`: покажи чеклист `sbox deliver --change <id> --check` и предложи доставку `sbox deliver --change <id>` (архив, коммит, push, пул-реквест). Не запускай доставку без просьбы пользователя.
18
- - `wait`: покажи статус и блокер, спроси пользователя, что делать.
19
- - `done`: сообщи об окончании.
20
- 4. Повторяй шаги 2–3, пока не встретишь gate, wait, deliver или done. Не запускай субагентов параллельно.
12
+ ## Подготовка
21
13
 
22
- Полезные команды: `sbox status --change <id>` (состояние, change-set, привязка ревью), `sbox changeset show` (запечатанный диф и дрейф), `sbox coverage --report jest=<path>` (покрытие утверждений тестами), `sbox change resume --returns N` (продолжить после parked).
14
+ Определи текущий хост: Claude Code → `claude`, Codex → `codex`. Не выбирай хост по runner.default: эта настройка относится к headless. Определи изменение из запроса или `sbox change list --json`; уточни только если выбор неоднозначен.
23
15
 
24
- Экономия контекста: не читай артефакты изменения (design.md, спецификации, отчёты) целиком; тебе нужны только ответы CLI. Запрещено: переформулировать задачу пользователя, редактировать артефакты, вызывать роли напрямую в обход пакета, менять код.
16
+ Запроси `sbox next --change <id> --runner <хост> --brief --json`. Не читай packet.json: передай путь роли. Модель и effort берутся из `execution`; после изменения конфига обнови материалы через `sbox host install --target <хост>` и перезапусти сессию, если хост не подхватил их.
17
+
18
+ ## Запуск роли
19
+
20
+ Для `kind: role` используй имя из `execution.agent` и сообщение `packet: <packetFile>`. Каждый запуск должен прочитать свежий пакет. Ключ повторного использования субагента: изменение + runner + agent + model + effort. Не продолжай агента другого изменения или с устаревшими настройками.
21
+
22
+ - **Claude Code:** запусти установленного агента через Agent; повторный запуск — через SendMessage. Агент пишет resultFile и возвращает его путь.
23
+ - **Codex:** запусти пользовательского агента по имени через доступный механизм субагентов (agent_type, если он поддерживается). Не подменяй его встроенным worker с унаследованной моделью. Если доступно продолжение завершённого агента — продолжи его, иначе создай нового с тем же типом. Агент возвращает полный Markdown и блок sbox-result в последнем сообщении. Сохрани ответ дословно в resultFile из brief: используй инструмент записи или безопасную передачу данных, без интерполяции ответа в shell-команду. Роли с read-only песочницей не должны запрашивать запись ради отчёта.
24
+
25
+ Дождись завершения роли. Не создавай отдельные пользовательские задачи вместо субагентов. Промежуточный статус, обрыв или пересказ ответа не являются отчётом.
26
+
27
+ Если текущий клиент Codex не позволяет выбрать установленного пользовательского агента, используй **headless-путь**: `sbox run --runner codex --change <id> --max-runs 1 --json`. Он сам запускает роль с нужными настройками и принимает ответ; повторно report не вызывай. Дождись завершения, покажи итог, снова запроси next. При ошибке запуска, недоступной модели или CLI остановись и покажи диагностику; не заменяй модель молча. Если результат остался в ожидании/ошибке, не запускай бесконечные повторы.
28
+
29
+ ## Приём результата
30
+
31
+ После нативного субагента выполни `sbox report --change <id> --role <role> --runner <хост> --json`. Показанные в execution настройки — запрошенные, не доказательство фактического запуска: не заполняй --model/--session предположениями.
32
+
33
+ Покажи строку summary дословно и диагностики. Если отчёт отклонён, снова запроси next для свежих packetFile/resultFile и передай ошибки роли; максимум две попытки исправления, затем остановись с диагностикой. Не продолжай роль, если CLI уже принял блокер и назначил другой следующий шаг. Повторная сдача принятого ответа безопасна: alreadyApplied не создаёт новый запуск.
34
+
35
+ Повторяй next после принятых отчётов до gate, wait, deliver или done.
36
+
37
+ ## Остальные шаги
38
+
39
+ - **gate:** покажи пути артефактов, conflicts и questions из CLI. Получи решение человека. «Да» → команда approve из ответа; замечания → reject с текстом. Ответы на вопросы передай через --answer. После решения снова next. Не утверждай гейт самостоятельно.
40
+ - **deliver:** покажи `sbox deliver --change <id> --check`. Запускай доставку только по просьбе пользователя; это архивирование, коммит, push и пул-реквест.
41
+ - **wait:** покажи статус и блокер, остановись. Продолжение выполняется через `sbox change resume` после разрешения причины; при parked нужен новый бюджет --returns.
42
+ - **done:** сообщи о завершении.
43
+
44
+ При просьбе остановить активный headless-запуск используй `sbox stop --change <id>`. При нативном запуске останови также субагента средствами хоста; не сдавай его незавершённый ответ. После утраты контекста восстанови состояние через CLI, а не по памяти о последнем сообщении.
@@ -1,13 +1,14 @@
1
+ import { resolveModel, MODEL_PROFILES, claudeAgentName } from '../../../core/model-policy.js';
1
2
  import path from 'node:path';
2
3
  import { loadRoleDescription, loadRoleText } from '../../../core/roles.js';
3
4
  import { ROLES } from '../../../core/phases.js';
4
- import { defaultHostModel, READ_ONLY_ROLES } from '../../../core/runner.js';
5
+ import { READ_ONLY_ROLES } from '../../../core/runner.js';
5
6
  import { loadSkills, skillsForRole } from '../../../core/skills.js';
6
7
  import { assetsDir, readText, writeText } from '../../../core/paths.js';
7
8
  import { configSchema } from '../../../core/config.js';
8
9
  const AGENT_ROLES = ['researcher', 'planner', 'tester', 'implementer', 'reviewer', 'verifier', 'challenger'];
9
10
  /**
10
- * Агенты Claude Code по одному на роль. Текст агента это шаблон assets/hosts/claude/agent.md с данными роли:
11
+ * Агенты Claude Code: базовое имя роли и варианты для каждого профиля. Текст агента это шаблон assets/hosts/claude/agent.md с данными роли:
11
12
  * описание из фронтматтера файла роли, инструменты из runner.claude конфига (как в headless-режиме),
12
13
  * скиллы из metadata.roles полем skills (субагенты не видят скиллы проекта сами).
13
14
  */
@@ -21,31 +22,24 @@ export function installClaudeAgents(root, config, skills = loadSkills(root)) {
21
22
  const file = path.join(root, '.claude', 'agents', `sbox-${role}.md`);
22
23
  writeText(file, agentFile(template, root, role, cfg, skillsForRole(skills, role)));
23
24
  written.push(file);
25
+ for (const profile of MODEL_PROFILES) {
26
+ const variant = path.join(root, '.claude', 'agents', `${claudeAgentName(role, profile)}.md`);
27
+ writeText(variant, agentFile(template, root, role, cfg, skillsForRole(skills, role), profile));
28
+ written.push(variant);
29
+ }
24
30
  }
25
31
  return written;
26
32
  }
27
- /** Имя модели для фронтматтера агента Claude Code: псевдонимы sonnet/opus/haiku или полный идентификатор. */
28
- function agentModel(configured) {
29
- if (!configured)
30
- return null;
31
- const v = configured.toLowerCase();
32
- if (v.includes('opus'))
33
- return 'opus';
34
- if (v.includes('sonnet'))
35
- return 'sonnet';
36
- if (v.includes('haiku'))
37
- return 'haiku';
38
- return configured;
39
- }
40
- function agentFile(template, root, role, config, skills) {
33
+ function agentFile(template, root, role, config, skills, profile) {
34
+ const execution = resolveModel(config, 'claude', role, undefined, profile);
41
35
  const tools = (READ_ONLY_ROLES.has(role) ? config.runner.claude.readOnlyTools : config.runner.claude.allowedTools).join(', ');
42
36
  const values = {
43
37
  role,
38
+ name: profile ? claudeAgentName(role, profile) : `sbox-${role}`,
44
39
  description: JSON.stringify(loadRoleDescription(root, role)),
45
40
  tools,
46
- model: agentModel(config.runner.models[role]) ?? defaultHostModel(role),
47
- // Без явного effort субагент наследует усилие сессии (в пилоте это был xhigh на каждом ходе).
48
- effort: config.runner.efforts[role] ?? config.runner.defaultEffort,
41
+ model: JSON.stringify(execution.model),
42
+ effort: execution.effort,
49
43
  skills: skills.length ? `skills:\n${skills.map((s) => ` - ${s}`).join('\n')}\n` : '',
50
44
  body: loadRoleText(root, role).trim(),
51
45
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/adapters/host/claude/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/adapters/host/claude/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { configSchema } from '../../../core/config.js';
4
+ import { SboxError } from '../../../core/errors.js';
5
+ import { MODEL_PROFILES, resolveModel, hostAgentName } from '../../../core/model-policy.js';
6
+ import { assetsDir, exists, readText } from '../../../core/paths.js';
7
+ import { ROLES } from '../../../core/phases.js';
8
+ import { loadRoleDescription, loadRoleText } from '../../../core/roles.js';
9
+ import { READ_ONLY_ROLES } from '../../../core/runner.js';
10
+ import { skillsForHost, skillsForRole } from '../../../core/skills.js';
11
+ const START = '<!-- sbox:begin -->';
12
+ const END = '<!-- sbox:end -->';
13
+ const GENERATED = '# Generated by @spec-box/sdd; run sbox host install --target codex';
14
+ // JSON basic strings are TOML-compatible except form feed and literal DEL.
15
+ export const tomlString = (s) => JSON.stringify(s).replace(/\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4})/g, escape => escape === '\\f' ? '\\u000c' : escape).replace(/\x7f/g, '\\u007f');
16
+ export function updateAgentsSection(text, body) {
17
+ const starts = text.split(START).length - 1;
18
+ const ends = text.split(END).length - 1;
19
+ const section = `${START}\n${body.trim()}\n${END}`;
20
+ if (!starts && !ends)
21
+ return `${text}${text && !text.endsWith('\n') ? '\n' : ''}${text ? '\n' : ''}${section}\n`;
22
+ if (starts !== 1 || ends !== 1 || text.indexOf(END) < text.indexOf(START)) {
23
+ throw new SboxError('HOST_SECTION_INVALID', 'Повреждён раздел sbox в AGENTS.md: нужны ровно одна пара sbox:begin / sbox:end.');
24
+ }
25
+ return text.slice(0, text.indexOf(START)) + section + text.slice(text.indexOf(END) + END.length);
26
+ }
27
+ /** Build all output before writing, preserving user config and unrelated AGENTS.md content. */
28
+ export function codexMaterials(root, config, skills) {
29
+ const cfg = config ?? configSchema.parse({ version: 1, spec: { adapter: 'spec-box' } });
30
+ const template = readText(path.join(assetsDir(), 'hosts', 'codex', 'agent.md'));
31
+ const files = new Map();
32
+ for (const role of ROLES.filter(r => r !== 'distiller')) {
33
+ const roleSkills = skillsForRole(skillsForHost(skills, 'codex'), role);
34
+ const values = {
35
+ role, body: loadRoleText(root, role).trim(),
36
+ skills: roleSkills.map(s => `- .agents/skills/${s}/SKILL.md`).join('\n'),
37
+ };
38
+ const instructions = template.replace(/\{\{(\w+)\}\}/g, (_, key) => values[key] ?? '');
39
+ for (const profile of [undefined, ...MODEL_PROFILES]) {
40
+ const execution = resolveModel(cfg, 'codex', role, undefined, profile);
41
+ const name = profile ? hostAgentName(role, profile) : `sbox-${role}`;
42
+ const file = path.join(root, '.codex', 'agents', `${name}.toml`);
43
+ if (exists(file) && !readText(file).startsWith(GENERATED)) {
44
+ throw new SboxError('HOST_AGENT_CONFLICT', `Файл ${file} не создан sbox; переименуйте его перед установкой.`);
45
+ }
46
+ files.set(file, `${GENERATED}\n` + Object.entries({
47
+ name, description: loadRoleDescription(root, role), model: execution.model,
48
+ model_reasoning_effort: execution.effort,
49
+ sandbox_mode: READ_ONLY_ROLES.has(role) ? cfg.runner.codex.sandboxRead : cfg.runner.codex.sandboxWrite,
50
+ developer_instructions: instructions,
51
+ }).map(([key, value]) => `${key} = ${tomlString(value)}`).join('\n') + '\n');
52
+ }
53
+ }
54
+ const agentsFile = path.join(root, 'AGENTS.md');
55
+ if (fs.existsSync(agentsFile) && fs.lstatSync(agentsFile).isSymbolicLink()) {
56
+ throw new SboxError('HOST_SECTION_SYMLINK', 'AGENTS.md является ссылкой; sbox не изменяет её целевой файл.');
57
+ }
58
+ files.set(agentsFile, updateAgentsSection(exists(agentsFile) ? readText(agentsFile) : '', readText(path.join(assetsDir(), 'hosts', 'codex', 'AGENTS.md'))));
59
+ return files;
60
+ }
61
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/adapters/host/codex/index.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,5 @@
1
+ import { codexMaterials } from './codex/index.js';
2
+ import { writeText } from '../../core/paths.js';
1
3
  import { SboxError } from '../../core/errors.js';
2
4
  import { loadSkills } from '../../core/skills.js';
3
5
  import { installClaudeAgents } from './claude/index.js';
@@ -7,6 +9,7 @@ export function installHostMaterials(root, target, config) {
7
9
  if (!isHostTarget(target))
8
10
  throw new SboxError('HOST_UNKNOWN', `Неизвестный хост ${target}; доступны ${HOST_TARGETS.join(' и ')}.`);
9
11
  const skills = loadSkills(root);
12
+ const codex = target === 'codex' ? codexMaterials(root, config, skills) : new Map();
10
13
  const files = installSkills(root, target, skills);
11
14
  const notes = [];
12
15
  let next;
@@ -16,8 +19,12 @@ export function installHostMaterials(root, target, config) {
16
19
  next = 'В Claude Code: `/sbox-run` после `sbox change new <id> --title "..." --request "..."`.';
17
20
  break;
18
21
  case 'codex':
19
- notes.push('Для Codex установлены скиллы sbox-approve, sbox-browser, sbox-contract и sbox-wiki в .agents/skills; оркестратор sbox-run, агенты ролей и раздел AGENTS.md для Codex появятся на этапе 4 плана.');
20
- next = 'В Codex: скиллы из .agents/skills доступны сразу; изменения пока ведутся из Claude Code или командами `sbox next` и `sbox report` вручную.';
22
+ for (const [file, text] of codex) {
23
+ writeText(file, text);
24
+ files.push(file);
25
+ }
26
+ notes.push('Перезапустите сессию Codex, чтобы загрузить новые агенты. Проектные материалы должны быть доверенными в Codex.');
27
+ next = 'В Codex: `$sbox-run` после `sbox change new <id> --title "..." --request "..."`. Headless: `sbox run --runner codex --change <id>`.';
21
28
  break;
22
29
  }
23
30
  return { target, files, notes, next };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/host/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEjD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAmB,MAAM,aAAa,CAAC;AAUzF,8GAA8G;AAC9G,MAAM,UAAU,oBAAoB,CAAC,IAAY,EAAE,MAAc,EAAE,MAAe;IAChF,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,cAAc,EAAE,oBAAoB,MAAM,cAAc,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpI,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAClD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,IAAY,CAAC;IACjB,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,QAAQ;YACX,KAAK,CAAC,IAAI,CAAC,GAAG,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;YACzD,IAAI,GAAG,wFAAwF,CAAC;YAChG,MAAM;QACR,KAAK,OAAO;YACV,KAAK,CAAC,IAAI,CAAC,iMAAiM,CAAC,CAAC;YAC9M,IAAI,GAAG,4IAA4I,CAAC;YACpJ,MAAM;IACV,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACxC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/host/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEjD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAmB,MAAM,aAAa,CAAC;AAUzF,8GAA8G;AAC9G,MAAM,UAAU,oBAAoB,CAAC,IAAY,EAAE,MAAc,EAAE,MAAe;IAChF,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,cAAc,EAAE,oBAAoB,MAAM,cAAc,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpI,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,KAAK,GAAG,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAkB,CAAC;IACpG,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAClD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,IAAY,CAAC;IACjB,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,QAAQ;YACX,KAAK,CAAC,IAAI,CAAC,GAAG,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;YACzD,IAAI,GAAG,wFAAwF,CAAC;YAChG,MAAM;QACR,KAAK,OAAO;YACV,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC;gBAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;gBAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAAC,CAAC;YAC9E,KAAK,CAAC,IAAI,CAAC,gHAAgH,CAAC,CAAC;YAC7H,IAAI,GAAG,qIAAqI,CAAC;YAC7I,MAAM;IACV,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACxC,CAAC"}
@@ -2,7 +2,7 @@ import { execFileSync, spawn } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { SboxError } from '../../core/errors.js';
5
- import { assetsDir } from '../../core/paths.js';
5
+ import { assetsDir, readText } from '../../core/paths.js';
6
6
  import { jsonAnswerToMarkdown } from '../../core/result.js';
7
7
  import { registerRunner } from '../../core/runner.js';
8
8
  const REQUIRED_FLAGS = ['--output-schema', '--output-last-message', '--json', '--cd', '--model'];
@@ -16,7 +16,7 @@ export class CodexRunner {
16
16
  executable;
17
17
  options;
18
18
  name = 'codex';
19
- supportsResume = false;
19
+ get supportsResume() { return this.probe().resume; }
20
20
  capabilities = null;
21
21
  constructor(executable, options) {
22
22
  this.executable = executable;
@@ -35,7 +35,13 @@ export class CodexRunner {
35
35
  throw new SboxError('CODEX_UNAVAILABLE', `Не удалось запустить ${this.executable}: ${e.message}`, 'Установите Codex CLI или укажите runner.codex.executable.');
36
36
  }
37
37
  const missing = REQUIRED_FLAGS.filter((f) => !help.includes(f));
38
- this.capabilities = { ok: missing.length === 0, missing, version };
38
+ let resume = false;
39
+ try {
40
+ const resumeHelp = execFileSync(this.executable, ['exec', 'resume', '--help'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
41
+ resume = ['--output-schema', '--output-last-message', '--json', '--model', '--config'].every(flag => resumeHelp.includes(flag));
42
+ }
43
+ catch { /* Older clients can still start fresh sessions. */ }
44
+ this.capabilities = { ok: missing.length === 0, missing, version, resume };
39
45
  return this.capabilities;
40
46
  }
41
47
  async run(req) {
@@ -49,7 +55,7 @@ export class CodexRunner {
49
55
  const schema = path.join(assetsDir(), 'schema', 'sbox-answer.schema.json');
50
56
  const args = [
51
57
  'exec',
52
- '--cd', req.cwd,
58
+ ...(req.resumeSession && caps.resume ? ['resume'] : ['--cd', req.cwd]),
53
59
  '--model', req.model,
54
60
  '--skip-git-repo-check',
55
61
  '--json',
@@ -57,12 +63,13 @@ export class CodexRunner {
57
63
  '--output-last-message', lastMessage,
58
64
  '-c', `model_reasoning_effort="${req.effort ?? 'medium'}"`,
59
65
  '-c', 'agents.enabled=false',
60
- '--sandbox', req.readOnly ? this.options.sandboxRead : this.options.sandboxWrite,
66
+ '-c', `sandbox_mode=${JSON.stringify(req.readOnly ? this.options.sandboxRead : this.options.sandboxWrite)}`,
61
67
  '-c', `approval_policy="${this.options.approvalPolicy}"`,
62
68
  '-c', 'approvals_reviewer="auto_review"',
63
69
  ...this.options.extraConfig.flatMap((c) => ['-c', c]),
70
+ ...(req.resumeSession && caps.resume ? [req.resumeSession, '-'] : ['-']),
64
71
  ];
65
- const prompt = `${req.prompt}\n\nПоследнее сообщение верни строго как JSON-объект по схеме вывода: { "markdown": <полный ответ в Markdown>, "result": <содержимое блока sbox-result как объект> }.`;
72
+ const prompt = `${req.prompt}\n\n${readText(path.join(assetsDir(), 'hosts', 'codex', 'headless-result.md'))}`;
66
73
  const started = new Date().toISOString();
67
74
  return new Promise((resolve) => {
68
75
  const events = fs.createWriteStream(eventsPath, { flags: 'a' });
@@ -71,6 +78,16 @@ export class CodexRunner {
71
78
  let lastProgress = Date.now();
72
79
  let finishedBy = null;
73
80
  let stderrText = '';
81
+ let eventBuffer = '';
82
+ let session;
83
+ const readEvent = (line) => {
84
+ try {
85
+ const event = JSON.parse(line);
86
+ if (event.type === 'thread.started' && typeof event.thread_id === 'string')
87
+ session = event.thread_id;
88
+ }
89
+ catch { /* Keep non-JSON diagnostics in the raw events log. */ }
90
+ };
74
91
  const killGroup = (sig) => {
75
92
  try {
76
93
  if (child.pid)
@@ -103,7 +120,9 @@ export class CodexRunner {
103
120
  events.end();
104
121
  stderr.end();
105
122
  const finished = new Date().toISOString();
106
- const base = { started, finished, eventsPath, stderrPath, exitCode: code };
123
+ if (eventBuffer.trim())
124
+ readEvent(eventBuffer);
125
+ const base = { started, finished, eventsPath, stderrPath, exitCode: code, ...(session ? { session } : {}) };
107
126
  if (finishedBy === 'stopped')
108
127
  return resolve({ ...base, usable: false, markdown: null, failure: 'stopped', failureMessage: 'остановлено' });
109
128
  if (finishedBy === 'timeout')
@@ -129,6 +148,11 @@ export class CodexRunner {
129
148
  child.stdout.on('data', (chunk) => {
130
149
  lastProgress = Date.now();
131
150
  events.write(chunk);
151
+ eventBuffer += chunk.toString();
152
+ const lines = eventBuffer.split('\n');
153
+ eventBuffer = lines.pop();
154
+ for (const line of lines)
155
+ readEvent(line);
132
156
  });
133
157
  child.stderr.on('data', (chunk) => {
134
158
  stderrText += chunk.toString();