mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 04:15:48 -06:00
6dc77a37d9
* refactor(agents): centralize failover user copy * refactor(agents): route failure callers through user copy * refactor(qa): carry typed reply failure markers * refactor(agents): keep failover copy import-light * refactor(agents): pass structured failure copy context * refactor(agents): isolate copy rendering from runtime state * refactor(agents): separate copy rendering from sanitization * refactor(agents): keep failover copy internals private * fix(qa-channel): type failure markers on bus sends * style(agents): brace failover copy conditions * test(qa-lab): avoid map spread in failure cases * chore(plugin-sdk): refresh failover closure hashes * fix(agents): preserve generic runner fallback * fix(qa): preserve text-only failure markers * test(qa): type failure delivery fixture
92 lines
3.2 KiB
TypeScript
92 lines
3.2 KiB
TypeScript
// Qa Channel plugin module implements outbound behavior.
|
|
import { randomUUID } from "node:crypto";
|
|
import path from "node:path";
|
|
import {
|
|
loadOutboundMediaFromUrl,
|
|
type OutboundMediaLoadOptions,
|
|
} from "openclaw/plugin-sdk/outbound-media";
|
|
import { resolveQaChannelAccount } from "./accounts.js";
|
|
import { buildQaTarget, resolveQaTargetThread, sendQaBusMessage } from "./bus-client.js";
|
|
import type { QaBusAttachment } from "./protocol.js";
|
|
import type { CoreConfig } from "./types.js";
|
|
|
|
type QaChannelTextSendParams = {
|
|
cfg: CoreConfig;
|
|
accountId?: string | null;
|
|
to: string;
|
|
text: string;
|
|
isError?: boolean;
|
|
threadId?: string | number | null;
|
|
replyToId?: string | number | null;
|
|
attachments?: QaBusAttachment[];
|
|
};
|
|
|
|
type QaChannelMediaAccessParams = {
|
|
mediaAccess?: OutboundMediaLoadOptions["mediaAccess"];
|
|
mediaLocalRoots?: readonly string[];
|
|
mediaReadFile?: (filePath: string) => Promise<Buffer>;
|
|
};
|
|
|
|
export async function sendQaChannelText(params: QaChannelTextSendParams) {
|
|
const account = resolveQaChannelAccount({ cfg: params.cfg, accountId: params.accountId });
|
|
const resolved = resolveQaTargetThread({ target: params.to, threadId: params.threadId });
|
|
const parsed = resolved.target;
|
|
const { message } = await sendQaBusMessage({
|
|
baseUrl: account.baseUrl,
|
|
accountId: account.accountId,
|
|
to: buildQaTarget({
|
|
chatType: parsed.chatType,
|
|
conversationId: parsed.conversationId,
|
|
threadId: resolved.threadId,
|
|
}),
|
|
text: params.text,
|
|
isError: params.isError,
|
|
senderId: account.botUserId,
|
|
senderName: account.botDisplayName,
|
|
threadId: resolved.threadId,
|
|
replyToId: params.replyToId == null ? undefined : String(params.replyToId),
|
|
...(params.attachments?.length ? { attachments: params.attachments } : {}),
|
|
});
|
|
return {
|
|
to: params.to,
|
|
messageId: message.id,
|
|
};
|
|
}
|
|
|
|
/** Resolve every attachment first so a failed batch cannot publish a partial reply. */
|
|
export async function sendQaChannelMediaBatch(
|
|
params: QaChannelTextSendParams & QaChannelMediaAccessParams & { mediaUrls: readonly string[] },
|
|
) {
|
|
if (params.mediaUrls.length === 0) {
|
|
throw new Error("QA channel media batch requires at least one media URL");
|
|
}
|
|
const attachments: QaBusAttachment[] = await Promise.all(
|
|
params.mediaUrls.map(async (mediaUrl) => {
|
|
const media = await loadOutboundMediaFromUrl(mediaUrl, {
|
|
mediaAccess: params.mediaAccess,
|
|
mediaLocalRoots: params.mediaLocalRoots,
|
|
mediaReadFile: params.mediaReadFile,
|
|
optimizeImages: false,
|
|
});
|
|
const kind =
|
|
media.kind === "image" || media.kind === "video" || media.kind === "audio"
|
|
? media.kind
|
|
: "file";
|
|
return {
|
|
id: randomUUID(),
|
|
kind,
|
|
mimeType: media.contentType ?? "application/octet-stream",
|
|
fileName: media.fileName ?? path.basename(mediaUrl),
|
|
contentBase64: media.buffer.toString("base64"),
|
|
};
|
|
}),
|
|
);
|
|
return await sendQaChannelText({ ...params, attachments });
|
|
}
|
|
|
|
export async function sendQaChannelMedia(
|
|
params: QaChannelTextSendParams & QaChannelMediaAccessParams & { mediaUrl: string },
|
|
) {
|
|
return await sendQaChannelMediaBatch({ ...params, mediaUrls: [params.mediaUrl] });
|
|
}
|