Агент прочитал инструкцию. Как показать ему работающее Flutter-приложение?
Анализатор молчит, модель получила новые заказы, а экран продолжает загрузку. Эксперимент показывает, что подтверждают widget-тесты и наблюдение через MCP — и почему доступный инструмент не обязательно становится частью проверки агента.
- Опубликовано
- 21 сентября 2026 г.
- Проверено
- 17 сентября 2026 г.
Агент прочитал инструкцию. Как показать ему работающее Flutter-приложение?
На экране открыт список заказов. После нажатия «Обновить» появляется индикатор, источник возвращает новые данные, операция завершается. Только список почему-то остаётся прежним, а индикатор продолжает вращаться.
Анализатор замечаний не находит. Проверка модели тоже проходит: новые заказы действительно получены, признак загрузки сброшен. Если поручить исправление AI-агенту, он вполне может найти причину по коду и добавить нужную строку. Но каким будет основание принять результат? Найденная строка, успешный тест или поведение приложения после исправления?
Вопрос особенно уместен сейчас, когда вокруг Flutter появляются инструкции для агентов, пакетные skills и подключения к инструментам разработки. Перечень подключений в редакторе ещё не объясняет, какие сведения получил агент и на чём основан его вывод. Как показать ему происходящее в приложении — и воспользуется ли он такой возможностью вообще?
Инструкция не знает, что сейчас на экране
Skill — это набор инструкций для определённой работы. Например, как пользоваться API пакета, где искать ограничения и что проверить после изменения кода. Такая инструкция может помочь агенту избежать неправильного решения, но сама по себе не сообщает, остался ли на экране индикатор после последнего нажатия.
Для получения подобных сведений нужен доступ к инструментам работающего приложения. Один из вариантов — Dart and Flutter MCP server. MCP, Model Context Protocol, задаёт способ, которым AI-клиент обращается к предоставленным ему инструментам. В данном случае сервер связывает клиента с инструментами Dart и Flutter, в том числе с диагностикой запущенного приложения. Документация Flutter отдельно рассматривает skills, локальный MCP-сервер и поиск документации: у них разные задачи.
Runtime здесь означает уже выполняющееся приложение: его дерево виджетов, состояние и доступную отладочную информацию. Читать реализацию кнопки и нажать эту кнопку в запущенном приложении — два разных действия. Первое позволяет объяснить механизм, второе — получить наблюдение о конкретном запуске.
Из этого, впрочем, не следует, что без MCP агент ничего не способен проверить. У него остаются обычные команды, тесты и другие средства отладки. В этом эксперименте их никто не отнимал: вопрос не в том, удастся ли заставить агента обратиться к единственному оставшемуся инструменту, а в том, какие проверки он выберет для конкретного дефекта.
Ошибка, для которой анализатор не обязан возмущаться
Для разбора подготовлен небольшой эксперимент ArkTelos Lab: один Flutter-экран, два заказа, кнопка обновления и номер версии данных. Источник локальный, без сети и внешнего сервиса. В работающем приложении он отвечает через 300 миллисекунд; в тестах момент ответа задаётся явно.
Модель хранит снимок данных snapshot, признак загрузки refreshing и сообщение error. Снимок имеет тип CatalogSnapshot и содержит версию и список заказов. Модель наследует ChangeNotifier, чтобы сообщать подписчикам об изменениях. На эти уведомления подписан ListenableBuilder, который заново строит соответствующую часть интерфейса.
Неисправный метод обновления выглядит так:
Future<void> refresh() async {
if (refreshing) return;
refreshing = true;
error = null;
notifyListeners();
try {
snapshot = await source.fetch();
} catch (_) {
error = 'Refresh failed. Previous orders retained.';
} finally {
refreshing = false;
}
}
В начале обновления интерфейс получает уведомление и показывает загрузку. Затем источник возвращает новый снимок, а finally сбрасывает флаг. Изменения остаются внутри модели: уведомления о завершении нет. В воспроизведённом сценарии интерфейс продолжает показывать то, что построил после первого уведомления.
Та же причина проявляется при отказе источника. Сообщение об ошибке уже записано, старые заказы сохранены, но экрану никто не сообщил, что пора убрать индикатор и показать результат неудачного обновления.
Все типы при этом корректны. Пропущенный вызов не делает метод синтаксически неправильным, а обычная проверка полей модели вполне может завершиться успешно. Она проверяет, какие данные получены, но ещё не проверяет, что эти данные стали видны.
Здесь намеренно нет другого state manager и дополнительного архитектурного слоя. Они усложнили бы поиск причины, не добавив ничего к вопросу эксперимента. Обработка ошибки тоже упрощена до одной строки: этот стенд не предлагает ловить любую ошибку таким способом в рабочем приложении.
Как проверить расхождение без живого окна
Проверка интерфейса начинается с небольшого изменения вопроса. Вместо «модель получила версию 2?» требуется спросить: «после ответа источника видна версия 2 и исчезла загрузка?»
Для этого подходит widget test — тест, который строит Flutter-виджеты, выполняет действия и проверяет полученное дерево. Чтобы не привязывать проверку к скорости машины, учебный источник возвращает Future, завершением которого управляет тест. В Dart такую возможность даёт Completer. Источник реализует интерфейс CatalogSource с единственным методом fetch():
class ControlledSource implements CatalogSource {
final List<Completer<CatalogSnapshot>> requests = [];
@override
Future<CatalogSnapshot> fetch() {
final pending = Completer<CatalogSnapshot>();
requests.add(pending);
return pending.future;
}
}
Тест сначала строит CatalogApp с этой моделью и нажимает кнопку. Пока ответ не выдан, индикатор должен присутствовать. Затем тест завершает запрос и проверяет уже интерфейс. Значимая часть проверки из эксперимента:
await tester.tap(find.byKey(const ValueKey('refresh')));
await tester.pump();
expect(find.byKey(const ValueKey('loading')), findsOneWidget);
source.requests[0].complete(
const CatalogSnapshot(2, ['Order A / v2', 'Order B / v2']),
);
await tester.pump();
expect(model.snapshot.version, 2);
expect(find.text('Version 2'), findsOneWidget);
expect(find.byKey(const ValueKey('loading')), findsNothing);
pump() даёт тестовой среде обработать обновление и построить кадр. Проверка не создаёт новый экземпляр приложения после ответа, чтобы случайная пересборка не скрыла пропущенное уведомление. Сначала проверяется модель, затем текст и индикатор в дереве. На неисправной версии первое ожидание после ответа проходит, а поиск Version 2 уже падает.
Исправление действительно состоит из одной строки:
} finally {
refreshing = false;
notifyListeners();
}
Важно её положение: уведомление должно приходить после завершения изменения состояния. Иначе подписчик, который сразу читает поля модели внутри обработчика уведомления, ещё получит refreshing == true.
После исправления исходные проверки экрана проходят. Проверен и следующий запрос: кнопка снова работает, появляется версия 3. Отдельная ветка проверяет отказ источника, сохранение прежнего списка и успешную повторную попытку. Иначе исправление одного удачного нажатия слишком легко принять за исправление всего сценария.
Что добавляет работающий Flutter runtime
Теперь тот же экран можно исследовать не только в widget test. В стенде запущен настоящий Flutter runtime без отдельного графического окна — flutter-tester. Это не прогон на Android или iPhone и не проверка изображения по пикселям. Зато у процесса есть живое дерево виджетов, с которым можно взаимодействовать.
Для подключения используется DTD, Dart Tooling Daemon: служба отладочного взаимодействия инструментов Dart. MCP-сервер получает адрес службы именно учебного запуска, после чего может обращаться к его инструментам. Тестовая точка входа дополнительно включает Flutter Driver — средство, через которое в этом стенде выполняется нажатие кнопки. Такой вход не предназначен для production-сборки.
Проверен dart_mcp_server 1.1.1 с Flutter 3.44.7 и Dart 3.12.2. Это зафиксированное окружение эксперимента, а не утверждение о последних доступных версиях SDK. У сервера запрошен фактический список инструментов; затем выполнены подключение, чтение дерева, чтение runtime-ошибок и нажатие Refresh.
Результат получился наглядным:
| Наблюдение после ответа источника | Неисправная версия | Исправная контрольная версия |
|---|---|---|
| Текст версии | Version 1 |
Version 2 |
| Заказы в дереве | прежние, v1 |
новые, v2 |
| Индикатор загрузки | остаётся | отсутствует |
| Runtime-диагностика | ошибок не сообщила | ошибок не сообщила |
Последняя строка важна не меньше остальных. Код не обязан выбрасывать исключение, чтобы нарушить пользовательский сценарий. Ответ «ошибок выполнения нет» ничего не говорит об исчезновении индикатора, пока это исчезновение не проверено отдельно.
Исправная контрольная версия была запущена новым процессом. Проверка не опиралась на предположение, что ранее открытый экземпляр уже подхватил изменения. Именно это здесь называется холодным запуском.
Эти наблюдения получены отдельным проверочным скриптом, без участия модели. Но между работающим сервером и доступом агента есть ещё клиент: он должен обнаружить инструменты и разрешить их вызов. Поэтому следующим шагом проверялось уже обращение из самого агентного клиента.
На заведомо исправном приложении агент получил короткое задание: подключиться, прочитать дерево, нажать Refresh и снова прочитать результат. Например, нажатие в журнале выглядит так — здесь показаны имя инструмента и переданные ему аргументы, а не команда для терминала:
{
"tool": "flutter_driver_command",
"arguments": {
"command": "tap",
"finderType": "ByValueKey",
"keyValueString": "refresh",
"keyValueType": "String",
"timeout": "3000"
}
}
Кнопка в приложении имеет ключ ValueKey('refresh'), поэтому инструмент находит именно её. Перед нажатием в этой проверке отключена синхронизация Flutter Driver с окончанием анимаций: иначе постоянно вращающийся индикатор может мешать следующему действию. После нажатия агент вызвал widget_inspector с командой get_widget_tree и параметром summaryOnly: true — получил сокращённое дерево, а не скриншот.
В первом ответе были Version 1 и заказы v1. Во втором — Version 2, заказы v2 и отсутствие CircularProgressIndicator. Отдельный вызов диагностики не обнаружил ошибок выполнения. Все шесть MCP-вызовов завершились успешно; вся проверка заняла около 36 секунд.
Теперь известно, что агент способен выполнить этот конкретный сценарий через MCP. Но готовое приложение ему уже предоставили. Поиск ошибки и проверка собственного исправления в эту короткую пробу не входили.
Воспользуется ли агент доступным инструментом
Для поиска ошибки проведены три самостоятельные попытки с рабочим MCP. В каждой использовались GPT-6 Astra, уровень усилия medium и Codex CLI 0.153.4. Агент получал чистую неисправную версию, одинаковую задачу и начальный тест, проверяющий только открытие экрана. Готовое исправление, история предыдущих попыток и проверочные тесты исследователя в рабочую папку не попадали. Лимит каждой попытки — пять минут и 25 вызовов инструментов.
Задание описывало зависший список и требовало сохранить загрузку, обработку ошибки и возможность повторного обновления. В нём был указан адрес работающего приложения, но не было обязательного условия «проверить исправление именно через MCP». Способ проверки оставался на выбор агента.
Все три участника нашли пропущенное уведомление и написали собственные регрессионные тесты. Сначала эти тесты упали на исходном коде, затем прошли после исправления. Каждая попытка заняла чуть больше двух минут и потребовала 9–10 инструментальных вызовов.
Ни одного обращения к MCP в этих трёх попытках не было. Каждый участник ограничился кодом, тестами и анализатором. Причём в итоговом ответе прямо указал: существующий экземпляр приложения не перезапускался и не проверялся. Приписывать себе наблюдение, которого не было, агенты не стали.
Патчи дополнительно проверены вне агентских попыток. Каждый прошёл анализатор и семь тестов: начальный, три написанных участником и три заранее подготовленных исследователем. Затем исправленное приложение запускалось новым процессом, и отдельный MCP-скрипт подтверждал обновление заказов и исчезновение загрузки. Эта последняя проверка принадлежит процедуре эксперимента, а не агенту, написавшему патч.
Из результата не следует, что MCP бесполезен. В этом небольшом примере дефект виден по коду, а его последствия воспроизводятся widget-тестом. Дополнительный доступ к приложению не был необходим для получения правильного патча. Подключение оставляло возможность ещё одной проверки, но само по себе не делало её частью результата.
У эксперимента была и неудачная предварительная серия из шести попыток: политика клиента не позволяла подтвердить MCP-вызов. Она сохранена в протоколе, но сравнением с работающим MCP не считается. После исправления настройки выполнены описанная выше техническая проба и три новых опыта. Лимиты и некоторые формулировки задания между сериями отличаются, поэтому сопоставлять их время как строгий бенчмарк нельзя.
Для новой пробы и трёх опытов вместе клиент сообщил 696 731 входной токен, включая 588 288 кешированных, и 8 570 выходных. Это суммарный учёт обращений с повторно передаваемым контекстом, не размер одного задания и не денежная оценка. Проверка доступности инструмента тоже расходует модельный бюджет; считать её бесплатной подготовкой нельзя.
Что требовать от результата агента
Практический критерий готовности стоит формулировать через поведение, которое можно проверить. Для этого экрана: после успешного ответа видны новые заказы и номер версии, загрузка завершена, повторное обновление доступно; после ошибки старые заказы остаются, а повторная попытка может завершиться успешно.
Агенту можно поручить воспроизвести нарушение этого критерия, добавить проверку, внести исправление и повторить проверку на изменённом коде. Если требуется проверить именно работающее приложение, это стоит включить в задание отдельно. Например:
После исправления запусти приложение с изменённым кодом. Через MCP нажми Refresh и проверь, что появились новые заказы и версия, а индикатор исчез. В результате раздели прохождение тестов и наблюдение в приложении. Если подключение или проверка недоступны, укажи, что осталось неподтверждённым.
Это предлагаемый критерий приёмки, а не задание, уже проверенное в серии исправлений. Короткая техническая проба подтвердила выполнение действий на готовой исправной версии. Как агент соединит поиск дефекта, обновление приложения и такую проверку в одной задаче — отдельный вопрос. Три проведённые попытки на него не отвечают.
Требование «используй все подключённые инструменты» слабее. Оно поощряет лишние действия, но не определяет, какую неопределённость требуется снять. Для пропущенного уведомления достаточное доказательство может дать небольшой регрессионный тест. Для проблемы, проявляющейся только в конкретном запущенном состоянии, потребуется исследование этого состояния. Если вопрос в перспективе, отступах, цвете или перекрытии элементов, одного дерева виджетов уже недостаточно — нужна визуальная проверка.
Подключение к runtime также требует границ доступа. В эксперименте использовался адрес только своего учебного процесса, а готовое исправление и оценочные тесты не передавались участникам. Перечень корней проекта в MCP удобен для навигации, но сам по себе не заменяет ограничения доступа к файлам и приложениям. Устойчивость к произвольным вредоносным инструкциям этот опыт не проверяет.
В разобранном примере тесты действительно обнаружили дефект и подтвердили исправление. MCP позволил отдельно наблюдать тот же сценарий в работающем приложении. Но простое добавление сервера не заставило участников провести эту проверку самостоятельно. Поэтому при ревью полезнее спросить, какое поведение подтверждено и каким способом, чем считать подключённые инструменты.
Для этого разбора ArkTelos Lab подготовила исходники, тесты и протоколы эксперимента. В них можно проследить исходный дефект, исправление и фактические обращения к инструментам. При повторении стоит начать с воспроизведения зависшего обновления и рабочего подключения, а модельные попытки запускать уже с заранее определённым бюджетом и критерием завершения.
ArkTelos: официальный сайт · новости RU · news EN
ArkTelos Lab: лаборатория · Telegram RU · Telegram EN
Границы результата
- Учебный дефект, Flutter 3.44.7 / Dart 3.12.2 / dart_mcp_server 1.1.1, macOS headless. Одна клиентская проба на исправном приложении и три попытки ремонта; участники не использовали MCP. Холодные проверки выполнены исследователем. Не доказаны превосходство MCP, визуальная корректность, работа на Android/iOS и защита от prompt injection.
CODE / DATA / AGENTS
Связанные эксперименты
agent-runtime-verificationВоспроизвести пропущенное уведомление и разделить тесты, MCP-наблюдения и действия агента.
Учебный дефект, Flutter 3.44.7 / Dart 3.12.2 / dart_mcp_server 1.1.1, macOS headless. Одна клиентская проба на исправном приложении и три попытки ремонта; участники не использовали MCP. Холодные проверки выполнены исследователем. Не доказаны превосходство MCP, визуальная корректность, работа на Android/iOS и защита от prompt injection.
Открыть паспорт эксперимента →Версия в Telegram