Skip to content

AI-EDT v0.2.29

Choose a tag to compare

@github-actions github-actions released this 19 Aug 10:36
· 106 commits to main since this release
  • Реализована ревизия 2026-07-28 спецификации MCP: метод server/discover, версия и возможности клиента в params._meta каждого запроса, поле resultType в каждом результате, расширение io.modelcontextprotocol/tasks поверх внутреннего механизма runKey
  • Совместимость: ответы клиентам ревизий 2024-11-05 - 2025-11-25 не изменились побайтово - ни resultType, ни _meta, ни полей кеширования в них не добавлено
  • Результат tools/list содержит ttlMs и cacheScope; порядок инструментов детерминирован (сортировка по имени)
  • Сервер занимает первый свободный TCP-порт из диапазона (по умолчанию 10 подряд от заданного); при занятости всего диапазона ответ перечисляет диапазон и идентифицирует держателя по реестру экземпляров
  • Межпроцессная блокировка монопольных операций: при конфликте ответ содержит держателя, выполняемую им операцию и время захвата
  • Ветка git связывается с приложением проекта; update_database отклоняет обновление приложения, не связанного с текущей веткой
  • Порог капа маркеров вычисляется после применения отбора, а не по всему проекту
  • Описание проверки резолвится по идентификатору из маркера: покрытие на реальном проекте выросло с 7 из 51 до 31 из 51
  • У call_hierarchy появился параметр depth (до 5 уровней) для направления callers

Протокол

Спецификация MCP выпустила ревизию 2026-07-28 с изменённой моделью установления соединения: метод initialize и уведомление notifications/initialized удалены. Клиент передаёт версию протокола и свои возможности в params._meta каждого запроса, обнаружение сервера выполняется методом server/discover, и каждый результат обязан содержать дискриминатор resultType.

Реализация: ревизия определяется на уровне отдельного запроса и только по его содержимому (ProtocolEra). Состояние между запросами не сохраняется, что и позволяет одному эндпоинту обслуживать обе модели соединения одновременно и в произвольном чередовании - конфигурация, которую спецификация называет dual-era server и явно разрешает.

Инвариант совместимости: ответ клиенту ревизии 2025-11-25 и старше не изменился побайтово. Не «остался совместимым», а именно не изменился: без resultType, без _meta, без ttlMs/cacheScope. Проверено на стенде: tools/list legacy-клиента возвращает 44 инструмента и ни одного нового поля.

Кеширование каталога: tools/list - крупнейший единичный ответ сервера (~133 КБ), передаётся до первого вызова инструмента и не меняется до переустановки плагина или смены пресета. Добавлены ttlMs = 300000 и cacheScope = private. Пять минут - компромисс: снятие инструмента пресетом должно вступать в силу за обозримое время, поскольку уведомления toolsListChanged сервер не отправляет. Порядок инструментов ранее определялся итерацией ConcurrentHashMap; теперь список сортируется по имени, что делает ответ байт-идентичным между вызовами.

Расширение задач

Механизм runKey существовал и раньше: инструмент, превысивший мягкий таймаут, возвращает ключ и продолжает работу в фоне. Ограничение было в контракте - чтобы воспользоваться ключом, вызывающий должен знать инструмент, воспроизводящий набор аргументов и то, что результат забирается повторным вызовом того же инструмента.

Расширение io.modelcontextprotocol/tasks вводит durable-идентификатор: CreateTaskResult с resultType: "task", опрос через tasks/get, отмена через tasks/cancel, подтверждение ввода через tasks/update. Реализованы все три метода; статусы - working / completed / failed / cancelled (состояние input_required не используется, поскольку ни одна операция сервера не запрашивает ввод).

Существенная деталь реализации: реестр PendingWorkRegistry отдаёт результат однократно и удаляет запись, тогда как задание опрашивается многократно и его терминальное состояние обязано быть стабильным. Поэтому первый опрос, обнаруживший завершение, извлекает результат из реестра и кеширует его в записи задания до истечения TTL (30 минут).

Задание создаётся только для клиента, объявившего расширение в clientCapabilities. Остальные получают прежний ответ с runKey без изменений.

Два уточнения по спецификации:

Код ошибки. Черновик ext-tasks указывает -32003 для Missing Required Client Capability. Основная схема ревизии 2026-07-28 (schema/2026-07-28/schema.ts) резервирует за протоколом диапазон от -32020 и явно помечает -32002 и -32003 как выведенные из обращения. Клиенты реализуются по основной схеме, поэтому используется -32021.

Область применимости. Раннер YAxUnit ведёт собственный реестр запусков (ACTIVE_LAUNCHES) с ключами вне PendingWorkRegistry. Задание над таким ключом не может быть разрешено ни при каком опросе, поэтому оно не создаётся - проверка выполняется запросом к реестрам, а не списком имён инструментов, так что новый инструмент не может незаметно попасть в ту же ситуацию.

Несколько экземпляров EDT на одной машине

