Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
414
extensions/qa-channel/src/inbound.ts
Normal file
414
extensions/qa-channel/src/inbound.ts
Normal file
@@ -0,0 +1,414 @@
|
||||
// Qa Channel plugin module implements inbound behavior.
|
||||
import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
||||
import { resolveNativeCommandSessionTargets } from "openclaw/plugin-sdk/command-auth-native";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { resolveInboundRouteEnvelopeBuilderWithRuntime } from "openclaw/plugin-sdk/inbound-envelope";
|
||||
import {
|
||||
buildAgentMediaPayload,
|
||||
saveMediaBuffer,
|
||||
saveMediaSource,
|
||||
} from "openclaw/plugin-sdk/media-runtime";
|
||||
import {
|
||||
sanitizeQaBusToolCallArguments,
|
||||
type QaBusToolCall,
|
||||
} from "openclaw/plugin-sdk/qa-channel-protocol";
|
||||
import {
|
||||
buildQaTarget,
|
||||
deleteQaBusMessage,
|
||||
editQaBusMessage,
|
||||
sendQaBusMessage,
|
||||
type QaBusMessage,
|
||||
} from "./bus-client.js";
|
||||
import { getQaChannelRuntime } from "./runtime.js";
|
||||
import type { CoreConfig, ResolvedQaChannelAccount } from "./types.js";
|
||||
|
||||
export function isHttpMediaUrl(value: string): boolean {
|
||||
try {
|
||||
const parsed = new URL(value);
|
||||
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeBase64ForCompare(value: string): string {
|
||||
return value.replace(/=+$/u, "").replace(/-/gu, "+").replace(/_/gu, "/");
|
||||
}
|
||||
|
||||
function decodeAttachmentBase64(value: string): Buffer | null {
|
||||
const buffer = Buffer.from(value, "base64");
|
||||
if (normalizeBase64ForCompare(buffer.toString("base64")) !== normalizeBase64ForCompare(value)) {
|
||||
return null;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
async function resolveQaInboundMediaPayload(attachments: QaBusMessage["attachments"]) {
|
||||
if (!Array.isArray(attachments) || attachments.length === 0) {
|
||||
return {};
|
||||
}
|
||||
const mediaList: Array<{ path: string; contentType?: string | null }> = [];
|
||||
for (const attachment of attachments) {
|
||||
if (!attachment?.mimeType) {
|
||||
continue;
|
||||
}
|
||||
if (typeof attachment.contentBase64 === "string" && attachment.contentBase64.trim()) {
|
||||
const buffer = decodeAttachmentBase64(attachment.contentBase64);
|
||||
if (!buffer) {
|
||||
console.warn("[qa-channel] inbound attachment contentBase64 rejected (invalid base64)");
|
||||
continue;
|
||||
}
|
||||
const saved = await saveMediaBuffer(
|
||||
buffer,
|
||||
attachment.mimeType,
|
||||
"inbound",
|
||||
undefined,
|
||||
attachment.fileName,
|
||||
);
|
||||
mediaList.push({
|
||||
path: saved.path,
|
||||
contentType: saved.contentType,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (typeof attachment.url === "string" && attachment.url.trim()) {
|
||||
if (!isHttpMediaUrl(attachment.url)) {
|
||||
console.warn(
|
||||
`[qa-channel] inbound attachment URL rejected (non-http scheme): ${attachment.url}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const saved = await saveMediaSource(attachment.url, undefined, "inbound");
|
||||
mediaList.push({
|
||||
path: saved.path,
|
||||
contentType: saved.contentType,
|
||||
});
|
||||
}
|
||||
}
|
||||
return mediaList.length > 0 ? buildAgentMediaPayload(mediaList) : {};
|
||||
}
|
||||
|
||||
function resolveQaGroupConfig(params: {
|
||||
account: ResolvedQaChannelAccount;
|
||||
conversationId: string;
|
||||
target: string;
|
||||
}) {
|
||||
const groups = params.account.config.groups;
|
||||
return groups?.[params.conversationId] ?? groups?.[params.target] ?? groups?.["*"];
|
||||
}
|
||||
|
||||
function formatQaErrorForLog(error: unknown): string {
|
||||
let escaped = "";
|
||||
const message = formatErrorMessage(error) || Object.prototype.toString.call(error);
|
||||
for (const character of message) {
|
||||
const codePoint = character.codePointAt(0) ?? 0;
|
||||
const isControl = codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f);
|
||||
const isLineSeparator = codePoint === 0x2028 || codePoint === 0x2029;
|
||||
escaped +=
|
||||
isControl || isLineSeparator ? `\\u${codePoint.toString(16).padStart(4, "0")}` : character;
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
|
||||
function createQaReplyPreview(params: {
|
||||
account: ResolvedQaChannelAccount;
|
||||
inbound: QaBusMessage;
|
||||
target: string;
|
||||
toolCalls: QaBusToolCall[];
|
||||
}) {
|
||||
let messageId: string | null = null;
|
||||
let currentText = "";
|
||||
let pending = Promise.resolve();
|
||||
|
||||
const write = (text: string) => {
|
||||
if (!text.trim() || text === currentText) {
|
||||
return pending;
|
||||
}
|
||||
pending = pending.then(async () => {
|
||||
if (messageId) {
|
||||
await editQaBusMessage({
|
||||
baseUrl: params.account.baseUrl,
|
||||
accountId: params.account.accountId,
|
||||
messageId,
|
||||
text,
|
||||
});
|
||||
} else {
|
||||
const response = await sendQaBusMessage({
|
||||
baseUrl: params.account.baseUrl,
|
||||
accountId: params.account.accountId,
|
||||
to: params.target,
|
||||
text,
|
||||
senderId: params.account.botUserId,
|
||||
senderName: params.account.botDisplayName,
|
||||
threadId: params.inbound.threadId,
|
||||
replyToId: params.inbound.id,
|
||||
toolCalls: params.toolCalls,
|
||||
});
|
||||
messageId = response.message.id;
|
||||
}
|
||||
currentText = text;
|
||||
});
|
||||
return pending;
|
||||
};
|
||||
|
||||
const clear = async () => {
|
||||
await pending.catch(() => undefined);
|
||||
if (!messageId) {
|
||||
return;
|
||||
}
|
||||
await deleteQaBusMessage({
|
||||
baseUrl: params.account.baseUrl,
|
||||
accountId: params.account.accountId,
|
||||
messageId,
|
||||
});
|
||||
messageId = null;
|
||||
currentText = "";
|
||||
};
|
||||
|
||||
const sendDurable = async (text: string) => {
|
||||
if (!text.trim()) {
|
||||
return;
|
||||
}
|
||||
await sendQaBusMessage({
|
||||
baseUrl: params.account.baseUrl,
|
||||
accountId: params.account.accountId,
|
||||
to: params.target,
|
||||
text,
|
||||
senderId: params.account.botUserId,
|
||||
senderName: params.account.botDisplayName,
|
||||
threadId: params.inbound.threadId,
|
||||
replyToId: params.inbound.id,
|
||||
toolCalls: params.toolCalls,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
clear,
|
||||
async deliver(text: string, kind: string) {
|
||||
await pending;
|
||||
if (kind === "final" && messageId && params.toolCalls.length === 0) {
|
||||
await write(text);
|
||||
return;
|
||||
}
|
||||
await clear();
|
||||
await sendDurable(text);
|
||||
},
|
||||
update: write,
|
||||
};
|
||||
}
|
||||
|
||||
export async function handleQaInbound(params: {
|
||||
channelId: string;
|
||||
channelLabel: string;
|
||||
account: ResolvedQaChannelAccount;
|
||||
config: CoreConfig;
|
||||
message: QaBusMessage;
|
||||
}) {
|
||||
const runtime = getQaChannelRuntime();
|
||||
const inbound = params.message;
|
||||
const target = buildQaTarget({
|
||||
chatType: inbound.conversation.kind,
|
||||
conversationId: inbound.conversation.id,
|
||||
threadId: inbound.threadId,
|
||||
});
|
||||
const toolCalls: QaBusToolCall[] = [];
|
||||
const preview = createQaReplyPreview({
|
||||
account: params.account,
|
||||
inbound,
|
||||
target,
|
||||
toolCalls,
|
||||
});
|
||||
const { route, buildEnvelope } = resolveInboundRouteEnvelopeBuilderWithRuntime({
|
||||
cfg: params.config as OpenClawConfig,
|
||||
channel: params.channelId,
|
||||
accountId: params.account.accountId,
|
||||
peer: {
|
||||
kind:
|
||||
inbound.conversation.kind === "direct"
|
||||
? "direct"
|
||||
: inbound.conversation.kind === "group"
|
||||
? "group"
|
||||
: "channel",
|
||||
id: target,
|
||||
},
|
||||
runtime: runtime.channel,
|
||||
sessionStore: params.config.session?.store,
|
||||
});
|
||||
const isGroup = inbound.conversation.kind !== "direct";
|
||||
const wasMentioned = isGroup
|
||||
? runtime.channel.mentions.matchesMentionPatterns(
|
||||
inbound.text,
|
||||
runtime.channel.mentions.buildMentionRegexes(
|
||||
params.config as OpenClawConfig,
|
||||
route.agentId,
|
||||
),
|
||||
)
|
||||
: undefined;
|
||||
const groupConfig = isGroup
|
||||
? resolveQaGroupConfig({
|
||||
account: params.account,
|
||||
conversationId: inbound.conversation.id,
|
||||
target,
|
||||
})
|
||||
: undefined;
|
||||
const access = await resolveStableChannelMessageIngress({
|
||||
channelId: params.channelId,
|
||||
accountId: params.account.accountId,
|
||||
identity: { key: "sender", entryIdPrefix: "qa-entry" },
|
||||
groupAllowFromFallbackToAllowFrom: true,
|
||||
subject: { stableId: inbound.senderId },
|
||||
conversation: {
|
||||
kind: inbound.conversation.kind,
|
||||
id: inbound.conversation.id,
|
||||
threadId: inbound.threadId,
|
||||
title: inbound.conversation.title,
|
||||
},
|
||||
mentionFacts: isGroup
|
||||
? {
|
||||
canDetectMention: true,
|
||||
wasMentioned: wasMentioned ?? false,
|
||||
}
|
||||
: undefined,
|
||||
dmPolicy: "open",
|
||||
groupPolicy: params.account.config.groupPolicy ?? "open",
|
||||
policy: {
|
||||
activation: isGroup
|
||||
? {
|
||||
requireMention: groupConfig?.requireMention ?? false,
|
||||
allowTextCommands: true,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
allowFrom: params.account.config.allowFrom,
|
||||
groupAllowFrom: params.account.config.groupAllowFrom,
|
||||
});
|
||||
if (access.ingress.admission !== "dispatch") {
|
||||
return;
|
||||
}
|
||||
const { storePath, body } = buildEnvelope({
|
||||
channel: params.channelLabel,
|
||||
from: inbound.senderName || inbound.senderId,
|
||||
timestamp: inbound.timestamp,
|
||||
body: inbound.text,
|
||||
});
|
||||
const mediaPayload = await resolveQaInboundMediaPayload(inbound.attachments);
|
||||
const nativeCommand = inbound.nativeCommand;
|
||||
const commandTargets = nativeCommand
|
||||
? resolveNativeCommandSessionTargets({
|
||||
agentId: route.agentId,
|
||||
sessionPrefix: "qa-channel:slash",
|
||||
userId: inbound.senderId,
|
||||
targetSessionKey: route.sessionKey,
|
||||
})
|
||||
: undefined;
|
||||
const commandBody = nativeCommand ? `/${nativeCommand.name}` : inbound.text;
|
||||
|
||||
const ctxPayload = runtime.channel.reply.finalizeInboundContext({
|
||||
Body: body,
|
||||
BodyForAgent: inbound.text,
|
||||
RawBody: inbound.text,
|
||||
CommandBody: commandBody,
|
||||
From: target,
|
||||
To: target,
|
||||
SessionKey: commandTargets?.sessionKey ?? route.sessionKey,
|
||||
CommandTargetSessionKey: commandTargets?.commandTargetSessionKey,
|
||||
AccountId: route.accountId ?? params.account.accountId,
|
||||
ChatType: inbound.conversation.kind === "direct" ? "direct" : "group",
|
||||
WasMentioned: wasMentioned,
|
||||
ConversationLabel:
|
||||
inbound.threadTitle ||
|
||||
inbound.conversation.title ||
|
||||
inbound.senderName ||
|
||||
inbound.conversation.id,
|
||||
GroupSubject: isGroup
|
||||
? inbound.threadTitle || inbound.conversation.title || inbound.conversation.id
|
||||
: undefined,
|
||||
GroupChannel: inbound.conversation.kind === "channel" ? inbound.conversation.id : undefined,
|
||||
NativeChannelId: inbound.conversation.id,
|
||||
MessageThreadId: inbound.threadId,
|
||||
ThreadLabel: inbound.threadTitle,
|
||||
ThreadParentId: inbound.threadId ? inbound.conversation.id : undefined,
|
||||
SenderName: inbound.senderName,
|
||||
SenderId: inbound.senderId,
|
||||
Provider: params.channelId,
|
||||
Surface: params.channelId,
|
||||
MessageSid: inbound.id,
|
||||
MessageSidFull: inbound.id,
|
||||
ReplyToId: inbound.replyToId,
|
||||
Timestamp: inbound.timestamp,
|
||||
OriginatingChannel: params.channelId,
|
||||
OriginatingTo: target,
|
||||
CommandAuthorized: true,
|
||||
CommandSource: nativeCommand ? "native" : undefined,
|
||||
CommandTurn: nativeCommand
|
||||
? {
|
||||
kind: "native",
|
||||
source: "native",
|
||||
authorized: true,
|
||||
body: commandBody,
|
||||
}
|
||||
: undefined,
|
||||
...mediaPayload,
|
||||
});
|
||||
|
||||
await runtime.channel.inbound.dispatchReply({
|
||||
cfg: params.config as OpenClawConfig,
|
||||
channel: params.channelId,
|
||||
accountId: params.account.accountId,
|
||||
agentId: route.agentId,
|
||||
routeSessionKey: route.sessionKey,
|
||||
storePath,
|
||||
ctxPayload,
|
||||
recordInboundSession: runtime.channel.session.recordInboundSession,
|
||||
dispatchReplyWithBufferedBlockDispatcher:
|
||||
runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher,
|
||||
delivery: {
|
||||
deliver: async (payload, info) => {
|
||||
const text =
|
||||
payload && typeof payload === "object" && "text" in payload
|
||||
? ((payload as { text?: string }).text ?? "")
|
||||
: "";
|
||||
if (!text.trim()) {
|
||||
return;
|
||||
}
|
||||
await preview.deliver(text, info?.kind ?? "final");
|
||||
},
|
||||
onError: (error) => {
|
||||
void preview.clear().catch((clearError: unknown) => {
|
||||
console.warn(
|
||||
`[qa-channel] failed to clear reply preview after dispatch error: ${formatQaErrorForLog(clearError)}`,
|
||||
);
|
||||
});
|
||||
console.warn(`[qa-channel] reply dispatch failed: ${formatQaErrorForLog(error)}`);
|
||||
},
|
||||
},
|
||||
replyOptions: {
|
||||
onPartialReply: async (payload) => {
|
||||
await preview.update(payload.text ?? "");
|
||||
},
|
||||
onToolStart: (payload) => {
|
||||
if (payload.phase && payload.phase !== "start") {
|
||||
return;
|
||||
}
|
||||
const name = payload.name?.trim();
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
const args = sanitizeQaBusToolCallArguments(payload.args);
|
||||
toolCalls.push({
|
||||
name,
|
||||
...(args && Object.keys(args).length > 0 ? { arguments: args } : {}),
|
||||
});
|
||||
},
|
||||
},
|
||||
replyPipeline: {},
|
||||
record: {
|
||||
onRecordError: (error) => {
|
||||
throw error instanceof Error
|
||||
? error
|
||||
: new Error(`qa-channel session record failed: ${String(error)}`);
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user