diff --git a/extensions/buzz/src/qa/adapter.runtime.test.ts b/extensions/buzz/src/qa/adapter.runtime.test.ts index 45914366312c..c13cbdb4db7c 100644 --- a/extensions/buzz/src/qa/adapter.runtime.test.ts +++ b/extensions/buzz/src/qa/adapter.runtime.test.ts @@ -1,6 +1,7 @@ -import type { - QaBusInboundMessageInput, - QaBusMessage, +import { + parseQaTarget, + type QaBusInboundMessageInput, + type QaBusMessage, } from "openclaw/plugin-sdk/qa-channel-protocol"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { parseBuzzQaCredentialPayload } from "./credentials.js"; @@ -179,7 +180,13 @@ describe("Buzz QA transport adapter", () => { ...input, id: `bus-outbound-${++outboundIndex}`, direction: "outbound" as const, - conversation: { id: "main", kind: "group" as const }, + conversation: (() => { + const target = parseQaTarget(input.to); + return { + id: target.conversationId, + kind: target.chatType, + }; + })(), })); sendMessage .mockResolvedValueOnce({ eventId: "native-root", timestamp: 1_750_000_000_000 }) @@ -199,7 +206,7 @@ describe("Buzz QA transport adapter", () => { const root = await adapter.sendInbound({ accountId: "sut", - conversation: { id: "main", kind: "group" }, + conversation: { id: "main", kind: "channel" }, senderId: "driver", senderName: "QA Driver", text: "@openclaw root", @@ -216,7 +223,7 @@ describe("Buzz QA transport adapter", () => { }); const followUp = await adapter.sendInbound({ accountId: "sut", - conversation: { id: "main", kind: "group" }, + conversation: { id: "main", kind: "channel" }, senderId: "driver", senderName: "QA Driver", text: "@openclaw follow-up", @@ -240,6 +247,7 @@ describe("Buzz QA transport adapter", () => { }); expect(addOutboundMessage).toHaveBeenLastCalledWith( expect.objectContaining({ + to: "channel:main", threadId: root.id, replyToId: followUp.id, }), diff --git a/extensions/buzz/src/qa/adapter.runtime.ts b/extensions/buzz/src/qa/adapter.runtime.ts index 772d56db1e33..40bd225272c2 100644 --- a/extensions/buzz/src/qa/adapter.runtime.ts +++ b/extensions/buzz/src/qa/adapter.runtime.ts @@ -1,5 +1,6 @@ import { setTimeout as sleep } from "node:timers/promises"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; +import type { QaBusConversationKind } from "openclaw/plugin-sdk/qa-channel-protocol"; import type { QaRunnerCliRegistration } from "openclaw/plugin-sdk/qa-runner-runtime"; import type { BuzzInboundMessage } from "../message-event.js"; import { buildBuzzTarget } from "../target.js"; @@ -90,7 +91,10 @@ export async function createBuzzQaTransportAdapter( const accountId = options.sutAccountId?.trim() || "sut"; const nativeMessageIds = new Map(); const busMessageIds = new Map(); + // QA scenarios own the logical target while Buzz uses one physical group room. + // Preserve both logical fields so outbound matching sees the conversation it sent. let logicalConversationId = credentials.roomId; + let logicalConversationKind: QaBusConversationKind = "group"; let relayDriver: Awaited>; const resolveBusMessageId = async (nativeId: string | undefined) => { @@ -118,7 +122,7 @@ export async function createBuzzQaTransportAdapter( ]); const outbound = await context.messages.addOutboundMessage({ accountId, - to: `group:${logicalConversationId}`, + to: `${logicalConversationKind}:${logicalConversationId}`, senderId: credentials.sutPublicKey, text: message.text, timestamp: message.createdAt * 1_000, @@ -154,6 +158,7 @@ export async function createBuzzQaTransportAdapter( heartbeat.throwIfFailed(); relayDriver.assertHealthy(); logicalConversationId = input.conversation.id; + logicalConversationKind = input.conversation.kind; const sent = await relayDriver.sendMessage({ text: input.text, mentionSut: isBuzzMention(input.text), @@ -172,6 +177,7 @@ export async function createBuzzQaTransportAdapter( }, resetTransport() { logicalConversationId = credentials.roomId; + logicalConversationKind = "group"; nativeMessageIds.clear(); busMessageIds.clear(); },