Восемь ИИ-агентов и пять способов узнать, что они делают: как мы подключали OpenCode, Kilo и Pi к чёлке Mac

—

от автора

Lumio — бесплатное приложение для чёлки MacBook. Оно показывает, что сейчас делают ваши ИИ-агенты: кто работает, кто закончил, кто ждёт разрешения на команду. Разрешить можно прямо из чёлки, не переключаясь в терминал. Ещё есть пиксельный офис, где каждая сессия — человечек за своим столом.

До сегодняшнего дня Lumio понимал Claude Code, Codex, Cursor, GitHub Copilot и Antigravity. В 1.3 добавились OpenCode, Kilo Code и Pi. Казалось бы, ещё три строки в настройках. На деле — три разных способа подключиться и одна неделя, из которой два дня ушли не на код, а на то, чтобы проверить его на настоящих агентах без платных ключей.

У каждого агента свой язык

Вот как Lumio узнаёт о событиях у разных агентов:

Агент

Откуда Lumio знает, что происходит

Claude Code

хуки в ~/.claude/settings.json, наш скрипт пишет строку в журнал

Codex

единственный слот notify у многих уже занят, поэтому читаем файлы сессий ~/.codex/sessions/…/rollout-*.jsonl

Cursor

хуки + маленькое расширение Lumio, через которое приходит ответ на разрешение

GitHub Copilot

свой файл хуков ~/.copilot/hooks/lumio.json

Antigravity

его хуки, наш скрипт пишет строку в журнал

OpenCode, Kilo

JS-плагин в ~/.config/opencode/plugins/ и ~/.config/kilo/plugins/

Pi

расширение в ~/.pi/agent/extensions/

Внутри Lumio всё это сводится к одному формату — событиям хуков Claude Code: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop. Мы выбрали его не из любви к Anthropic, а потому что он был первым и под него уже написаны карточки, офис и итоги дня. Каждый новый агент — это переводчик в этот формат, а не новая ветка в интерфейсе.

У Pi переводчик занимает около 260 строк. Главная функция — одна строка в журнал:

function emit(ctx, name, fields) {  try {    const id = sessionID(ctx)    if (!id) return    const line = { session_id: `pi-${id}`, hook_event_name: name }    const cwd = cwdOf(ctx)    if (cwd) line.cwd = cwd    line.ts = Date.now() / 1000    line.lumio_agent = "pi"    line.pid = process.pid    Object.assign(line, fields)    fs.appendFileSync(LOG, JSON.stringify(line) + "\n")  } catch {}}

Пустой catch здесь не лень. Плагин живёт внутри чужого процесса, и если он бросит исключение, упадёт или зависнет агент человека. Правило для всех наших плагинов: никогда не бросать, никогда не ждать, не писать содержимое файлов, правок и вывода команд. В журнал попадают команда (до 300 символов), путь к файлу, шаблон поиска — и всё.

Разрешение из чёлки, когда агент не ждёт

С Claude Code и Cursor просто: хук блокирующий, агент ждёт ответа, мы показываем карточку и через две минуты без ответа отдаём решение обратно в терминал.

OpenCode и Kilo так не умеют. Плагин получает событие permission.asked, но агент не останавливается и сам показывает человеку свой вопрос. Поэтому получилась гонка: карточка появляется в чёлке, вопрос — в терминале, где ответили первым, там и решено. Второе место узнаёт об этом по событию permission.replied и закрывается. Двухминутного отсчёта на этих карточках нет — его просто не к чему привязать.

У Kilo нашёлся ещё один подвох. Ответ на «чувствительные» разрешения он принимает только с флагом, что ответил человек:

