diff --git a/ui/goose2/src/features/chat/hooks/__tests__/useChat.attachments.test.ts b/ui/goose2/src/features/chat/hooks/__tests__/useChat.attachments.test.ts index da14807a07..d22e640e9d 100644 --- a/ui/goose2/src/features/chat/hooks/__tests__/useChat.attachments.test.ts +++ b/ui/goose2/src/features/chat/hooks/__tests__/useChat.attachments.test.ts @@ -95,12 +95,12 @@ describe("useChat attachments", () => { expect(mockAcpSendMessage).toHaveBeenCalledWith( "session-1", "Attached items:\n- [file] /tmp/report.pdf\n- [directory] /tmp/screenshots\nPlease review these", - { + expect.objectContaining({ systemPrompt: undefined, personaId: undefined, personaName: undefined, images: undefined, - }, + }), ); }); @@ -146,12 +146,12 @@ describe("useChat attachments", () => { expect(mockAcpSendMessage).toHaveBeenCalledWith( "session-1", "Attached items:\n- [image] diagram.png (image attached)\n ", - { + expect.objectContaining({ systemPrompt: undefined, personaId: undefined, personaName: undefined, images: [["abc123", "image/png"]], - }, + }), ); }); @@ -193,12 +193,12 @@ describe("useChat attachments", () => { expect(mockAcpSendMessage).toHaveBeenCalledWith( "session-1", "Attached items:\n- [file] /tmp/mobile-confirmation.html\n- [directory] /tmp/neighborhood block\n- [image] Screenshot 2026-04-09 at 1.25.32 PM.png (image attached)\ncan you see the attachments i attached?", - { + expect.objectContaining({ systemPrompt: undefined, personaId: undefined, personaName: undefined, images: [["abc123", "image/png"]], - }, + }), ); }); @@ -233,12 +233,12 @@ describe("useChat attachments", () => { expect(mockAcpSendMessage).toHaveBeenCalledWith( "session-1", "Attached items:\n- [file] report.pdf\nPlease review this", - { + expect.objectContaining({ systemPrompt: undefined, personaId: undefined, personaName: undefined, images: undefined, - }, + }), ); }); }); diff --git a/ui/goose2/src/features/chat/hooks/__tests__/useChat.test.ts b/ui/goose2/src/features/chat/hooks/__tests__/useChat.test.ts index bbe9009125..d46cdbef7b 100644 --- a/ui/goose2/src/features/chat/hooks/__tests__/useChat.test.ts +++ b/ui/goose2/src/features/chat/hooks/__tests__/useChat.test.ts @@ -127,12 +127,14 @@ describe("useChat", () => { result.current.stopGeneration(); }); - expect(mockAcpSendMessage).toHaveBeenCalledWith("session-1", "Hello", { - systemPrompt: undefined, - personaId: "persona-b", - personaName: "Persona B", - images: undefined, - }); + expect(mockAcpSendMessage).toHaveBeenCalledWith("session-1", "Hello", + expect.objectContaining({ + systemPrompt: undefined, + personaId: "persona-b", + personaName: "Persona B", + images: undefined, + }), + ); expect(mockAcpCancelSession).toHaveBeenCalledWith("session-1", "persona-b"); deferred.resolve(); @@ -313,23 +315,23 @@ describe("useChat", () => { 1, "session-1", "First", - { + expect.objectContaining({ systemPrompt: undefined, personaId: undefined, personaName: undefined, images: undefined, - }, + }), ); expect(mockAcpSendMessage).toHaveBeenNthCalledWith( 2, "session-2", "Second", - { + expect.objectContaining({ systemPrompt: undefined, personaId: undefined, personaName: undefined, images: undefined, - }, + }), ); deferred.resolve(); @@ -366,12 +368,14 @@ describe("useChat", () => { personaId: undefined, }); expect(mockAcpSetModel).toHaveBeenCalledWith("session-1", "gpt-4.1"); - expect(mockAcpSendMessage).toHaveBeenCalledWith("session-1", "Hello", { - systemPrompt: undefined, - personaId: undefined, - personaName: undefined, - images: undefined, - }); + expect(mockAcpSendMessage).toHaveBeenCalledWith("session-1", "Hello", + expect.objectContaining({ + systemPrompt: undefined, + personaId: undefined, + personaName: undefined, + images: undefined, + }), + ); }); it("appends an error message and removes the empty assistant placeholder when send fails", async () => { diff --git a/ui/goose2/src/features/chat/hooks/useChat.ts b/ui/goose2/src/features/chat/hooks/useChat.ts index 7db196b364..86736a00dd 100644 --- a/ui/goose2/src/features/chat/hooks/useChat.ts +++ b/ui/goose2/src/features/chat/hooks/useChat.ts @@ -244,6 +244,7 @@ export function useChat( (img) => [img.base64, img.mimeType] as [string, string], ), truncateBeforeMessageId, + messageId: userMessage.id, }); store.setChatState(sessionId, "idle"); diff --git a/ui/goose2/src/shared/api/acp.ts b/ui/goose2/src/shared/api/acp.ts index c2f23ee4e4..1d94f9089b 100644 --- a/ui/goose2/src/shared/api/acp.ts +++ b/ui/goose2/src/shared/api/acp.ts @@ -22,6 +22,10 @@ export interface AcpSendMessageOptions { * from both the display log and LLM context before appending the new prompt. * Used by edit and retry to rewind the conversation. */ truncateBeforeMessageId?: string; + /** Pre-generated message ID to use for this user message. When provided the + * same ID is registered with the notification handler and sent to the + * backend so the frontend store and backend thread_messages stay in sync. */ + messageId?: string; } export interface AcpPrepareSessionOptions { @@ -63,7 +67,7 @@ export async function acpSendMessage( ? { truncate_before_message_id: truncateBeforeMessageId } : undefined; - const messageId = crypto.randomUUID(); + const messageId = options.messageId ?? crypto.randomUUID(); setActiveMessageId(gooseSessionId, messageId); await directAcp.prompt(gooseSessionId, content, messageId, meta);