feat(ai-chat): дать in-app агенту insertFootnote / insertImage / replaceImage — исследователь сыплет литеральные ^[...] вместо сносок #410

Closed
opened 2026-07-07 20:53:09 +03:00 by agent_vscode · 0 comments
Collaborator

Контекст (инцидент)

Страница «Иранские языки: исследование» (написана ролью «Исследователь») содержит 40 литеральных вставок ^[...] в теле параграфов и ни одной настоящей сноски (нет footnotesList внизу). Пример из хранимого узла:

…фонетические различия^[Iranica, «Writing Systems» (Skjærvø)]. …письмо брахми^[Там же].

Диагноз (проверено эмпирически на живом сервере)

Синтаксис ^[...] в промпте роли — правильный. Проблема в пути записи:

Путь записи ^[...] → сноска?
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:828client.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 агенту НЕ добавлять.

Что сделать

  1. 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?)
  2. packages/mcp/src/index.ts — заменить три inline server.registerTool(...) на registerShared(SHARED_TOOL_SPECS.*); убрать комментарии «MCP-only by design».
  3. apps/server/src/core/ai-chat/tools/ai-chat-tools.service.ts — зарегистрировать три тула через sharedTool(...) → существующие client.insertFootnote / client.insertImage / client.replaceImage.
  4. apps/server/src/core/ai-chat/tools/tool-tiers.ts — тир-записи: insertFootnotecore (обязательно: иначе повторяется исходная асимметрия — footnote-тул спрятан, а editPageText под рукой); insertImage/replaceImage — по вкусу (предлагаю deferred).
  5. agent-roles-catalog/bundles/research/ru.yaml + en.yaml, секция FOOTNOTES — дописать правило: ^[...] парсится ТОЛЬКО при записи целым markdown-телом; для точечной цитаты к уже написанному тексту — insertFootnote(anchorText, text); в editPageText/insertNode ^[...] оседает литералом.
  6. Тесты: контрактные (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-тире.
  • Агент может вставить/заменить картинку по URL.
  • Существующее поведение MCP-тулов не изменено (имена — в рамках отдельного issue про camelCase).
## Контекст (инцидент) Страница «Иранские языки: исследование» (написана ролью «Исследователь») содержит **40 литеральных вставок `^[...]`** в теле параграфов и **ни одной настоящей сноски** (нет `footnotesList` внизу). Пример из хранимого узла: ``` …фонетические различия^[Iranica, «Writing Systems» (Skjærvø)]. …письмо брахми^[Там же]. ``` ## Диагноз (проверено эмпирически на живом сервере) Синтаксис `^[...]` в промпте роли — правильный. Проблема в **пути записи**: | Путь записи | `^[...]` → сноска? | |---|---| | `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` агенту НЕ добавлять. ## Что сделать 1. **`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?) 2. **`packages/mcp/src/index.ts`** — заменить три inline `server.registerTool(...)` на `registerShared(SHARED_TOOL_SPECS.*)`; убрать комментарии «MCP-only by design». 3. **`apps/server/src/core/ai-chat/tools/ai-chat-tools.service.ts`** — зарегистрировать три тула через `sharedTool(...)` → существующие `client.insertFootnote` / `client.insertImage` / `client.replaceImage`. 4. **`apps/server/src/core/ai-chat/tools/tool-tiers.ts`** — тир-записи: `insertFootnote` → **core** (обязательно: иначе повторяется исходная асимметрия — footnote-тул спрятан, а `editPageText` под рукой); `insertImage`/`replaceImage` — по вкусу (предлагаю deferred). 5. **`agent-roles-catalog/bundles/research/ru.yaml` + `en.yaml`**, секция FOOTNOTES — дописать правило: `^[...]` парсится ТОЛЬКО при записи целым markdown-телом; для точечной цитаты к уже написанному тексту — `insertFootnote(anchorText, text)`; в `editPageText`/`insertNode` `^[...]` оседает литералом. 6. Тесты: контрактные (`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-тире. - Агент может вставить/заменить картинку по URL. - Существующее поведение MCP-тулов не изменено (имена — в рамках отдельного issue про camelCase).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#410