async function replyPermission(requestID, sessionID, reply, message) {  const body = { reply, interactive: true }  if (message) body.message = message  try { await post("/permission/{requestID}/reply", requestID, body); return } catch {}  // Старый путь есть только у OpenCode; Kilo по нему теряет interactive.  if (AGENT !== "opencode") return false  try { await base.client.postSessionIdPermissionsPermissionId(/* … */) } catch {}}

Без interactive: true Kilo такой ответ не принимает, а старый путь ответа, который есть у OpenCode, этот флаг теряет. Кстати, Kilo по умолчанию спрашивает разрешение на npm install и похожие команды, а OpenCode разрешает почти всё. Так что чёлка полезнее всего как раз для Kilo.

Как проверить чужого агента без платного ключа

Первая версия плагинов была готова за одну ночь. Тесты на поддельных OpenCode, Kilo и Pi — 31 сценарий для первых двух, 9 для Pi. Помощник, писавший плагин, нарочно ломал код в 19 местах — проверка ловила все. Всё было зелёное, и мы поставили на агентов пометку «бета», потому что ни разу не запускали их по-настоящему.

Потом сделали живую проверку. Настоящие агенты из npm ставятся во временную папку, рядом поднимается крошечный сервер в формате OpenAI API. Это наша «модель»: на первый запрос она просит выполнить touch, после результата отвечает «готово». Агент получает временные HOME и XDG_*, а вместо Lumio работает тестовый стенд, он отвечает «разрешить».

OpenCode сразу удивил: в режиме opencode run без терминала он отклоняет все разрешения сам, не спрашивая никого. Пришлось запускать opencode serve и гонять его через HTTP API. После этого OpenCode 1.19.4 и Kilo 7.8.15 прошли весь путь: запрос → разрешение в чёлке → команда выполнена → «готово».

С Pi живой прогон оказался полезнее всего. Новые версии сообщают о конце хода событием agent_settled и полем messageType, а в 0.73 их просто нет. Конец хода там — agent_end, а итог надо смотреть по stopReason последнего ответа модели. Поэтому расширение слушает оба события:

// упрощено// Pi 0.73: конец хода — agent_end (исход — по stopReason последнего ответа).on("agent_end", (e, ctx) => finish(ctx, false))// Новее: agent_settled после agent_end — второго конца не будет.on("agent_settled", (e, ctx) => finish(ctx, e.aborted === true))

Потом мы прогнали настоящий Pi трёх версий (0.73, 1.3 и 1.4, у последних пакет уже называется @earendil-works/pi-coding-agent): 292 запуска. Нашлась ещё одна тонкость: после сбоя модели Pi 1.x повторяет запрос до трёх раз и шлёт agent_end после каждой попытки. Поэтому у новых версий ход закрывает только agent_settled.

После живой проверки «бету» сняли. Вывод для себя: 40 зелёных сценариев на подделках проверяют наш код, а не то, как с ним разговаривает агент. Теперь живой прогон — обязательный шаг для каждого нового агента. Скрипт лежит рядом с тестами и запускается одной командой, но в общий набор не входит: ему нужна сеть, чтобы скачать агентов.

Второй читатель

Перед каждым выпуском у нас есть отдельный шаг: агент, который не писал этот код, читает весь diff с одной задачей — найти, обо что споткнётся живой человек на чужом Mac. Не стиль и не красоту, а именно это. Хороший пример того, на что он смотрит, — где искать установленных агентов. Проще всего проверить папку самого агента, Homebrew, /usr/local/bin и ~/.npm-global: на нашем Mac всё лежит там. Но у заметной части людей Node стоит через nvm, volta или pnpm, и агенты лежат совсем в других папках. Поэтому Lumio обходит и их, включая каждую версию Node из nvm, fnm, mise и asdf, и у человека с nvm кнопка «Подключить» появляется так же, как у нас.

На каждое замечание сначала пишем тест, который падает, и только потом код. А когда всё готово, diff заново читает другой агент, который не видел первого отчёта. Звучит как бюрократия, но это самый дешёвый способ посмотреть на Mac, не похожий на наш.

Вкладка «Итоги»

Пятая вкладка в чёлке: двенадцать столбиков по два часа за сегодня и семь дней недели. Высота столбика — сколько работали агенты. Нажали на столбик — внизу остаются только задачи за эти часы. У каждой задачи вместо «что просили» — первая содержательная фраза из ответа агента: «Добавил экспорт в CSV», а не «сделай экспорт плиз».

Данные берутся из журналов самих агентов: у Claude Code и Codex это их файлы сессий, у остальных общий журнал событий Lumio, тот же, что и для чёлки. Тонкость: Lumio сам периодически обрезает журнал, чтобы он не рос. Поэтому читатель истории помнит контрольные точки, и если файл стал короче прочитанного, перечитывает с начала, а не теряет день. Пересчёт идёт только по событиям, таймеров нет. В сеть ничего не уходит.

Сверху три плитки, и каждую можно нажать. «Вчера к этому часу» раскрывается в две кривые накопленного времени, сегодня и вчера, и видно, в котором часу вы обогнали вчерашний день или отстали. «Изменено файлов» показывает сами файлы по проектам, нажатие открывает файл. «Серия» показывает дни подряд с агентами и рекорд.

Для «вчера к этому часу» правило одно: оба дня считаются одинаково. Вчера — вся работа агентов до этой минуты, сегодня — тоже вся, включая ходы, которые ещё идут. Если засчитывать время только в конце хода, сегодня почти всегда проигрывает: агент второй час делает задачу, а плитка уверенно пишет «на час меньше, чем вчера». Поэтому идущий ход входит в «сегодня» сразу, и сравнение честное.

Если итоги дня пишутся в Obsidian, теперь туда можно добавить и картинки агентов: последнюю картинку, которую агент сделал или открыл в задаче, до трёх на проект. По умолчанию выключено. Lumio кладёт уменьшенные копии в папку рядом с заметками и никуда их не отправляет.

Mac не засыпает, но батарею не ест

Частая история: поставил агенту задачу на сорок минут, отошёл, Mac уснул через десять. В 1.2 для этого была ручная кнопка. В 1.3 Lumio делает это сам, пока агент работает, — и на этом правиле мы просидели дольше, чем на любом плагине, потому что легко сделать приложение, которое за ночь съест батарею.

Технически это одна системная блокировка PreventUserIdleSystemSleep (экран при этом гаснет как обычно):

$ pmset -g assertions | grep -A2 '(Lumio)'   pid 80042(Lumio): [0x0006290c00019ba1] 00:04:46 PreventUserIdleSystemSleep named: "Lumio: an AI agent is working"Details: Lumio keeps the Mac from idle sleep while an agent works; the display still sleepsTimeout will fire in 913 secs Action=<unknown action>

Блокировка ставится со сроком 20 минут и продлевается раз в 10. Если Lumio зависнет, macOS снимет её сама. Остальное — правила, и они разные от сети и от батареи. От сети держим, пока от агента приходят события (не старше 15 минут), плюс две минуты после хода. Если агент ждёт ответа — до получаса: вдруг вы отвечаете из Remote Control.

От батареи строже: только пока агент реально работает. Закончил или ждёт вас — отпускаем сразу. Заряд 30 % и ниже — отпускаем. Два часа никто не трогал клавиатуру и трекпад — отпускаем. В режиме энергосбережения не держим никогда, ни от сети, ни от батареи.

Отдельный случай — долгая команда. Сборка или тесты идут двадцать минут и не шлют ни одного события. Без особого правила Lumio решил бы, что агент затих, и отпустил Mac посреди сборки. Поэтому начатая и не законченная команда считается работой до 60 минут от начала, а батарейные пороги действуют и посреди неё.

Опроса нет: пересчёт идёт по событиям агентов, уведомлениям macOS о питании и пробуждению. Процессор с активной блокировкой — 0,001 с за 30 с. Ручной режим с таймером остался в Настройках для тех, кто хочет держать Mac сам, а автоматический можно выключить одним переключателем.

Ещё в 1.3

Задачу из заметок можно отметить выполненной прямо в чёлке. Яркость свечения у чёлки настраивается ползунком «Свечение у чёлки» в Настройках. А значок в шапке офиса позволяет проголосовать за общий офис для команды.

Что дальше

В 1.4 — ещё агенты: Qwen Code и Crush уже изучены, план готов. Механика та же: переводчик в формат хуков, живой прогон на поддельной модели, второй читатель перед выпуском.

Lumio бесплатный, ставится одной строкой в Терминале:

curl -fsSL https://lumiomac.com/install.sh | sh

Если Lumio у вас уже стоит, эта же строка обновит его до 1.3. Сайт: lumiomac.com.

Вопрос к тем, кто пишет интеграции с чужими CLI: как вы ловите изменения событий между версиями? У Pi формат событий поменялся между минорными версиями, и узнали мы об этом только на живом запуске. Подписываетесь на релизы, держите живой прогон в CI или просто ждёте жалоб?

И второй вопрос: какого агента вы используете, которого нет в нашем списке?

—

ссылка на оригинал статьи https://habr.com/ru/articles/1100704/