diff --git a/packages/core/src/comments/anchors.ts b/packages/core/src/comments/anchors.ts index 99c1bc6ba5..57e6293fd4 100644 --- a/packages/core/src/comments/anchors.ts +++ b/packages/core/src/comments/anchors.ts @@ -59,6 +59,9 @@ export type CommentAnchor = z.infer; export const commentContextSchema = z.object({ anchor: commentAnchorSchema, threadState: z.enum(["resolved", "open"]).optional(), + // The task the commented resource belongs to. Artifact and canvas ids live in a run's + // JSON rather than a table, so the server can't get back to the task without being told. + taskId: z.string().optional(), }); export type CommentContext = z.infer; diff --git a/packages/ui/src/features/canvas/components/TaskCommentsList.tsx b/packages/ui/src/features/canvas/components/TaskCommentsList.tsx index 19d64f7c68..51c34d4984 100644 --- a/packages/ui/src/features/canvas/components/TaskCommentsList.tsx +++ b/packages/ui/src/features/canvas/components/TaskCommentsList.tsx @@ -123,6 +123,7 @@ function ResourceThreadRow({ thread, source, root, + taskId, members, selected, pulsing, @@ -132,13 +133,14 @@ function ResourceThreadRow({ thread: TaskCommentThread; source: CommentSource; root: ResourceComment; + taskId: string; members: UserBasic[]; selected: boolean; pulsing: boolean; resolution?: HighlightResolution; onOpen: () => void; }) { - const createComment = useCreateComment(source.target); + const createComment = useCreateComment(source.target, taskId); const setResolved = useSetCommentResolved(source.target); return ( @@ -278,7 +280,7 @@ export function TaskCommentsList({ const taskTarget = useMemo(() => taskCommentTarget(task.id), [task.id]); const taskSourceKey = commentTargetKey(taskTarget); - const createTaskComment = useCreateComment(taskTarget); + const createTaskComment = useCreateComment(taskTarget, task.id); const threads = useMemo(() => { const reviewByUrl = new Map(prReviews.byUrl); @@ -531,6 +533,7 @@ export function TaskCommentsList({ thread={thread} source={thread.origin.source} root={thread.origin.root} + taskId={task.id} members={members} selected={thread.id === focusedThreadId} pulsing={thread.id === pulseThreadId} diff --git a/packages/ui/src/features/sessions/components/ArtifactPreview.tsx b/packages/ui/src/features/sessions/components/ArtifactPreview.tsx index 2a9a1fe6ec..96c9c43394 100644 --- a/packages/ui/src/features/sessions/components/ArtifactPreview.tsx +++ b/packages/ui/src/features/sessions/components/ArtifactPreview.tsx @@ -108,7 +108,7 @@ export function ArtifactPreview({ ); const commentsQuery = useCommentsQuery(commentTarget); const { members } = useOrgMembers(); - const createComment = useCreateComment(commentTarget); + const createComment = useCreateComment(commentTarget, taskId); const requestCommentFocus = useCommentNavigationStore( (state) => state.requestCommentFocus, ); diff --git a/packages/ui/src/features/sessions/components/useComments.ts b/packages/ui/src/features/sessions/components/useComments.ts index 21b9d08637..874b7feeb4 100644 --- a/packages/ui/src/features/sessions/components/useComments.ts +++ b/packages/ui/src/features/sessions/components/useComments.ts @@ -133,15 +133,26 @@ export function useCommentsForTargetsQuery( }); } -export function useCreateComment(target: CommentTarget) { +/** + * @param taskId Names the task the resource belongs to, so a mention on it reaches that + * task's activity feed — the server can't resolve an artifact or canvas id back to a task. + */ +export function useCreateComment(target: CommentTarget, taskId?: string) { const service = useService(SESSION_SERVICE); const queryClient = useQueryClient(); const caches = commentCachesCoveringTarget(target); + const contextWithTask = (context: unknown) => + taskId ? { ...(context as Record), taskId } : context; return useMutation({ mutationFn: ( request: Omit, - ) => service.createResourceComment({ ...request, ...target }), + ) => + service.createResourceComment({ + ...request, + ...target, + context: contextWithTask(request.context), + }), onMutate: async (request) => { await queryClient.cancelQueries(caches); const previous = queryClient.getQueriesData(caches); @@ -151,7 +162,7 @@ export function useCreateComment(target: CommentTarget) { content: request.content, created_at: new Date().toISOString(), item_id: target.itemId, - item_context: request.context, + item_context: contextWithTask(request.context), scope: target.scope, source_comment: request.sourceCommentId ?? null, completed_at: null,