fix(desktop): arm mention-first agent prewake

Signed-off-by: Jitter <d14dfe033ef0f809866f9f984de04821b0d900d7652fd85a54776ee40ca3a68f@buzz.block.builderlab.xyz>
This commit is contained in:
Jitter
2026-08-26 17:19:55 +10:00
parent 2c66df82d3
commit 6efa205944
5 changed files with 45 additions and 9 deletions

View File

@@ -217,6 +217,7 @@ function MessageComposerImpl({
const syncAddressedAgentsFromTextRef = React.useRef<(text: string) => void>(
() => {},
);
const prepareMentionWakeRef = React.useRef<(text: string) => void>(() => {});
disabledRef.current = disabled;
isSendingRef.current = isSending;
isUploadingRef.current = media.isUploading;
@@ -274,6 +275,7 @@ function MessageComposerImpl({
onUpdate: ({ cursor, linkPreviewContent, text }) => {
trackAuthoredContent(text);
contentRef.current = text;
prepareMentionWakeRef.current(text);
setComposerContentFromText(text);
setPreviewContent(linkPreviewContent);
if (!isSubmitLockedRef.current && !editTargetRef.current) {
@@ -373,6 +375,7 @@ function MessageComposerImpl({
restoreQueuedAttachments: media.restoreQueuedAttachments,
setSpoileredAttachmentUrls,
});
prepareMentionWakeRef.current = mentionSendFlow.prepareMentionWake;
React.useEffect(() => {
onDeferredEditPendingChange?.(isDeferredEditPending);
return () => onDeferredEditPendingChange?.(false);

View File

@@ -116,9 +116,8 @@ export function useMentionSendFlow({
agents.map((agent) => [normalizePubkey(agent.pubkey), agent]),
);
}, [managedAgentsQuery.refetch, queryClient]);
useMentionWakePreflight({
const { prepareMentionWake } = useMentionWakePreflight({
channelId,
content: contentRef.current,
contentRef,
enabled:
mentionWakeEnabled &&
@@ -984,6 +983,7 @@ export function useMentionSendFlow({
onInvite: handleInviteNonMembers,
open: pendingNonMemberSend !== null,
},
prepareMentionWake,
sendMessageWithMentionFlow,
};
}

View File

@@ -72,6 +72,37 @@ test("wake plan rejects non-member managed agents", () => {
);
});
test("editor updates arm a mention-first draft without a rerender", async (t) => {
t.mock.timers.enable({ apis: ["setTimeout"] });
const { act, renderHook } = await import("@testing-library/react");
let starts = 0;
const contentRef = { current: "@Fizz " };
const view = renderHook(() =>
useMentionWakePreflight({
channelId: "general",
contentRef,
enabled: true,
expectedRelayUrl: "wss://relay.example",
expectedSignerPubkey: "c".repeat(64),
getDraftMentionRefs: () => [fizzRef],
getManagedAgentsByPubkey: async () =>
new Map([[AGENT, { pubkey: AGENT, status: "stopped" }]]),
isManagedAgentPubkey: () => true,
memberPubkeys: new Set([AGENT]),
startManagedAgent: async () => {
starts += 1;
return { pubkey: AGENT, status: "running" };
},
}),
);
contentRef.current = "@Fizz please investigate";
act(() => view.result.current.prepareMentionWake(contentRef.current));
await act(async () => t.mock.timers.tick(MENTION_WAKE_DELAY_MS));
assert.equal(starts, 1);
});
test("unmount prevents a wake after an in-flight lookup resolves", async (t) => {
t.mock.timers.enable({ apis: ["setTimeout"] });
const { act, renderHook } = await import("@testing-library/react");
@@ -84,7 +115,6 @@ test("unmount prevents a wake after an in-flight lookup resolves", async (t) =>
const view = renderHook(() =>
useMentionWakePreflight({
channelId: "general",
content: contentRef.current,
contentRef,
enabled: true,
expectedRelayUrl: "wss://relay.example",

View File

@@ -68,7 +68,6 @@ export function buildMentionWakePlan({
export function useMentionWakePreflight({
channelId,
content,
contentRef,
enabled,
expectedRelayUrl,
@@ -80,7 +79,6 @@ export function useMentionWakePreflight({
startManagedAgent,
}: {
channelId: string | null;
content: string;
contentRef: React.MutableRefObject<string>;
enabled: boolean;
expectedRelayUrl?: string;
@@ -196,10 +194,10 @@ export function useMentionWakePreflight({
// biome-ignore lint/correctness/useExhaustiveDependencies: contextRef keeps callbacks fresh; these inputs are plan invalidation signals.
React.useEffect(() => {
prepareMentionWake(content);
prepareMentionWake(contentRef.current);
}, [
channelId,
content,
contentRef,
enabled,
expectedRelayUrl,
expectedSignerPubkey,
@@ -210,4 +208,6 @@ export function useMentionWakePreflight({
]);
React.useEffect(() => cancelMentionWake, [cancelMentionWake]);
return { prepareMentionWake };
}

View File

@@ -2085,13 +2085,16 @@ test("substantive in-channel managed-agent mentions wake before send", async ({
"start_managed_agent",
);
const input = page.getByTestId("message-input");
await input.fill("Hey @fizz");
await input.fill("@fizz");
const dropdown = autocomplete(page);
await expect(dropdown.getByText("fizz")).toBeVisible();
await expect(dropdown.getByText("agent")).toBeVisible();
await input.press("Enter");
await page.keyboard.type(" can you help?");
await page.keyboard.type(
" Can you investigate this? Please report what you find.",
{ delay: 75 },
);
await expect
.poll(