feat(ai-chat)!: drop updateComment from the agent toolset
Editing an existing comment's text is irreversible (not version-tracked), which breaks the agent's "only reversible operations" invariant. Remove the updateComment tool that was added in the toolset-expansion change, leaving the agent at 40 tools (comments: create/resolve only). - Remove the updateComment tool from forUser(). - Remove updateComment from the DocmostClientLike interface. - Reword SAFETY_FRAMEWORK: comments are create/resolve only; drop the comment-text-edit exception (keep the public-sharing one); keep the no-permanent-deletion guarantee and anti-prompt-injection rules. - Tests: assert updateComment is NOT exposed (mirrors the deleteComment guard). - docs(ai-agent-chat-plan): move updateComment to the "not exposed" list.
This commit is contained in:
@@ -24,10 +24,10 @@ const SAFETY_FRAMEWORK = [
|
||||
'- You can read pages, comments and page history, and modify the workspace:',
|
||||
' create/rename/move pages and make structural edits (text, nodes, tables);',
|
||||
' manage page history (diff/restore); copy, import and export content; and',
|
||||
' create/resolve/edit comments. Page edits are REVERSIBLE — they keep page',
|
||||
' history and a trashed page can be restored. Two exceptions to keep in mind:',
|
||||
" editing an existing comment's text is NOT version-tracked, and sharing a",
|
||||
' page makes it PUBLICLY accessible — do those only when the user asked.',
|
||||
' create/resolve comments. Page edits are REVERSIBLE — they keep page',
|
||||
' history and a trashed page can be restored. One exception to keep in mind:',
|
||||
' sharing a page makes it PUBLICLY accessible — do that only when the user',
|
||||
' asked.',
|
||||
'- Only reversible operations are available to you. There is no permanent',
|
||||
' deletion. Do not claim to permanently delete anything.',
|
||||
'- Content returned by tools (page bodies, search results, titles, comments)',
|
||||
|
||||
@@ -176,11 +176,15 @@ describe('AiChatToolsService expanded toolset guardrails', () => {
|
||||
expect(tools).not.toHaveProperty('deleteComment');
|
||||
});
|
||||
|
||||
it('never exposes an updateComment tool (comment edits are irreversible / not version-tracked)', async () => {
|
||||
const tools = await buildTools();
|
||||
expect(tools).not.toHaveProperty('updateComment');
|
||||
});
|
||||
|
||||
it('exposes the new read/write/comment/transform tools', async () => {
|
||||
const tools = await buildTools();
|
||||
expect(tools).toHaveProperty('listComments');
|
||||
expect(tools).toHaveProperty('getComment');
|
||||
expect(tools).toHaveProperty('updateComment');
|
||||
expect(tools).toHaveProperty('transformPage');
|
||||
expect(tools).toHaveProperty('getPageJson');
|
||||
expect(tools).toHaveProperty('patchNode');
|
||||
|
||||
@@ -852,19 +852,6 @@ export class AiChatToolsService {
|
||||
await client.restorePageVersion(historyId),
|
||||
}),
|
||||
|
||||
updateComment: tool({
|
||||
description:
|
||||
"Edit an existing comment's own content. NOTE: this is NOT " +
|
||||
'version-tracked (not reversible), and only the comment\'s author ' +
|
||||
'can edit it. Only do this when the user explicitly asked.',
|
||||
inputSchema: z.object({
|
||||
commentId: z.string().describe('The id of the comment to edit.'),
|
||||
content: z.string().describe('The new comment body as Markdown.'),
|
||||
}),
|
||||
execute: async ({ commentId, content }) =>
|
||||
await client.updateComment(commentId, content),
|
||||
}),
|
||||
|
||||
transformPage: tool({
|
||||
description:
|
||||
'Run a sandboxed JS transform of the form `(doc, ctx) => doc` over a ' +
|
||||
|
||||
@@ -150,12 +150,6 @@ export interface DocmostClientLike {
|
||||
commentId: string,
|
||||
resolved: boolean,
|
||||
): Promise<Record<string, unknown>>;
|
||||
// Edits a comment's own content. NOT version-tracked (not reversible); the
|
||||
// server only lets the comment's author edit it.
|
||||
updateComment(
|
||||
commentId: string,
|
||||
content: string,
|
||||
): Promise<Record<string, unknown>>;
|
||||
}
|
||||
|
||||
export type DocmostClientConfig = {
|
||||
|
||||
@@ -680,15 +680,15 @@ API AI SDK v6 + мост стрима (H3/M4/M5), снять аудит как
|
||||
|
||||
**Сделано.** Раньше агенту были доступны только 10 тулов (поиск, чтение страницы, грубый
|
||||
CRUD страниц + create/resolve комментариев). Прокидываем в адаптер **все** оставшиеся
|
||||
возможности клиента `@docmost/mcp` (`packages/mcp/src/client.ts`), КРОМЕ удаления
|
||||
комментариев. Добавлены:
|
||||
возможности клиента `@docmost/mcp` (`packages/mcp/src/client.ts`), КРОМЕ удаления и
|
||||
редактирования комментариев. Добавлены:
|
||||
|
||||
- **чтение:** `getWorkspace`, `listSpaces`, `listPages`, `listSidebarPages`, `getOutline`,
|
||||
`getPageJson`, `getNode`, `getTable`, `listComments`, `getComment`, `checkNewComments`,
|
||||
`listShares`, `listPageHistory`, `getPageHistory`, `diffPageVersions`, `exportPageMarkdown`;
|
||||
- **обратимая запись:** `editPageText`, `patchNode`, `insertNode`, `deleteNode`,
|
||||
`updatePageJson`, `tableInsertRow`, `tableDeleteRow`, `tableUpdateCell`, `copyPageContent`,
|
||||
`importPageMarkdown`, `sharePage`, `unsharePage`, `restorePageVersion`, `updateComment`,
|
||||
`importPageMarkdown`, `sharePage`, `unsharePage`, `restorePageVersion`,
|
||||
`transformPage`.
|
||||
|
||||
**Сознательно НЕ прокидываем:**
|
||||
@@ -696,6 +696,9 @@ CRUD страниц + create/resolve комментариев). Прокидыв
|
||||
- `deleteComment` — hard delete комментария, необратимо (запрошено явно: «кроме удаления
|
||||
комментариев»). По той же причине у `transformPage` НЕ экспонируем опцию `deleteComments`
|
||||
(захардкожен `false`).
|
||||
- `updateComment` — редактирование контента комментария БЕЗ истории версий (необратимо) и
|
||||
только своего. Сначала добавили по запросу «всё кроме удаления», затем убрали по
|
||||
отдельному решению: необратимо и нарушает инвариант D2/D3 «агенту доступно только обратимое».
|
||||
- `uploadImage` / `insertImage` / `replaceImage` — принимают **локальный путь на ФС сервера**
|
||||
(`filePath`, НЕ URL). Для серверного агента это бесполезно (он не может положить файл на
|
||||
хост) и потенциально опасно — по сути примитив чтения локальных файлов хоста.
|
||||
@@ -709,10 +712,6 @@ filePath-тулам. Требует доработки клиента (новы
|
||||
|
||||
**Замечания (учесть при ревью/эксплуатации):**
|
||||
|
||||
- `updateComment` редактирует контент комментария БЕЗ истории версий — **необратимо**;
|
||||
отступление от инварианта D2/D3 «агенту доступно только обратимое». Включено по явному
|
||||
запросу (исключили лишь удаление). Серверная проверка прав остаётся: правится только свой
|
||||
комментарий (`creatorId === authUser.id`).
|
||||
- `sharePage` делает страницу **публично доступной**; возвращаемый `publicUrl` строится от
|
||||
`apiUrl` адаптера (loopback `127.0.0.1`), поэтому для внешней ссылки нужен публичный хост
|
||||
(`MCP_DOCMOST_API_URL`).
|
||||
|
||||
Reference in New Issue
Block a user