feat(tools): пассивный сигнал «new comments: N» в результатах tool-вызовов — комментарии человека находят агента сами #417
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Мотивация
Цикл «человек комментирует, пока агент работает» сейчас pull-only: агент должен ВСПОМНИТЬ вызвать
checkNewComments(deferred-тир, т.е. тул ещё и спрятан за каталогом), а сам тул дорогой — полный обход дерева спейса + per-page выборка комментариев (client.ts:2801-2852). На практике агент в длинном ходе (роль исследователя, класс инцидента #410) не проверяет комментарии никогда, и suggestedText-флоуcreateComment(правки «на подпись» человеку) не превращается в петлю обратной связи.Прецедент пассивного сигнала уже есть —
<page_changed>-блок in-app чата (ai-chat.prompt.ts:185-191): диф открытой страницы доставляется агенту без запроса. Но он per-turn и только про тело страницы. Здесь — то же самое для комментариев, и mid-turn: короткая эфемерная строка «new comments: N» в результате ЛЮБОГО tool-вызова. Проблема исчезает классом: сигнал сам находит агента, помнить ничего не нужно.Дизайн
Формат сигнала
Дополнительный content-элемент в результате тула (не смешивать с основным результатом):
</page_changed>вai-chat.prompt.ts:124-135).listComments/checkNewComments/createComment) — там он тавтологичен.Точки перехвата (обе поверхности)
registerTool(packages/mcp/src/index.ts:118-125, паттерн метрик #402/#403): все тулы проходят через него, per-tool боилерплейта нет. Состояние — per-session:McpServer+DocmostClientсоздаются на сессию (http.ts:35-52), state живёт рядом с клиентом.ai-chat-tools.service.ts(или общий с метрик-обвязкой #402). Межходовой watermark — из тех же механизмов, что снапшотpageChanged.Источник сигнала (стоимость — главный вопрос)
Наивное «checkNewComments на каждый вызов» запрещено (обход всего спейса). Варианты по слоям:
createdAt > watermark AND pageId IN <рабочий набор>) — сервер имеет доступ к БД, один дешёвый запрос; дебаунс ≥15–30 с.listComments(pageId)по страницам рабочего набора — не чаще раза в X секунд на страницу; пустой рабочий набор → ноль вызовов.getPageRaw/mutatePageContent), сканируют comment-марки и диффят набор id против per-session seen-set: ноль лишних API-вызовов. Ограничение: не видит реплаи (реплай не добавляет марку) и unanchored-комментарии → годится как ТРИГГЕР для точногоlistComments, не как замена.Рабочий набор = pageId, к которым сессия обращалась (+ открытая страница для in-app). Весь workspace автоматически НЕ сканируется никогда — для этого остаётся явный
checkNewComments.Watermark: инициализируется стартом сессии (in-app — временем конца прошлого хода); продвигается при emission и при явных вызовах
listComments/checkNewComments.Критерии приёмки
listCommentsна страницу за дебаунс-окно.Тесты
Unit на wrapper: emission-on-change, дебаунс, тавтологичные тулы исключены, injection-safety; e2e: комментарий появляется между двумя tool-вызовами → сигнал во втором.
Связано
Roadmap #416 (независимая работа, фаза 0/1, non-breaking). #410 (сценарий исследователя — главный потребитель), #408 (комментарийная петля createComment), #407 (наблюдаемость эффекта), #412 (имя
listCommentsв тексте сигнала — сразу camelCase).