mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-26 18:31:34 +01:00
Set comment assignee from agent mentions
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
3bf8cfd127
commit
8b344b25cc
4 files changed
+57
No files matched your search
@@ -117,6 +117,7 @@ import {
|
||||
computeComposerHandoffPreview,
|
||||
extractAgentMentionIds,
|
||||
findPlainAgentNameCandidate,
|
||||
resolveMentionedAgentAssigneeValue,
|
||||
type HandoffAgentMention,
|
||||
} from "../lib/interrupt-handoff";
|
||||
import { restoreSubmittedCommentDraft } from "../lib/comment-submit-draft";
|
||||
@@ -3367,6 +3368,10 @@ const IssueChatComposer = forwardRef<IssueChatComposerHandle, IssueChatComposerP
|
||||
const composerContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
const draftTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const canAcceptFiles = Boolean(onImageUpload || onAttachImage);
|
||||
const reassignOptionIds = useMemo(
|
||||
() => new Set(reassignOptions.map((option) => option.id)),
|
||||
[reassignOptions],
|
||||
);
|
||||
|
||||
function queueViewportRestore(snapshot: ReturnType<typeof captureComposerViewportSnapshot>) {
|
||||
if (!snapshot) return;
|
||||
@@ -3408,6 +3413,16 @@ const IssueChatComposer = forwardRef<IssueChatComposerHandle, IssueChatComposerP
|
||||
setReassignTarget(effectiveSuggestedAssigneeValue);
|
||||
}, [effectiveSuggestedAssigneeValue]);
|
||||
|
||||
const firstMentionedAssigneeValue = useMemo(
|
||||
() => resolveMentionedAgentAssigneeValue(body, reassignOptionIds),
|
||||
[body, reassignOptionIds],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enableReassign || !firstMentionedAssigneeValue) return;
|
||||
setReassignTarget(firstMentionedAssigneeValue);
|
||||
}, [enableReassign, firstMentionedAssigneeValue]);
|
||||
|
||||
useEffect(() => {
|
||||
setPendingWorkMode(resolvedIssueWorkMode);
|
||||
}, [resolvedIssueWorkMode]);
|
||||
|
||||
@@ -107,6 +107,7 @@ import { InlineEntitySelector, type InlineEntityOption } from "./InlineEntitySel
|
||||
import { IssueThreadInteractionCardClassic } from "./IssueThreadInteractionCardClassic";
|
||||
import { AgentIcon } from "./AgentIconPicker";
|
||||
import { restoreSubmittedCommentDraft } from "../lib/comment-submit-draft";
|
||||
import { resolveMentionedAgentAssigneeValue } from "../lib/interrupt-handoff";
|
||||
import {
|
||||
captureComposerViewportSnapshot,
|
||||
restoreComposerViewportSnapshot,
|
||||
@@ -3311,6 +3312,10 @@ const IssueChatComposer = forwardRef<IssueChatComposerHandle, IssueChatComposerP
|
||||
const composerContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
const draftTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const canAcceptFiles = Boolean(onImageUpload || onAttachImage);
|
||||
const reassignOptionIds = useMemo(
|
||||
() => new Set(reassignOptions.map((option) => option.id)),
|
||||
[reassignOptions],
|
||||
);
|
||||
|
||||
function queueViewportRestore(snapshot: ReturnType<typeof captureComposerViewportSnapshot>) {
|
||||
if (!snapshot) return;
|
||||
@@ -3352,6 +3357,16 @@ const IssueChatComposer = forwardRef<IssueChatComposerHandle, IssueChatComposerP
|
||||
setReassignTarget(effectiveSuggestedAssigneeValue);
|
||||
}, [effectiveSuggestedAssigneeValue]);
|
||||
|
||||
const firstMentionedAssigneeValue = useMemo(
|
||||
() => resolveMentionedAgentAssigneeValue(body, reassignOptionIds),
|
||||
[body, reassignOptionIds],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enableReassign || !firstMentionedAssigneeValue) return;
|
||||
setReassignTarget(firstMentionedAssigneeValue);
|
||||
}, [enableReassign, firstMentionedAssigneeValue]);
|
||||
|
||||
useEffect(() => {
|
||||
setUnassignedConfirmed(false);
|
||||
}, [reassignTarget]);
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
extractAgentMentionIds,
|
||||
findPlainAgentNameCandidate,
|
||||
isOperatorInterruptedRun,
|
||||
resolveMentionedAgentAssigneeValue,
|
||||
resolveRunStatusPresentation,
|
||||
type PauseAffectsIssueLike,
|
||||
} from "./interrupt-handoff";
|
||||
@@ -61,6 +62,22 @@ describe("structured mention vs plain text", () => {
|
||||
expect(bodyHasAgentMention(`hey ${qaMention}`)).toBe(true);
|
||||
});
|
||||
|
||||
it("resolves the first available structured agent mention as the pending assignee", () => {
|
||||
const coderHref = buildAgentMentionHref("agent-coder-2222", null);
|
||||
const available = new Set([`agent:${QA_ID}`, "agent:agent-coder-2222"]);
|
||||
expect(
|
||||
resolveMentionedAgentAssigneeValue(
|
||||
`ask ${qaMention} and [@Coder](${coderHref})`,
|
||||
available,
|
||||
),
|
||||
).toBe(`agent:${QA_ID}`);
|
||||
});
|
||||
|
||||
it("does not resolve plain text or unavailable mentioned agents as pending assignees", () => {
|
||||
expect(resolveMentionedAgentAssigneeValue("ask QA to confirm", new Set([`agent:${QA_ID}`]))).toBeNull();
|
||||
expect(resolveMentionedAgentAssigneeValue(`ask ${qaMention}`, new Set(["agent:other"]))).toBeNull();
|
||||
});
|
||||
|
||||
it("treats a plain QA name as NOT a mention", () => {
|
||||
expect(bodyHasAgentMention("QA you take the screenshot")).toBe(false);
|
||||
expect(extractAgentMentionIds("ask QA to confirm")).toEqual([]);
|
||||
|
||||
@@ -105,6 +105,16 @@ export function bodyHasAgentMention(body: string): boolean {
|
||||
return extractAgentMentionIds(body).length > 0;
|
||||
}
|
||||
|
||||
export function resolveMentionedAgentAssigneeValue(
|
||||
body: string,
|
||||
availableAssigneeValues: ReadonlySet<string>,
|
||||
): string | null {
|
||||
const agentId = extractAgentMentionIds(body)[0];
|
||||
if (!agentId) return null;
|
||||
const assigneeValue = `agent:${agentId}`;
|
||||
return availableAssigneeValues.has(assigneeValue) ? assigneeValue : null;
|
||||
}
|
||||
|
||||
/** Strip every markdown link so chip labels/hrefs are not mistaken for plain text. */
|
||||
function plainTextOutsideLinks(body: string): string {
|
||||
return body.replace(MARKDOWN_LINK_RE, " ");
|
||||
|
||||
Reference in new issue
Block a user