feat(ai-chat): дать in-app агенту insertFootnote / insertImage / replaceImage — исследователь сыплет литеральные ^[...] вместо сносок
#410
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?
Контекст (инцидент)
Страница «Иранские языки: исследование» (написана ролью «Исследователь») содержит 40 литеральных вставок
^[...]в теле параграфов и ни одной настоящей сноски (нетfootnotesListвнизу). Пример из хранимого узла:Диагноз (проверено эмпирически на живом сервере)
Синтаксис
^[...]в промпте роли — правильный. Проблема в пути записи:^[...]→ сноска?createPage/updatePageContent/importPageMarkdown(целое markdown-тело →markdownToProseMirrorCanonical)[^id]insertNode/patchNode/updatePageJson(готовые PM-узлы)editPageText(plain-text find/replace, markdown не парсится по контракту)Промпт исследователя (
agent-roles-catalog/bundles/research/*.yaml) требует инкрементальной работы («документ — рабочая память», flush cadence) → агент пишет черезinsertNode/editPageText→ сноски не материализуются. Усугубляется тирами:editPageTextв core, а footnote-способныйupdatePageContent— в deferred (apps/server/src/core/ai-chat/tools/tool-tiers.ts).Точечный инструмент для сноски уже существует, но только на внешнем MCP:
insert_footnote(packages/mcp/src/index.ts:828→client.insertFootnote,packages/mcp/src/client.ts:1738). В тулсете in-app агента (apps/server/src/core/ai-chat/tools/ai-chat-tools.service.ts, 42 тула) его нет; вSHARED_TOOL_SPECS(packages/mcp/src/tool-specs.ts) — тоже. Комментарий в index.ts прямо фиксирует это как «MCP-only by design (#294)» — этот дизайн и есть баг.Аналогично агенту недоступны любые операции с картинками:
insert_image/replace_imageесть только на MCP (client-методы уже URL-based:insertImage(pageId, url, opts)@ client.ts:3140,replaceImage(pageId, oldAttachmentId, url, opts)@ client.ts:3280).Решено (владелец):
updateComment/deleteCommentагенту НЕ добавлять.Что сделать
packages/mcp/src/tool-specs.ts— добавить вSHARED_TOOL_SPECSтри спеки (схема+описание переносятся из inline-регистраций index.ts):insertFootnote(pageId, anchorText, text)insertImage(pageId, url, align?/alt?/replaceText?/afterText?)replaceImage(pageId, oldAttachmentId, url, align?/alt?)packages/mcp/src/index.ts— заменить три inlineserver.registerTool(...)наregisterShared(SHARED_TOOL_SPECS.*); убрать комментарии «MCP-only by design».apps/server/src/core/ai-chat/tools/ai-chat-tools.service.ts— зарегистрировать три тула черезsharedTool(...)→ существующиеclient.insertFootnote/client.insertImage/client.replaceImage.apps/server/src/core/ai-chat/tools/tool-tiers.ts— тир-записи:insertFootnote→ core (обязательно: иначе повторяется исходная асимметрия — footnote-тул спрятан, аeditPageTextпод рукой);insertImage/replaceImage— по вкусу (предлагаю deferred).agent-roles-catalog/bundles/research/ru.yaml+en.yaml, секция FOOTNOTES — дописать правило:^[...]парсится ТОЛЬКО при записи целым markdown-телом; для точечной цитаты к уже написанному тексту —insertFootnote(anchorText, text); вeditPageText/insertNode^[...]оседает литералом.shared-tool-specs.contract.spec.ts), тиры (tool-tiers.spec.ts), сервис (ai-chat-tools.service.spec.ts).Опционально (защита от повторения)
Гардрейл в
editPageText:replace, содержащий токен^[...], отклонять вfailed[]с подсказкой «add footnotes via insertFootnote» — по образцу уже существующего отклонения форматных маркеров (**bold**/~~strike~~).Критерии приёмки
insertFootnote, без перезаписи тела.insertFootnoteвиден агенту в core-тире.