Claude Code как супервизор воркеров OpenCode
Введение
В этой схеме Claude Code не делает
правку сам.
Он отдаёт задачу OpenCode в контейнере
Docker, проверяет то, что вернулось, и
отправляет воркеру, что не так.
Сторона воркера, скрипт и параметры
opencode run описаны в статье
Claude Code как супервизор,
OpenCode в Docker как воркер.
Эта страница — сторона Claude Code: что запускает цикл, что он читает и
пишет и что проверяет, прежде чем назвать правку готовой.
Почему не субагент Claude Code?
Воркер работает на другой модели, по умолчанию на
бесплатной модели OpenCode, и под
--auto в контейнере, который видит один
worktree (OpenCode в Docker).
Claude Code читает только отчёт и diff git, поэтому
его собственный контекст остаётся небольшим.
Фраза, с которой всё начинается
Без фразы Claude Code делает задачу сам, как обычно.
Сообщение, которое начинается с одной из этих фраз, передаёт задачу
воркеру:
assign to opencode: <task> delegate to opencode: <task> /assign-to-opencode <task>
Последняя — это сам навык, вызванный как слэш-команда: любой навык в .claude/skills/ можно вызвать по имени.
Навык
Claude Code загружает навык, когда сообщение совпадает с его описанием
(как писать навыки для ИИ-ассистентов).
В описании названы фразы, а тело — это и есть цикл.
Это навык, который использовался для задачи на этой странице; строки
шага ревью, специфичные для проекта, сокращены до того, что есть в
любом репозитории:
mkdir -p .claude/skills/assign-to-opencode
vi .claude/skills/assign-to-opencode/SKILL.md
--- name: assign-to-opencode description: "The user says \"assign to opencode ...\" (also \"delegate to opencode\", \"give it to opencode\", /assign-to-opencode): you supervise and review, OpenCode in Docker does the work through container/tools/ai/delegate.py" --- ## Use when The user's message starts with "assign to opencode", "delegate to opencode" or "give it to opencode", or they run `/assign-to-opencode <task>`. You are the supervisor: you write the brief, start the worker, review its result on the host and send fixes. You do not make the change yourself, not even a small part of it. ## Do not use for - OpenCode in the container: this skill is for Claude Code on the host. - OpenCode on the host: use `delegate-to-container`; the same `delegate.py` and container, with OpenCode as the supervisor. - A task without the phrase: do it yourself as usual. ## Workflow 1. Name the task with a slug: lower case and dashes, e.g. `erlang-lesson-32`. The worker gets the new worktree `../site-agent-<slug>` on the branch `agent/<slug>` from `main`; pass `--worktree PATH` to give it an existing one. Tell the user in one line: slug, worktree, model. 2. Write the brief to the scratchpad (template below). The worker starts cold: name the files, the skills to load, what done means and the checks it can run in the container. Read the target files yourself only as far as the brief needs. 3. Start it with Bash `run_in_background` and wait for the notification; do not poll: `python3 container/tools/ai/delegate.py start <slug> --brief <file>` Add `--rw <path>` when the task edits outside container/src, `-m <provider/model>` only when the user named a model. Exit 3 means the model left the catalog: ask the user before you pick a paid model. 4. Review when it ends. The report shows the status, errors, the worker's last answer and the changed paths. Then, on the host: - `git -C <wt> status --short` and `git -C <wt> diff`; read new files. For big generated batches: tests and a sample of pages, not every file. - The linters on the touched files and a render of the touched pages with the docroot of the worktree. - Tests with the venv of the main checkout and no cache, so the worktree gets no ignored files (the launcher refuses the next round): `cd <wt>/container && <main>/container/.venv/bin/python -m pytest -p no:cacheprovider <tests> -q` - The rules of AGENTS.md: version headers, line endings, links, the language of the page. - It does what the user asked, and nothing else. 5. Problems: write a fix file, one bullet per issue with `file:line`, what is wrong and what to do, and send it into the same session, in the background: `python3 container/tools/ai/delegate.py fix <slug> --brief <fix-file>`. The worker keeps its context, so do not repeat the task. At most 3 fix rounds; after that, report what is still wrong and ask the user. 6. A round that failed before doing the work (an `error:` line, a usage limit, "Background service failed to start", `interrupted`): read the report, fix the cause, then send one `fix` that says to continue. 7. Done: tell the user what changed (paths), the checks and their results, the rounds and the cost, and what the worker left for the host. Then ask about the commit, with explicit-path `git -C <wt> add` and `git -C <wt> commit` commands. Never commit, merge or remove the worktree yourself. `delegate.py status` lists every task; `status <slug>` prints the last report again. State and logs: `container/reports/delegate/<slug>/`. ## Brief template ``` # Task: <one line> <what and why in 2-5 lines; the user's words where they matter> ## Files - edit or create: <paths> - read first: <paths> ## Load first <skills from the routing table of AGENTS.md> ## Done when - <checkable outcomes> ## Checks you can run here - php -l <files>; <other checks that need neither git nor the local site> ## Out of scope - commits (there is no git), anything outside the files above End with: the files you changed, the checks you ran and their result, and what is left to run on the host. ```
OpenCode тоже читает .claude/skills/, и в
контейнере он увидел бы этот навык.
Первая строка под «Do not use for» говорит воркеру, что навык не для
него.
Вторая строка отправляет OpenCode на хосте к его собственному навыку,
delegate-to-container: тот же цикл, запускаемый
сообщением, которое начинается со слов delegate to container
(OpenCode как
супервизор).
Маршрут в AGENTS.md
Задача про страницу подходит и навыкам для страниц.
Строка в начале таблицы маршрутизации в
AGENTS.md говорит, что фраза идёт первой, и
Claude Code читает эту таблицу в начале каждой сессии:
vi AGENTS.md
| Task | Load these skills first | |---|---| | Claude Code only: the message starts with "assign to opencode" (or "delegate to opencode") | `assign-to-opencode`: you supervise and review, OpenCode in Docker does the work |
Разрешение для скрипта
Claude Code запускает delegate.py один раз за
раунд и ещё по разу на каждый status.
Правило allow в .claude/settings.json избавляет
вас от запроса каждый раз.
Сам Claude Code не может добавить его в режиме auto.
Его попытка была заблокирована:
Permission for this action was denied by the Claude Code auto mode classifier. Reason: [Self-Modification].
Поэтому правило добавляете вы.
Эта команда сохраняет все правила, которые уже есть, и добавляет новое
в список allow:
jq '.permissions.allow = ((.permissions.allow // []) + ["Bash(python3 container/tools/ai/delegate.py *)"] | unique)' .claude/settings.json > .claude/settings.json.new && mv .claude/settings.json.new .claude/settings.json
jq '.permissions.allow, (.permissions.deny | length)' .claude/settings.json
[ "Bash(python3 container/tools/ai/delegate.py *)" ] 12
Эти 12 правил deny — те, что были в файле раньше
(правила deny для OpenCode и Claude
Code).
Добавить правило можно и вручную, командой
/permissions.
Бриф
Задача для этой страницы пришла одним сообщением:
assign to opencode: "http://127.0.0.1:8080/dev/cpp/sfml2_tetris.php#video this page has no video chapter but has video in table of contents it should be fixed. also it has legacy Статьи про С++ related articles block - it should be removed. also it is not translated to english - need to be translated, also it has empty introduction section - need to be written"
Воркер стартует холодным, поэтому перед брифом Claude Code читает
страницу ровно настолько, насколько нужно брифу.
Его вопросы и что дало чтение:
- Было ли когда-нибудь видео? Нет: ни в истории страницы, ни в истории её русской пары видео нет. Значит, правка — убрать пункт из содержания, а не добавить раздел.
- Откуда берётся старый блок? Из одного include файла, который ещё включают 9 других страниц. Поэтому бриф убирает строку, а файл оставляет.
- Что всё ещё на русском? 68 строк: заголовок, ключевые слова, описание, один подзаголовок и комментарии в коде. Ещё настройка языка страницы говорит «русский».
- Что воркер может запустить? В его образе есть php, python3 и rg, поэтому бриф просит php -l и рендер.
Сам бриф, без названий навыков этого репозитория:
# Task: fix and translate the English page /dev/cpp/sfml2_tetris.php (Tetris in C++ with SFML 2) The page lives on the English site but is still in Russian. The user asked for four things: 1. The table of contents links to `#video`, but the page has no video section, and no video was ever there. Remove the `'#video', 'Видео'` entry from `$tcont_urls`. Do not add a video section. 2. Remove the legacy "Статьи о C++" related-articles block: the line `include('_cpp_articles_inc.php');` near the end of the page. The page already shows related articles through `RelatedArticles` and `ArticlePage`, so keep those. Leave `_cpp_articles_inc.php` itself alone, because other pages still include it. 3. Translate everything still in Russian into English: the `$page_*` variables at the top, the same values in the `new ArticlePage(...)` arguments, the `Код` heading (→ "Code"), and every Russian comment in the C++ code block. Set `$page_lang = 'ru';` to `'en'`. Keep the code itself and its HTML escaping exactly as they are, and change only the comment text. 4. Write the empty Introduction: 1-3 short paragraphs on what the page gives the reader, based only on what the code on the page really does. Read the code first and invent nothing. Link the first mention of SFML and of C++ to their articles, and use English pages only. ## Files - edit: container/src/dev/cpp/sfml2_tetris.php (LF line endings; keep them) - read first: container/src/dev/cpp/RelatedArticles.php, container/src/dev/cpp/sfml.php - for reference only, do not edit: container/src/ru/code/cpp/sfml2_tetris.php (the Russian twin) ## Done when - no `#video` in `$tcont_urls`, no `_cpp_articles_inc.php` include in the page - no Cyrillic left in the file: `grep -n '[А-Яа-яЁё]' container/src/dev/cpp/sfml2_tetris.php` prints nothing - the Introduction has real English text with the links above ## Checks you can run here - `php -l container/src/dev/cpp/sfml2_tetris.php` - render with a shim that sets DOCUMENT_ROOT to /work/container/src, expect exit 0 ## Out of scope - every other file, the code logic, the class of the code block - commits (there is no git), the sitemap End with: the files you changed, the checks you ran and their result, and what is left to run on the host.
Два факта в первой версии этого брифа были неверны: путь инструмента,
который находит статьи для ссылок, и класс блока кода.
Оба пришли из памяти.
Claude Code сверил каждый путь и класс брифа с репозиторием до запуска
и исправил их.
Воркер идёт по неверному пути так же охотно, как по верному.
Запуск и ожидание
Claude Code запускает раунд инструментом Bash с параметром run_in_background:
python3 container/tools/ai/delegate.py start cpp-sfml2-tetris --brief cpp-sfml2-tetris-brief.md
Command running in background with ID: bepj3sbgq. Output is being written to: /tmp/claude-1000/.../tasks/bepj3sbgq.output. You will be notified when it completes
Затем он заканчивает свой ход одной строкой для вас: слаг, worktree и
модель.
Опросом он не занимается.
Claude Code получает уведомление, когда процесс завершается, и только
тогда читает отчёт.
Тем временем вы можете дать ему другую работу — как с этой страницей,
которая писалась, пока работал воркер.
У OpenCode в роли супервизора такого параметра нет: его инструмент
shell останавливает команду через две минуты, если в вызове не задан
более длинный таймаут.
Он запускает раунд с --detach, а затем вызывает
delegate.py wait, пока раунд не закончится
(--detach и
ожидание).
Ревью
В отчёте было done, и воркер перечислил свои
проверки как пройденные.
Claude Code проверяет ещё раз на хосте, потому что список воркера —
это утверждение, а не доказательство.
Изменились только комментарии в коде. Перевод затронул длинный блок кода. Короткий скрипт сравнивает каждую строку кода старого и нового файла с отрезанным комментарием:
python3 -I - container/src/dev/cpp/sfml2_tetris.php <<'EOF'
import re, subprocess, sys f = sys.argv[1] old = subprocess.run(["git", "show", f"HEAD:{f}"], capture_output=True, text=True, check=True).stdout new = open(f, encoding="utf-8").read() def code(text): out = [] for line in text.splitlines(): if line.startswith("<code>"): out.append(re.split(r"//|//", line, maxsplit=1)[0].rstrip()) return out a, b = code(old), code(new) print("code lines old/new:", len(a), len(b)) print("non-comment differences:", sum(x != y for x, y in zip(a, b)))
EOF
code lines old/new: 441 441 non-comment differences: 0
Введение говорит только о том, что делает код. В нём названы
поле 15 на 25, семь фигур, падение каждые 0,3 секунды, 0,05 секунды со
стрелкой вниз и одно очко за полную линию.
Каждое число было сверено по коду страницы:
grep -nE 'field_height =|field_width =|float</span> delay|delay = 0.05|userPoints \+= 1' container/src/dev/cpp/sfml2_tetris.php
126:<code><span class="blue">const</span> <span class="blue">int</span> field_height = 25;</code> 128:<code><span class="blue">const</span> <span class="blue">int</span> field_width = 15;</code> 174:<code> userPoints += 1;</code> 416:<code> <span class="blue">float</span> delay = 0.3;</code> 459:<code> <span class="violet">if</span> (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) delay = 0.05;</code>
Сама страница. php -l, рендер с docroot рабочего worktree и линтер PHP:
No syntax errors detected in container/src/dev/cpp/sfml2_tetris.php render exit 0 OK: all selected tools clean.
В отрендеренной странице: <html lang="en">,
кириллицы нет, #video нет и старого блока нет.
В заголовке C++ записан как
C++, как и на других
страницах сайта про C++.
Правка или конец
В этой задаче ревью ничего не нашло, поэтому раунда правок не было.
Когда оно что-то находит, Claude Code пишет по одному пункту на
проблему с файлом и строкой, что не так и что делать, и отправляет это
в ту же сессию
(отправить правку).
- Не больше 3 раундов правок. После этого Claude Code сообщает, что всё ещё не так, и спрашивает вас.
- Раунд, который упал до всякой работы, например из-за недоступной модели, получает исправленную причину и ещё один раунд с указанием продолжать.
- Никаких правок руками. Даже правка в одну букву уходит воркеру, чтобы в worktree лежала только работа воркера и её ревью.
Ключ для платной модели
Другая задача этой схемы — проверка путей к картинкам на сайте — провела
первый раунд на бесплатной модели OpenCode.
Ревью оставило длинный список для второго раунда, и Claude Code выбрал
для него платную модель, opencode-go/deepseek-v4.1-flash
из OpenCode Go, в той же сессии
(другая модель со
следующего раунда).
Этой модели нужен ключ OpenCode Go.
Claude Code попросил файл с ключом, и вот как это прошло.
1. Файл создаёте вы, в своём терминале.
Не через ! в сессии Claude Code: вывод команды
! попадает в разговор, а запрос, который ждёт
вашего ввода, там может не сработать.
И не вставляйте ключ в чат: он останется в транскрипте.
mkdir -p ~/.config/opencode-agent && chmod 700 ~/.config/opencode-agent
( umask 077; read -rsp 'OpenCode Go key: ' k; echo; printf '%s\n' "$k" > ~/.config/opencode-agent/opencode.key )
read -s не показывает то, что вы набираете.
Ключ попадает в саму оболочку, а не в командную строку, поэтому его нет
в истории оболочки, а printf, встроенная
команда оболочки, пишет его без процесса, который показал бы его в
списке процессов.
umask 077 внутри скобок делает новый файл
читаемым только для вас.
Проверено на фиктивном ключе:
stat -c '%a %U %n' ~/.config/opencode-agent ~/.config/opencode-agent/opencode.key
700 me /home/me/.config/opencode-agent 600 me /home/me/.config/opencode-agent/opencode.key
Страница OpenRouter делает свой файл с ключом через install -m 600 и редактор (ключ для контейнера). Файл тот же; read -s держит ключ подальше от экрана.
2. Claude Code проверяет, что файл на месте, и не читает его.
test -r ~/.config/opencode-agent/opencode.key && echo present
present
Затем он запускает раунд с путём к файлу:
python3 container/tools/ai/delegate.py fix img-src-check -m opencode-go/deepseek-v4.1-flash --opencode-key ~/.config/opencode-agent/opencode.key --brief fix-1.md
3. Что происходит с ключом.
- Где он может лежать. Лаунчер отказывается от файла с ключом внутри репозитория или worktree, поэтому ключ нельзя ни смонтировать, ни закоммитить. Проверено на копии фиктивного ключа в worktree: ERROR: keep the key file outside the repo and the worktree.
- Один запуск. Лаунчер читает файл и отдаёт ключ именно этому запуску контейнера как переменную окружения OPENCODE_API_KEY. Docker получает только имя, -e OPENCODE_API_KEY, так что ключа нет и в аргументах docker run. В контейнер и в worktree ничего не записывается.
- Состояние. delegate.py хранит только путь к файлу. Поиск фиктивного ключа в файле состояния и в логах всех раундов не нашёл его нигде.
- Claude Code. Он запускает по файлу test -r и больше ничего.
4. Что всё-таки остаётся открытым.
Пока идёт запуск, ключ находится в окружении контейнера.
Воркер может прочитать его через env, а любой,
кто может пользоваться Docker на вашей машине, — через
docker inspect.
Проверено на работающем контейнере с фиктивным ключом:
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' opencode-agent-site-agent-delegate-selftest-161385 | grep OPENCODE_API_KEY
OPENCODE_API_KEY=dummy-not-a-real-key
Это цена такой схемы.
Под --auto воркер может пользоваться ключом,
поэтому дайте ему собственный ключ с лимитом трат, который можно
удалить, не трогая остальные ваши ключи.
5. Не рекомендуется: сохранённый вход.
opencode auth login в контейнере кладёт ключ в
общий том сессий, и каждый следующий запуск воркера его видит,
хотели вы этого или нет
(сохранённый вход: проверить,
использовать, создать или удалить).
В этой схеме проверка нашла один такой вход:
docker run --rm -v opencode-agent-data:/home/agent/.local/share/opencode opencode-agent auth list
OpenCode Go OpenCode Go stored
С ним запуск на opencode-go/ работает без
всякого файла с ключом, а воркер любой задачи, в том числе бесплатных,
может добраться до ключа в томе.
Удалите его и держите ключ в файле из шага 1.
Дождитесь, пока не работает ни один раунд: выход из аккаунта пишет в
ту же базу в томе, которой пользуется работающий воркер.
Здесь второй раунд ещё шёл, поэтому Claude Code дождался конца его
процесса (tail --pid) и только потом удалил
вход:
docker run --rm -v opencode-agent-data:/home/agent/.local/share/opencode opencode-agent auth logout opencode-go "OpenCode Go"
┌ Log out of an account │ ◇ Removed account from OpenCode Go │ └ Done
docker run --rm -v opencode-agent-data:/home/agent/.local/share/opencode opencode-agent auth list
No authenticated integrations
Теперь запуску на opencode-go/ нужен
--opencode-key.
Запуску на бесплатной модели вход не нужен, и он работает как раньше.
6. Когда ключ отвергнут.
Следующая задача — список файлов картинок на сайте — стартовала на той
же модели с файлом ключа.
Её раунд закончился через 30 секунд:
python3 container/tools/ai/delegate.py start image-list --base agent/img-src-check -m opencode-go/deepseek-v4.1-flash --opencode-key ~/.config/opencode-agent/opencode.key --rw container/tests --rw container/docs --rw container/tools/images --rw container/tools/ai/docker --rw .pre-commit-config.yaml --brief brief-image-list.md
model: opencode-go/deepseek-v4.1-flash: not checked: only opencode/<id> models have a public catalog worktree: created /home/me/github/site-agent-image-list on agent/image-list from agent/img-src-check round 1 of image-list started 2026-11-04T18:11:02+02:00; events: /home/me/github/site/container/reports/delegate/image-list/round-1.jsonl task: image-list, round 1 (start), branch agent/image-list worktree: /home/me/github/site-agent-image-list model: opencode-go/deepseek-v4.1-flash session: ses_e58aba3daffeZbYfWe45LlfgqI started: 2026-11-04T18:11:02+02:00, ended 2026-11-04T18:11:32+02:00 status: failed, exit 1 events: error 1 cost: $0.0000, tokens 0 in / 0 out error: provider.auth: Upstream request failed: Invalid credential worker's last answer: none worktree changes: none files: /home/me/github/site/container/reports/delegate/image-list/round-1.{prompt.md,jsonl,err}
Запрос отвергли ещё до первого шага: ни изменений в worktree, ни
затрат.
Второй раунд проверки картинок работал на этой модели до выхода из
аккаунта в шаге 5.
Он пользовался сохранённым входом: ключ в файле никогда не проверяли.
Файл записали заново, и раунд, отправленный сразу же, упал так же,
через 12 секунд.
Проверяйте ключ в оболочке контейнера, прежде чем снова отправлять
раунд: один короткий запрос стоит меньше раунда и отличает неверный
ключ от проблемы одной модели.
Проверьте ключ сами, в своём терминале, в оболочке контейнера с
файлом ключа.
В worktree упавшей задачи изменений нет, поэтому оболочка может
пользоваться им:
container/tools/ai/opencode-docker.sh --opencode-key ~/.config/opencode-agent/opencode.key ../site-agent-image-list shell
На приглашении /work $:
opencode auth list
OpenCode Go OPENCODE_API_KEY environment OpenCode Console OPENCODE_API_KEY environment
Ключ дошёл до контейнера.
environment говорит только, что переменная
задана, а не что ключ работает
(как узнать, есть ли
сохранённый вход).
Один короткий запрос на двух моделях OpenCode Go:
opencode run -m opencode-go/deepseek-v4.1-flash "say ok"
> build · deepseek-v4.1-flash Error: Upstream request failed: Invalid credential
opencode run -m opencode-go/deepseek-v4-pro "say ok"
Error: Upstream request failed: Invalid credential
Что значат ответы:
- В auth list нет строки с OpenCode Go и environment. Ключ не доходит до контейнера: проверьте путь после --opencode-key.
- Invalid credential на обеих моделях. Ключ не работает как ключ OpenCode Go: это его часть, другой ключ или удалённый. Скопируйте его заново из аккаунта OpenCode Go и запишите файл снова командой из шага 1.
- Падает только одна модель. Проблема на стороне OpenCode, для этой модели: подождите или выберите другую модель.
- Обе ответили ok. Ключ работает: отправляйте раунд снова.
Здесь упали обе модели.
Ключ скопировали заново, файл записали снова, и та же проверка в новой
оболочке:
opencode run -m opencode-go/deepseek-v4.1-flash "say ok"
> build · deepseek-v4.1-flash ok
opencode run -m opencode-go/deepseek-v4-pro "say ok"
> build · deepseek-v4-pro ok
После этого Claude Code проверяет файл, не читая ключ: его права, размер и время записи.
stat -c '%a %s %y' ~/.config/opencode-agent/opencode.key
600 52 2026-11-04 21:08:23.768605839 +0200
Новое время значит, что файл записали заново.
Файл отвергнутого ключа весил 31 байт.
Файл, который намного короче файла рабочего ключа, стоит перепроверить:
ключ обрезался при вставке или это другой ключ.
Затем задача продолжается в своей сессии.
Отвергнутые раунды не сделали ни шага, поэтому бриф первого раунда
остаётся задачей.
Раунду достаточно сказать это:
python3 container/tools/ai/delegate.py fix image-list --message "The previous attempts failed on an authentication error before any step ran; the key is fixed now. Do the whole task from your first message, from the start, and end with the summary it asks for."
round 3 of image-list started 2026-11-04T21:10:14+02:00; events: /home/me/github/site/container/reports/delegate/image-list/round-3.jsonl task: image-list, round 3 (fix), branch agent/image-list worktree: /home/me/github/site-agent-image-list model: opencode-go/deepseek-v4.1-flash session: ses_e58aba3daffeZbYfWe45LlfgqI started: 2026-11-04T21:10:14+02:00, ended 2026-11-04T21:19:49+02:00 status: done, exit 0 events: step_finish 85, step_start 86, text 4, tool_use 92 cost: $0.0726, tokens 135579 in / 23566 out
Тот же id сессии, что у отвергнутого первого раунда, и работа сделана
за 10 минут.
fix сохраняет модель и файл ключа задачи,
поэтому команде не нужно ни то, ни другое.
Чего Claude Code не делает
- Он не коммитит и не сливает. В конце он спрашивает, коммитить ли вам, и даёт команды git add и git commit с явными путями.
- Он не удаляет worktree. Его удаляете вы, после слияния.
- Он не правит файлы задачи. Фраза значит, что работу делает воркер.
- Он не трогает задачу, которую не запускал. Во время этой задачи delegate.py status показал ещё и работающую задачу, которую Claude Code не запускал; она осталась как была.
Настоящая задача в цифрах
- Один раунд на 3 минуты, с бесплатной моделью OpenCode, стоил $0.
- Один файл изменён в worktree: 88 строк добавлено, 72 удалено.
- Осталось вам: посмотреть в браузере и закоммитить ветку agent/cpp-sfml2-tetris.
Автор статьи: Клавдия Кондакова