refactor(api): extract assistant comments slice

This commit is contained in:
2026-03-30 22:29:07 +02:00
parent 73fdf1c6ab
commit ab32c7804b
3 changed files with 213 additions and 143 deletions
+13 -142
View File
@@ -8,8 +8,6 @@ import {
CreateAssignmentSchema,
AllocationStatus,
EstimateStatus,
type CommentEntityType,
COMMENT_ENTITY_TYPE_VALUES,
PermissionKey,
SystemRole,
} from "@capakraken/shared";
@@ -129,6 +127,11 @@ import {
createDashboardInsightsReportsExecutors,
dashboardInsightsReportsToolDefinitions,
} from "./assistant-tools/dashboard-insights-reports.js";
import {
commentMutationToolDefinitions,
commentReadToolDefinitions,
createCommentExecutors,
} from "./assistant-tools/comments.js";
import {
withToolAccess,
type ToolAccessRequirements,
@@ -136,7 +139,6 @@ import {
type ToolDef,
type ToolExecutor,
} from "./assistant-tools/shared.js";
import { getCommentToolEntityDescription, getCommentToolScopeSentence } from "../lib/comment-entity-registry.js";
export type { ToolContext } from "./assistant-tools/shared.js";
@@ -2319,22 +2321,7 @@ export const TOOL_DEFINITIONS: ToolDef[] = withToolAccess([
// ── TASK MANAGEMENT ──
...notificationTaskToolDefinitions,
{
type: "function",
function: {
name: "list_comments",
description: `List comments (with replies) for a supported comment-enabled entity. ${getCommentToolScopeSentence()} Entity visibility is required.`,
parameters: {
type: "object",
properties: {
entityType: { type: "string", enum: [...COMMENT_ENTITY_TYPE_VALUES], description: getCommentToolEntityDescription() },
entityId: { type: "string", description: "Entity ID" },
},
required: ["entityType", "entityId"],
},
},
},
...commentReadToolDefinitions,
{
type: "function",
function: {
@@ -2399,37 +2386,7 @@ export const TOOL_DEFINITIONS: ToolDef[] = withToolAccess([
},
},
},
{
type: "function",
function: {
name: "create_comment",
description: `Add a comment to a supported comment-enabled entity. ${getCommentToolScopeSentence()} Entity visibility is required. Supports @mentions. Always confirm with the user first.`,
parameters: {
type: "object",
properties: {
entityType: { type: "string", enum: [...COMMENT_ENTITY_TYPE_VALUES], description: getCommentToolEntityDescription() },
entityId: { type: "string", description: "Entity ID" },
body: { type: "string", description: "Comment body text. Use @[Name](userId) for mentions." },
},
required: ["entityType", "entityId", "body"],
},
},
},
{
type: "function",
function: {
name: "resolve_comment",
description: `Mark a comment as resolved (or unresolve it) on a supported comment-enabled entity. ${getCommentToolScopeSentence()} Entity visibility is required, and only the comment author or an admin can change resolution.`,
parameters: {
type: "object",
properties: {
commentId: { type: "string", description: "Comment ID to resolve" },
resolved: { type: "boolean", description: "Set to true to resolve, false to unresolve. Default: true" },
},
required: ["commentId"],
},
},
},
...commentMutationToolDefinitions,
{
type: "function",
function: {
@@ -2861,6 +2818,12 @@ const executors = {
createReportCaller,
createScopedCallerContext,
}),
...createCommentExecutors({
createCommentCaller,
createScopedCallerContext,
toAssistantCommentCreationError,
toAssistantCommentResolveError,
}),
async search_estimates(params: {
projectCode?: string; query?: string; status?: string; limit?: number;
@@ -3324,30 +3287,6 @@ const executors = {
toAssistantNotificationCreationError,
}),
async list_comments(params: { entityType: CommentEntityType; entityId: string }, ctx: ToolContext) {
const caller = createCommentCaller(createScopedCallerContext(ctx));
const comments = await caller.list({
entityType: params.entityType,
entityId: params.entityId,
});
return comments.map((c) => ({
id: c.id,
author: c.author.name ?? c.author.email,
body: c.body,
resolved: c.resolved,
createdAt: c.createdAt.toISOString(),
replyCount: c.replies.length,
replies: c.replies.map((r) => ({
id: r.id,
author: r.author.name ?? r.author.email,
body: r.body,
resolved: r.resolved,
createdAt: r.createdAt.toISOString(),
})),
}));
},
async lookup_rate(params: {
clientId?: string;
chapter?: string;
@@ -3425,74 +3364,6 @@ const executors = {
return caller.generateProjectNarrative({ projectId: params.projectId });
},
async create_comment(params: {
entityType: CommentEntityType;
entityId: string;
body: string;
}, ctx: ToolContext) {
if (params.body.length === 0) {
return { error: "Comment body is required." };
}
if (params.body.length > 10_000) {
return { error: "Comment body must be at most 10000 characters." };
}
const caller = createCommentCaller(createScopedCallerContext(ctx));
let comment;
try {
comment = await caller.create({
entityType: params.entityType,
entityId: params.entityId,
body: params.body,
});
} catch (error) {
const mapped = toAssistantCommentCreationError(error);
if (mapped) {
return mapped;
}
throw error;
}
return {
__action: "invalidate",
scope: ["comment"],
id: comment.id,
author: comment.author.name ?? comment.author.email,
body: comment.body,
createdAt: comment.createdAt.toISOString(),
};
},
async resolve_comment(params: {
commentId: string;
resolved?: boolean;
}, ctx: ToolContext) {
const caller = createCommentCaller(createScopedCallerContext(ctx));
let updated;
try {
updated = await caller.resolve({
id: params.commentId,
resolved: params.resolved !== false,
});
} catch (error) {
const mapped = toAssistantCommentResolveError(error);
if (mapped) {
return mapped;
}
throw error;
}
return {
__action: "invalidate",
scope: ["comment"],
id: updated.id,
resolved: updated.resolved,
author: updated.author.name ?? updated.author.email,
body: updated.body.slice(0, 100),
};
},
async query_change_history(params: {
entityType?: string;
search?: string;