AI-EDT v0.2.29
- Реализована ревизия 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 отказов.