feat(mcp): hide resolved-comment anchors + feed from the agent (#328)
The AI agent (MCP + in-app chat) saw ALL comments incl. resolved via two channels, cluttering its context and breaking fragment search. Default now: the agent sees only ACTIVE discussions; resolved is opt-in. Active anchors and threads are always kept. Channel 1 — resolved comment anchors on agent reads (converter option): `convertProseMirrorToMarkdown(content, options?)` gains `options.dropResolvedCommentAnchors` (default false — zero change for every existing caller incl. git-sync). Both `case "comment"` emitters (top-level and the raw-HTML inlineToHtml path) emit BARE text (no `<span data-comment-id>`) when `resolved && the flag`; active anchors keep their wrapper. mcp `getPage` passes the flag; `export_page_markdown` does NOT (lossless export must preserve resolved anchors — that is why it is an opt-in option, not unconditional); `get_page_json` is untouched (lossless PM JSON). Built on the #293 package converter. Channel 2 — `list_comments` default active-only: `listComments(pageId, includeResolved=false)` now returns `{ items, resolvedThreadsHidden }` (was a bare array). By default a RESOLVED top-level thread is hidden wholesale — the root AND every reply anchored to it (a thread is gated only by its root's resolvedAt; a resolved reply under an ACTIVE root stays). `resolvedThreadsHidden` counts hidden threads so the agent knows to re-query. `includeResolved:true` returns everything. The `includeResolved` param is added to both tool registrations (MCP index.ts + in-app ai-chat-tools.service.ts); `DocmostClientLike` signature updated. Server `findPageComments` is NOT touched — the web UI's tabs depend on the full feed; filtering is only at the mcp-client level. All internal call sites (export_page_markdown / checkNewComments / transformPage) updated to `.items` with `includeResolved:true` to keep their full-feed behavior. The comment model is assumed FLAT (a reply's parentCommentId points at the thread root) — documented in the filter; a future reply-of-reply model would need a root-walk there. Tests: resolved-comment-anchors.test.ts (6 — anchor dropped with flag / kept without, for BOTH emitters; active always kept); list-comments-resolved.test.mjs (4 — resolved thread+reply hidden + counter; includeResolved:true returns all; an ACTIVE thread with a RESOLVED reply is NOT hidden). package vitest: 664 passed; tsc clean. mcp: node --test 458 passed; tsc clean. apps/server + git-sync: tsc clean (converter option default-off). NOTE: based on feat/293-B (#293/#326 STEP 5) — the converter lives in the package; this PR is stacked on #333 and its base retargets to develop once #333 merges. mcp/build is gitignored (not committed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -649,13 +649,21 @@ export class AiChatToolsService {
|
||||
|
||||
listComments: tool({
|
||||
description:
|
||||
'List ALL comments on a page in one call, including RESOLVED ' +
|
||||
'threads — filter by resolvedAt when you need only open ones. ' +
|
||||
'Content is returned as Markdown.',
|
||||
'List comments on a page in one call. By DEFAULT only ACTIVE ' +
|
||||
'threads are returned; resolved threads (a resolved top-level ' +
|
||||
'comment and all its replies) are hidden and their count reported ' +
|
||||
'as `resolvedThreadsHidden` so you can re-query with ' +
|
||||
'`includeResolved: true` to see everything. Returns ' +
|
||||
'`{ items, resolvedThreadsHidden }`. Content is returned as Markdown.',
|
||||
inputSchema: modelFriendlyInput({
|
||||
pageId: z.string().describe('The id of the page.'),
|
||||
includeResolved: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('default only active threads; true — include resolved'),
|
||||
}),
|
||||
execute: async ({ pageId }) => await client.listComments(pageId),
|
||||
execute: async ({ pageId, includeResolved }) =>
|
||||
await client.listComments(pageId, includeResolved),
|
||||
}),
|
||||
|
||||
getComment: tool({
|
||||
|
||||
@@ -56,7 +56,12 @@ export interface DocmostClientLike {
|
||||
getPageJson(pageId: string): Promise<Record<string, unknown>>;
|
||||
getNode(pageId: string, nodeId: string): Promise<Record<string, unknown>>;
|
||||
getTable(pageId: string, tableRef: string): Promise<Record<string, unknown>>;
|
||||
listComments(pageId: string): Promise<unknown[]>;
|
||||
// Returns `{ items, resolvedThreadsHidden }`. DEFAULT (includeResolved unset/
|
||||
// false) hides resolved threads wholesale; pass true for the full feed.
|
||||
listComments(
|
||||
pageId: string,
|
||||
includeResolved?: boolean,
|
||||
): Promise<{ items: unknown[]; resolvedThreadsHidden: number }>;
|
||||
getComment(
|
||||
commentId: string,
|
||||
): Promise<{ data: Record<string, unknown>; success: boolean }>;
|
||||
|
||||
Reference in New Issue
Block a user