mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 06:27:27 +00:00
feat: per-channel responsePrefix override (#9001)
* feat: per-channel responsePrefix override
Add responsePrefix field to all channel config types and Zod schemas,
enabling per-channel and per-account outbound response prefix overrides.
Resolution cascade (most specific wins):
L1: channels.<ch>.accounts.<id>.responsePrefix
L2: channels.<ch>.responsePrefix
L3: (reserved for channels.defaults)
L4: messages.responsePrefix (existing global)
Semantics:
- undefined -> inherit from parent level
- empty string -> explicitly no prefix (stops cascade)
- "auto" -> derive [identity.name] from routed agent
Changes:
- Core logic: resolveResponsePrefix() in identity.ts accepts
optional channel/accountId and walks the cascade
- resolveEffectiveMessagesConfig() passes channel context through
- Types: responsePrefix added to WhatsApp, Telegram, Discord, Slack,
Signal, iMessage, Google Chat, MS Teams, Feishu, BlueBubbles configs
- Zod schemas: responsePrefix added for config validation
- All channel handlers wired: telegram, discord, slack, signal,
imessage, line, heartbeat runner, route-reply, native commands
- 23 new tests covering backward compat, channel/account levels,
full cascade, auto keyword, empty string stops, unknown fallthrough
Fully backward compatible - no existing config is affected.
Fixes #8857
* fix: address CI lint + review feedback
- Replace Record<string, any> with proper typed helpers (no-explicit-any)
- Add curly braces to single-line if returns (eslint curly)
- Fix JSDoc: 'Per-channel' → 'channel/account' on shared config types
- Extract getChannelConfig() helper for type-safe dynamic key access
* fix: finish responsePrefix overrides (#9001) (thanks @mudrii)
* fix: normalize prefix wiring and types (#9001) (thanks @mudrii)
---------
Co-authored-by: Gustavo Madeira Santana <gumadeiras@gmail.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
||||
import {
|
||||
createReplyPrefixOptions,
|
||||
logAckFailure,
|
||||
logInboundDrop,
|
||||
logTypingFailure,
|
||||
@@ -2173,10 +2174,17 @@ async function processMessage(
|
||||
}, typingRestartDelayMs);
|
||||
};
|
||||
try {
|
||||
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
|
||||
cfg: config,
|
||||
agentId: route.agentId,
|
||||
channel: "bluebubbles",
|
||||
accountId: account.accountId,
|
||||
});
|
||||
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
||||
ctx: ctxPayload,
|
||||
cfg: config,
|
||||
dispatcherOptions: {
|
||||
...prefixOptions,
|
||||
deliver: async (payload, info) => {
|
||||
const rawReplyToId =
|
||||
typeof payload.replyToId === "string" ? payload.replyToId.trim() : "";
|
||||
@@ -2288,6 +2296,7 @@ async function processMessage(
|
||||
},
|
||||
},
|
||||
replyOptions: {
|
||||
onModelSelected,
|
||||
disableBlockStreaming:
|
||||
typeof account.config.blockStreaming === "boolean"
|
||||
? !account.config.blockStreaming
|
||||
|
||||
@@ -37,6 +37,7 @@ const FeishuAccountSchema = z
|
||||
blockStreaming: z.boolean().optional(),
|
||||
streaming: z.boolean().optional(),
|
||||
mediaMaxMb: z.number().optional(),
|
||||
responsePrefix: z.string().optional(),
|
||||
groups: z.record(z.string(), FeishuGroupSchema.optional()).optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
||||
import { resolveMentionGatingWithBypass } from "openclaw/plugin-sdk";
|
||||
import { createReplyPrefixOptions, resolveMentionGatingWithBypass } from "openclaw/plugin-sdk";
|
||||
import type {
|
||||
GoogleChatAnnotation,
|
||||
GoogleChatAttachment,
|
||||
@@ -725,10 +725,18 @@ async function processMessageWithPipeline(params: {
|
||||
}
|
||||
}
|
||||
|
||||
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
|
||||
cfg: config,
|
||||
agentId: route.agentId,
|
||||
channel: "googlechat",
|
||||
accountId: route.accountId,
|
||||
});
|
||||
|
||||
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
||||
ctx: ctxPayload,
|
||||
cfg: config,
|
||||
dispatcherOptions: {
|
||||
...prefixOptions,
|
||||
deliver: async (payload) => {
|
||||
await deliverGoogleChatReply({
|
||||
payload,
|
||||
@@ -749,6 +757,9 @@ async function processMessageWithPipeline(params: {
|
||||
);
|
||||
},
|
||||
},
|
||||
replyOptions: {
|
||||
onModelSelected,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ export const MatrixConfigSchema = z.object({
|
||||
threadReplies: z.enum(["off", "inbound", "always"]).optional(),
|
||||
textChunkLimit: z.number().optional(),
|
||||
chunkMode: z.enum(["length", "newline"]).optional(),
|
||||
responsePrefix: z.string().optional(),
|
||||
mediaMaxMb: z.number().optional(),
|
||||
autoJoin: z.enum(["always", "allowlist", "off"]).optional(),
|
||||
autoJoinAllowlist: z.array(allowFromEntry).optional(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { LocationMessageEventContent, MatrixClient } from "@vector-im/matrix-bot-sdk";
|
||||
import {
|
||||
createReplyPrefixContext,
|
||||
createReplyPrefixOptions,
|
||||
createTypingCallbacks,
|
||||
formatAllowlistMatchMeta,
|
||||
logInboundDrop,
|
||||
@@ -579,7 +579,12 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
channel: "matrix",
|
||||
accountId: route.accountId,
|
||||
});
|
||||
const prefixContext = createReplyPrefixContext({ cfg, agentId: route.agentId });
|
||||
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
|
||||
cfg,
|
||||
agentId: route.agentId,
|
||||
channel: "matrix",
|
||||
accountId: route.accountId,
|
||||
});
|
||||
const typingCallbacks = createTypingCallbacks({
|
||||
start: () => sendTypingMatrix(roomId, true, undefined, client),
|
||||
stop: () => sendTypingMatrix(roomId, false, undefined, client),
|
||||
@@ -604,8 +609,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
});
|
||||
const { dispatcher, replyOptions, markDispatchIdle } =
|
||||
core.channel.reply.createReplyDispatcherWithTyping({
|
||||
responsePrefix: prefixContext.responsePrefix,
|
||||
responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
|
||||
...prefixOptions,
|
||||
humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, route.agentId),
|
||||
deliver: async (payload) => {
|
||||
await deliverMatrixReplies({
|
||||
@@ -635,7 +639,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
replyOptions: {
|
||||
...replyOptions,
|
||||
skillFilter: roomConfig?.skills,
|
||||
onModelSelected: prefixContext.onModelSelected,
|
||||
onModelSelected,
|
||||
},
|
||||
});
|
||||
markDispatchIdle();
|
||||
|
||||
@@ -71,6 +71,8 @@ export type MatrixConfig = {
|
||||
textChunkLimit?: number;
|
||||
/** Chunking mode: "length" (default) splits by size; "newline" splits on every newline. */
|
||||
chunkMode?: "length" | "newline";
|
||||
/** Outbound response prefix override for this channel/account. */
|
||||
responsePrefix?: string;
|
||||
/** Max outbound media size in MB. */
|
||||
mediaMaxMb?: number;
|
||||
/** Auto-join invites (always|allowlist|off). Default: always. */
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
||||
import { createReplyPrefixOptions } from "openclaw/plugin-sdk";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { mattermostPlugin } from "./channel.js";
|
||||
|
||||
@@ -44,5 +46,27 @@ describe("mattermostPlugin", () => {
|
||||
});
|
||||
expect(formatted).toEqual(["@alice", "user123", "bot999"]);
|
||||
});
|
||||
|
||||
it("uses account responsePrefix overrides", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: {
|
||||
mattermost: {
|
||||
responsePrefix: "[Channel]",
|
||||
accounts: {
|
||||
default: { responsePrefix: "[Account]" },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const prefixContext = createReplyPrefixOptions({
|
||||
cfg,
|
||||
agentId: "main",
|
||||
channel: "mattermost",
|
||||
accountId: "default",
|
||||
});
|
||||
|
||||
expect(prefixContext.responsePrefix).toBe("[Account]");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,6 +27,7 @@ const MattermostAccountSchemaBase = z
|
||||
chunkMode: z.enum(["length", "newline"]).optional(),
|
||||
blockStreaming: z.boolean().optional(),
|
||||
blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
|
||||
responsePrefix: z.string().optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
RuntimeEnv,
|
||||
} from "openclaw/plugin-sdk";
|
||||
import {
|
||||
createReplyPrefixContext,
|
||||
createReplyPrefixOptions,
|
||||
createTypingCallbacks,
|
||||
logInboundDrop,
|
||||
logTypingFailure,
|
||||
@@ -760,7 +760,12 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
|
||||
accountId: account.accountId,
|
||||
});
|
||||
|
||||
const prefixContext = createReplyPrefixContext({ cfg, agentId: route.agentId });
|
||||
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
|
||||
cfg,
|
||||
agentId: route.agentId,
|
||||
channel: "mattermost",
|
||||
accountId: account.accountId,
|
||||
});
|
||||
|
||||
const typingCallbacks = createTypingCallbacks({
|
||||
start: () => sendTypingIndicator(channelId, threadRootId),
|
||||
@@ -775,8 +780,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
|
||||
});
|
||||
const { dispatcher, replyOptions, markDispatchIdle } =
|
||||
core.channel.reply.createReplyDispatcherWithTyping({
|
||||
responsePrefix: prefixContext.responsePrefix,
|
||||
responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
|
||||
...prefixOptions,
|
||||
humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, route.agentId),
|
||||
deliver: async (payload: ReplyPayload) => {
|
||||
const mediaUrls = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
|
||||
@@ -825,7 +829,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
|
||||
...replyOptions,
|
||||
disableBlockStreaming:
|
||||
typeof account.blockStreaming === "boolean" ? !account.blockStreaming : undefined,
|
||||
onModelSelected: prefixContext.onModelSelected,
|
||||
onModelSelected,
|
||||
},
|
||||
});
|
||||
markDispatchIdle();
|
||||
|
||||
@@ -42,6 +42,8 @@ export type MattermostAccountConfig = {
|
||||
blockStreaming?: boolean;
|
||||
/** Merge streamed block replies before sending. */
|
||||
blockStreamingCoalesce?: BlockStreamingCoalesceConfig;
|
||||
/** Outbound response prefix override for this channel/account. */
|
||||
responsePrefix?: string;
|
||||
};
|
||||
|
||||
export type MattermostConfig = {
|
||||
|
||||
@@ -493,6 +493,7 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
||||
const { dispatcher, replyOptions, markDispatchIdle } = createMSTeamsReplyDispatcher({
|
||||
cfg,
|
||||
agentId: route.agentId,
|
||||
accountId: route.accountId,
|
||||
runtime,
|
||||
log,
|
||||
adapter,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
createReplyPrefixContext,
|
||||
createReplyPrefixOptions,
|
||||
createTypingCallbacks,
|
||||
logTypingFailure,
|
||||
resolveChannelMediaMaxBytes,
|
||||
@@ -26,6 +26,7 @@ import { getMSTeamsRuntime } from "./runtime.js";
|
||||
export function createMSTeamsReplyDispatcher(params: {
|
||||
cfg: OpenClawConfig;
|
||||
agentId: string;
|
||||
accountId?: string;
|
||||
runtime: RuntimeEnv;
|
||||
log: MSTeamsMonitorLogger;
|
||||
adapter: MSTeamsAdapter;
|
||||
@@ -55,16 +56,17 @@ export function createMSTeamsReplyDispatcher(params: {
|
||||
});
|
||||
},
|
||||
});
|
||||
const prefixContext = createReplyPrefixContext({
|
||||
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: "msteams",
|
||||
accountId: params.accountId,
|
||||
});
|
||||
const chunkMode = core.channel.text.resolveChunkMode(params.cfg, "msteams");
|
||||
|
||||
const { dispatcher, replyOptions, markDispatchIdle } =
|
||||
core.channel.reply.createReplyDispatcherWithTyping({
|
||||
responsePrefix: prefixContext.responsePrefix,
|
||||
responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
|
||||
...prefixOptions,
|
||||
humanDelay: core.channel.reply.resolveHumanDelayConfig(params.cfg, params.agentId),
|
||||
deliver: async (payload) => {
|
||||
const tableMode = core.channel.text.resolveMarkdownTableMode({
|
||||
@@ -124,7 +126,7 @@ export function createMSTeamsReplyDispatcher(params: {
|
||||
|
||||
return {
|
||||
dispatcher,
|
||||
replyOptions: { ...replyOptions, onModelSelected: prefixContext.onModelSelected },
|
||||
replyOptions: { ...replyOptions, onModelSelected },
|
||||
markDispatchIdle,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ export const NextcloudTalkAccountSchemaBase = z
|
||||
chunkMode: z.enum(["length", "newline"]).optional(),
|
||||
blockStreaming: z.boolean().optional(),
|
||||
blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
|
||||
responsePrefix: z.string().optional(),
|
||||
mediaMaxMb: z.number().positive().optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
createReplyPrefixOptions,
|
||||
logInboundDrop,
|
||||
resolveControlCommandGate,
|
||||
type OpenClawConfig,
|
||||
@@ -285,10 +286,18 @@ export async function handleNextcloudTalkInbound(params: {
|
||||
},
|
||||
});
|
||||
|
||||
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
|
||||
cfg: config as OpenClawConfig,
|
||||
agentId: route.agentId,
|
||||
channel: CHANNEL_ID,
|
||||
accountId: account.accountId,
|
||||
});
|
||||
|
||||
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
||||
ctx: ctxPayload,
|
||||
cfg: config as OpenClawConfig,
|
||||
dispatcherOptions: {
|
||||
...prefixOptions,
|
||||
deliver: async (payload) => {
|
||||
await deliverNextcloudTalkReply({
|
||||
payload: payload as {
|
||||
@@ -308,6 +317,7 @@ export async function handleNextcloudTalkInbound(params: {
|
||||
},
|
||||
replyOptions: {
|
||||
skillFilter: roomConfig?.skills,
|
||||
onModelSelected,
|
||||
disableBlockStreaming:
|
||||
typeof account.config.blockStreaming === "boolean"
|
||||
? !account.config.blockStreaming
|
||||
|
||||
@@ -68,6 +68,8 @@ export type NextcloudTalkAccountConfig = {
|
||||
blockStreaming?: boolean;
|
||||
/** Merge streamed block replies before sending. */
|
||||
blockStreamingCoalesce?: BlockStreamingCoalesceConfig;
|
||||
/** Outbound response prefix override for this channel/account. */
|
||||
responsePrefix?: string;
|
||||
/** Media upload max size in MB. */
|
||||
mediaMaxMb?: number;
|
||||
};
|
||||
|
||||
@@ -23,6 +23,7 @@ export const TlonAccountSchema = z.object({
|
||||
dmAllowlist: z.array(ShipSchema).optional(),
|
||||
autoDiscoverChannels: z.boolean().optional(),
|
||||
showModelSignature: z.boolean().optional(),
|
||||
responsePrefix: z.string().optional(),
|
||||
});
|
||||
|
||||
export const TlonConfigSchema = z.object({
|
||||
@@ -35,6 +36,7 @@ export const TlonConfigSchema = z.object({
|
||||
dmAllowlist: z.array(ShipSchema).optional(),
|
||||
autoDiscoverChannels: z.boolean().optional(),
|
||||
showModelSignature: z.boolean().optional(),
|
||||
responsePrefix: z.string().optional(),
|
||||
authorization: TlonAuthorizationSchema.optional(),
|
||||
defaultAuthorizedShips: z.array(ShipSchema).optional(),
|
||||
accounts: z.record(z.string(), TlonAccountSchema).optional(),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { RuntimeEnv, ReplyPayload, OpenClawConfig } from "openclaw/plugin-sdk";
|
||||
import { format } from "node:util";
|
||||
import { createReplyPrefixOptions } from "openclaw/plugin-sdk";
|
||||
import { getTlonRuntime } from "../runtime.js";
|
||||
import { normalizeShip, parseChannelNest } from "../targets.js";
|
||||
import { resolveTlonAccount } from "../types.js";
|
||||
@@ -28,6 +29,29 @@ type ChannelAuthorization = {
|
||||
allowedShips?: string[];
|
||||
};
|
||||
|
||||
type UrbitMemo = {
|
||||
author?: string;
|
||||
content?: unknown;
|
||||
sent?: number;
|
||||
};
|
||||
|
||||
type UrbitUpdate = {
|
||||
id?: string | number;
|
||||
response?: {
|
||||
add?: { memo?: UrbitMemo };
|
||||
post?: {
|
||||
id?: string | number;
|
||||
"r-post"?: {
|
||||
set?: { essay?: UrbitMemo };
|
||||
reply?: {
|
||||
id?: string | number;
|
||||
"r-reply"?: { set?: { memo?: UrbitMemo } };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
function resolveChannelAuthorization(
|
||||
cfg: OpenClawConfig,
|
||||
channelNest: string,
|
||||
@@ -120,15 +144,14 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
runtime.log?.("[tlon] No group channels to monitor (DMs only)");
|
||||
}
|
||||
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
const handleIncomingDM = async (update: any) => {
|
||||
const handleIncomingDM = async (update: UrbitUpdate) => {
|
||||
try {
|
||||
const memo = update?.response?.add?.memo;
|
||||
if (!memo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const messageId = update.id as string | undefined;
|
||||
const messageId = update.id != null ? String(update.id) : undefined;
|
||||
if (!processedTracker.mark(messageId)) {
|
||||
return;
|
||||
}
|
||||
@@ -160,25 +183,24 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
}
|
||||
};
|
||||
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
const handleIncomingGroupMessage = (channelNest: string) => async (update: any) => {
|
||||
const handleIncomingGroupMessage = (channelNest: string) => async (update: UrbitUpdate) => {
|
||||
try {
|
||||
const parsed = parseChannelNest(channelNest);
|
||||
if (!parsed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const essay = update?.response?.post?.["r-post"]?.set?.essay;
|
||||
const memo = update?.response?.post?.["r-post"]?.reply?.["r-reply"]?.set?.memo;
|
||||
const post = update?.response?.post?.["r-post"];
|
||||
const essay = post?.set?.essay;
|
||||
const memo = post?.reply?.["r-reply"]?.set?.memo;
|
||||
if (!essay && !memo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const content = memo || essay;
|
||||
const isThreadReply = Boolean(memo);
|
||||
const messageId = isThreadReply
|
||||
? update?.response?.post?.["r-post"]?.reply?.id
|
||||
: update?.response?.post?.id;
|
||||
const rawMessageId = isThreadReply ? post?.reply?.id : update?.response?.post?.id;
|
||||
const messageId = rawMessageId != null ? String(rawMessageId) : undefined;
|
||||
|
||||
if (!processedTracker.mark(messageId)) {
|
||||
return;
|
||||
@@ -355,17 +377,19 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
|
||||
const dispatchStartTime = Date.now();
|
||||
|
||||
const responsePrefix = core.channel.reply.resolveEffectiveMessagesConfig(
|
||||
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
|
||||
cfg,
|
||||
route.agentId,
|
||||
).responsePrefix;
|
||||
agentId: route.agentId,
|
||||
channel: "tlon",
|
||||
accountId: route.accountId,
|
||||
});
|
||||
const humanDelay = core.channel.reply.resolveHumanDelayConfig(cfg, route.agentId);
|
||||
|
||||
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
||||
ctx: ctxPayload,
|
||||
cfg,
|
||||
dispatcherOptions: {
|
||||
responsePrefix,
|
||||
...prefixOptions,
|
||||
humanDelay,
|
||||
deliver: async (payload: ReplyPayload) => {
|
||||
let replyText = payload.text;
|
||||
@@ -408,6 +432,9 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
);
|
||||
},
|
||||
},
|
||||
replyOptions: {
|
||||
onModelSelected,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ const TwitchAccountSchema = z.object({
|
||||
allowedRoles: z.array(TwitchRoleSchema).optional(),
|
||||
/** Require @mention to trigger bot responses */
|
||||
requireMention: z.boolean().optional(),
|
||||
/** Outbound response prefix override for this channel/account. */
|
||||
responsePrefix: z.string().optional(),
|
||||
/** Twitch client secret (required for token refresh via RefreshingAuthProvider) */
|
||||
clientSecret: z.string().optional(),
|
||||
/** Refresh token (required for automatic token refresh) */
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
import type { ReplyPayload, OpenClawConfig } from "openclaw/plugin-sdk";
|
||||
import { createReplyPrefixOptions } from "openclaw/plugin-sdk";
|
||||
import type { TwitchAccountConfig, TwitchChatMessage } from "./types.js";
|
||||
import { checkTwitchAccessControl } from "./access-control.js";
|
||||
import { getOrCreateClientManager } from "./client-manager-registry.js";
|
||||
@@ -103,11 +104,18 @@ async function processTwitchMessage(params: {
|
||||
channel: "twitch",
|
||||
accountId,
|
||||
});
|
||||
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
|
||||
cfg,
|
||||
agentId: route.agentId,
|
||||
channel: "twitch",
|
||||
accountId,
|
||||
});
|
||||
|
||||
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
||||
ctx: ctxPayload,
|
||||
cfg,
|
||||
dispatcherOptions: {
|
||||
...prefixOptions,
|
||||
deliver: async (payload) => {
|
||||
await deliverTwitchReply({
|
||||
payload,
|
||||
@@ -121,6 +129,9 @@ async function processTwitchMessage(params: {
|
||||
});
|
||||
},
|
||||
},
|
||||
replyOptions: {
|
||||
onModelSelected,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,8 @@ export interface TwitchAccountConfig {
|
||||
allowedRoles?: TwitchRole[];
|
||||
/** Require @mention to trigger bot responses */
|
||||
requireMention?: boolean;
|
||||
/** Outbound response prefix override for this channel/account. */
|
||||
responsePrefix?: string;
|
||||
/** Twitch client secret (required for token refresh via RefreshingAuthProvider) */
|
||||
clientSecret?: string;
|
||||
/** Refresh token (required for automatic token refresh) */
|
||||
|
||||
@@ -16,6 +16,7 @@ const zaloAccountSchema = z.object({
|
||||
allowFrom: z.array(allowFromEntry).optional(),
|
||||
mediaMaxMb: z.number().optional(),
|
||||
proxy: z.string().optional(),
|
||||
responsePrefix: z.string().optional(),
|
||||
});
|
||||
|
||||
export const ZaloConfigSchema = zaloAccountSchema.extend({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import type { OpenClawConfig, MarkdownTableMode } from "openclaw/plugin-sdk";
|
||||
import { createReplyPrefixOptions } from "openclaw/plugin-sdk";
|
||||
import type { ResolvedZaloAccount } from "./accounts.js";
|
||||
import {
|
||||
ZaloApiError,
|
||||
@@ -583,11 +584,18 @@ async function processMessageWithPipeline(params: {
|
||||
channel: "zalo",
|
||||
accountId: account.accountId,
|
||||
});
|
||||
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
|
||||
cfg: config,
|
||||
agentId: route.agentId,
|
||||
channel: "zalo",
|
||||
accountId: account.accountId,
|
||||
});
|
||||
|
||||
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
||||
ctx: ctxPayload,
|
||||
cfg: config,
|
||||
dispatcherOptions: {
|
||||
...prefixOptions,
|
||||
deliver: async (payload) => {
|
||||
await deliverZaloReply({
|
||||
payload,
|
||||
@@ -606,6 +614,9 @@ async function processMessageWithPipeline(params: {
|
||||
runtime.error?.(`[${account.accountId}] Zalo ${info.kind} reply failed: ${String(err)}`);
|
||||
},
|
||||
},
|
||||
replyOptions: {
|
||||
onModelSelected,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ export type ZaloAccountConfig = {
|
||||
mediaMaxMb?: number;
|
||||
/** Proxy URL for API requests. */
|
||||
proxy?: string;
|
||||
/** Outbound response prefix override for this channel/account. */
|
||||
responsePrefix?: string;
|
||||
};
|
||||
|
||||
export type ZaloConfig = {
|
||||
|
||||
@@ -19,6 +19,7 @@ const zalouserAccountSchema = z.object({
|
||||
groupPolicy: z.enum(["disabled", "allowlist", "open"]).optional(),
|
||||
groups: z.object({}).catchall(groupConfigSchema).optional(),
|
||||
messagePrefix: z.string().optional(),
|
||||
responsePrefix: z.string().optional(),
|
||||
});
|
||||
|
||||
export const ZalouserConfigSchema = zalouserAccountSchema.extend({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ChildProcess } from "node:child_process";
|
||||
import type { OpenClawConfig, MarkdownTableMode, RuntimeEnv } from "openclaw/plugin-sdk";
|
||||
import { mergeAllowlist, summarizeMapping } from "openclaw/plugin-sdk";
|
||||
import { createReplyPrefixOptions, mergeAllowlist, summarizeMapping } from "openclaw/plugin-sdk";
|
||||
import type { ResolvedZalouserAccount, ZcaFriend, ZcaGroup, ZcaMessage } from "./types.js";
|
||||
import { getZalouserRuntime } from "./runtime.js";
|
||||
import { sendMessageZalouser } from "./send.js";
|
||||
@@ -334,10 +334,18 @@ async function processMessage(
|
||||
},
|
||||
});
|
||||
|
||||
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
|
||||
cfg: config,
|
||||
agentId: route.agentId,
|
||||
channel: "zalouser",
|
||||
accountId: account.accountId,
|
||||
});
|
||||
|
||||
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
||||
ctx: ctxPayload,
|
||||
cfg: config,
|
||||
dispatcherOptions: {
|
||||
...prefixOptions,
|
||||
deliver: async (payload) => {
|
||||
await deliverZalouserReply({
|
||||
payload: payload as { text?: string; mediaUrls?: string[]; mediaUrl?: string },
|
||||
@@ -360,6 +368,9 @@ async function processMessage(
|
||||
runtime.error(`[${account.accountId}] Zalouser ${info.kind} reply failed: ${String(err)}`);
|
||||
},
|
||||
},
|
||||
replyOptions: {
|
||||
onModelSelected,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ export type ZalouserAccountConfig = {
|
||||
{ allow?: boolean; enabled?: boolean; tools?: { allow?: string[]; deny?: string[] } }
|
||||
>;
|
||||
messagePrefix?: string;
|
||||
responsePrefix?: string;
|
||||
};
|
||||
|
||||
export type ZalouserConfig = {
|
||||
@@ -95,6 +96,7 @@ export type ZalouserConfig = {
|
||||
{ allow?: boolean; enabled?: boolean; tools?: { allow?: string[]; deny?: string[] } }
|
||||
>;
|
||||
messagePrefix?: string;
|
||||
responsePrefix?: string;
|
||||
accounts?: Record<string, ZalouserAccountConfig>;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user