Порт. Ранее каждому экземпляру требовалось задать порт вручную в настройках; при конфликте сервер не стартовал, а диагностика ограничивалась сообщением о невозможности привязки. Реализован диапазон: сервер последовательно пробует порты от заданного, занимает первый свободный и публикует в реестр экземпляров фактически занятый. При занятости всего диапазона сообщение перечисляет диапазон и, по реестру, соседний экземпляр с его портом. Ширина диапазона - преференция mcpServerPortSpan, по умолчанию 10; значение 1 воспроизводит прежнее поведение для конфигураций клиентов с жёстко заданным портом.

Информационная база. Два экземпляра, выполняющие монопольную операцию над одной базой, конфликтуют, но диагностика конфликта отсутствует: возвращается ошибка платформы о заблокированной конфигурации либо ожидание до таймаута. Оба случая неотличимы от отказа инструмента.

Реализована межпроцессная блокировка (MonopolyLock): файловая заявка в ~/.aiedt/locks, ключ - тождество информационной базы, вычисленное из строки соединения (InfobaseIdentity), а не из имени проекта, поскольку имя проекта локально для воркспейса. Ожидание не предусмотрено: при конфликте возвращается отказ с идентификацией держателя, выполняемой операции и времени захвата.

Заявка процесса, завершившегося аварийно, перехватывается по паре pid + время старта процесса - проверка времени старта исключает наследование блокировки переиспользованным pid. Нечитаемая заявка удаляется. Без этих двух правил единичное падение делало бы операцию неисполнимой на машине до ручного удаления файла.

Блокировку берёт update_database. Шесть точек в BmInfobaseExtensionHelper пока не покрыты: у каждой собственный объект результата, модуль реализован через рефлексию, и эти пути не исполняются в headless-прогоне тестов. BmBinaryImportHelper в список не входит - он работает с временной информационной базой и не конкурирует за монополию.

Привязка ветки к информационной базе

Сценарий отказа: переключение ветки меняет метаданные, последующее update_database выполняет реструктуризацию таблиц базы под новую конфигурацию. Операция необратима, а с точки зрения инструмента вызов ничем не отличается от обычного.

Реализовано сопоставление «ветка -> applicationId», хранимое в .settings/aiedt-branch-infobases.yaml проекта (тот же каталог, что у маркеров и кластеров). update_database сверяется с ним и отклоняет обновление, если ветка связана с другим приложением; переопределяется параметром ignoreBranchBinding=true, который входит в состав runKey и потому не может быть удовлетворён закешированным отказом.

Проверка выполняется только при наличии явно заданной связи. Нечитаемый файл настроек интерпретируется как отсутствие связей, а не как основание для отказа.

Автоматического переключения нет: выбор информационной базы за пользователя - решение с той же ценой ошибки, от которой защищает сама проверка.

Текущая ветка читается из .git/HEAD без git-библиотеки. Покрыты тестами нетривиальные случаи: .git как файл со ссылкой gitdir: (worktree, submodule), абсолютный и относительный путь ссылки, имя ветки с несколькими сегментами (feature/tax/report), detached HEAD.

Точность ответов

Кап маркеров. Порог PROJECT_SUMMARY_THRESHOLD сравнивался с общим числом маркеров проекта независимо от применённого отбора. Запрос с checkId, заполнивший страницу, получал сообщение о сорока тысячах маркеров в проекте и рекомендацию передать checkId. Счёт теперь выполняется через тот же предикат, что и выборка строк, с потолком в COMPACT_SCAN_CAP и явным указанием на неточность при его достижении. Рекомендация зависит от того, применён ли отбор.

Резолвинг описаний проверок. Маркер возвращает идентификатор вида module-unused-method, файл описания поставляется как module-unused-method-check.md; оба имени формируются EDT. Порядок кандидатов: точное имя, нижний регистр, затем те же варианты с суффиксом -check. Точное имя обязательно первым - одно описание существует под обоими именами одновременно. Измерено на реальном проекте: из 51 срабатывающей проверки резолвилось 7, стало 31; оставшиеся 20 - legacy- и Xtext-диагностики EDT, не имеющие описаний, о чём теперь сообщается явно. Та же процедура резолвинга используется для колонки Docs в таблице находок, которая ранее возвращала false для существующих описаний.

Область проверки

Проверено на стенде на данной сборке: server/discover (пять поддерживаемых ревизий, объявленное расширение задач, ttlMs/cacheScope); tools/list для legacy-клиента - 44 инструмента без новых полей; tools/list для клиента текущей ревизии - resultType, ttlMs, cacheScope, _meta.serverInfo, сортировка по имени; отказ -32022 на неподдерживаемой версии со списком поддерживаемых; tasks/get на невыданный идентификатор (-32602) и на legacy-клиенте (-32601); call_hierarchy с depth=3 - 11 вызывающих на трёх уровнях; резолвинг описания проверки по идентификатору маркера; кап маркеров в обоих направлениях; branch_infobase через фасад infobase_admin.

Не проверено на стенде: диапазон портов (требуется второй экземпляр EDT), межпроцессная блокировка (требуются два экземпляра над одной базой), отказ по привязке ветки (требуется проект внутри git-репозитория), выдача CreateTaskResult (требуется инструмент, фактически превысивший мягкий таймаут), тексты отказов чтения журнала регистрации (требуется база с журналом в формате SQLite либо серверная).

Сборка: 1585 тестов, 0 отказов.