feat(messages): add whatsapp messagePrefix and responsePrefix auto
This commit is contained in:
@@ -77,7 +77,7 @@ Details: [Thinking + reasoning directives](/tools/thinking) and [Token use](/tok
|
||||
## Prefixes, threading, and replies
|
||||
|
||||
Outbound message formatting is centralized in `messages`:
|
||||
- `messages.responsePrefix` and `messages.messagePrefix`
|
||||
- `messages.responsePrefix` (outbound prefix) and `whatsapp.messagePrefix` (WhatsApp inbound prefix)
|
||||
- Reply threading via `replyToMode` and per-provider defaults
|
||||
|
||||
Details: [Configuration](/gateway/configuration#messages) and provider docs.
|
||||
|
||||
@@ -926,8 +926,7 @@ See [Messages](/concepts/messages) for queueing, sessions, and streaming context
|
||||
```json5
|
||||
{
|
||||
messages: {
|
||||
messagePrefix: "[clawdbot]",
|
||||
responsePrefix: "🦞",
|
||||
responsePrefix: "🦞", // or "auto"
|
||||
ackReaction: "👀",
|
||||
ackReactionScope: "group-mentions"
|
||||
}
|
||||
@@ -938,11 +937,13 @@ See [Messages](/concepts/messages) for queueing, sessions, and streaming context
|
||||
streaming, final replies) across providers unless already present.
|
||||
|
||||
If `messages.responsePrefix` is unset, no prefix is applied by default.
|
||||
Set it to `"auto"` to derive `[{identity.name}]` for the routed agent (when set).
|
||||
|
||||
If `messages.messagePrefix` is unset, the default stays **unchanged**:
|
||||
`"[clawdbot]"` when `whatsapp.allowFrom` is empty, otherwise `""` (no prefix).
|
||||
When using `"[clawdbot]"`, Clawdbot will instead use `[{identity.name}]` when
|
||||
the routed agent has `identity.name` set.
|
||||
WhatsApp inbound prefix is configured via `whatsapp.messagePrefix` (deprecated:
|
||||
`messages.messagePrefix`). Default stays **unchanged**: `"[clawdbot]"` when
|
||||
`whatsapp.allowFrom` is empty, otherwise `""` (no prefix). When using
|
||||
`"[clawdbot]"`, Clawdbot will instead use `[{identity.name}]` when the routed
|
||||
agent has `identity.name` set.
|
||||
|
||||
`ackReaction` sends a best-effort emoji reaction to acknowledge inbound messages
|
||||
on providers that support reactions (Slack/Discord/Telegram). Defaults to the
|
||||
|
||||
@@ -197,7 +197,7 @@ Behavior:
|
||||
- `whatsapp.actions.reactions` (gate WhatsApp tool reactions).
|
||||
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`)
|
||||
- `messages.groupChat.historyLimit`
|
||||
- `messages.messagePrefix` (inbound prefix)
|
||||
- `whatsapp.messagePrefix` (inbound prefix; per-account: `whatsapp.accounts.<accountId>.messagePrefix`; deprecated: `messages.messagePrefix`)
|
||||
- `messages.responsePrefix` (outbound prefix)
|
||||
- `agents.defaults.mediaMaxMb`
|
||||
- `agents.defaults.heartbeat.every`
|
||||
|
||||
65
src/agents/identity.test.ts
Normal file
65
src/agents/identity.test.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import { resolveMessagePrefix, resolveResponsePrefix } from "./identity.js";
|
||||
|
||||
describe("message prefix resolution", () => {
|
||||
it("returns configured messagePrefix override", () => {
|
||||
const cfg: ClawdbotConfig = {};
|
||||
expect(
|
||||
resolveMessagePrefix(cfg, "main", {
|
||||
configured: "[x]",
|
||||
hasAllowFrom: true,
|
||||
}),
|
||||
).toBe("[x]");
|
||||
expect(
|
||||
resolveMessagePrefix(cfg, "main", {
|
||||
configured: "",
|
||||
hasAllowFrom: false,
|
||||
}),
|
||||
).toBe("");
|
||||
});
|
||||
|
||||
it("defaults messagePrefix based on allowFrom + identity", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
agents: { list: [{ id: "main", identity: { name: "Richbot" } }] },
|
||||
};
|
||||
expect(resolveMessagePrefix(cfg, "main", { hasAllowFrom: true })).toBe("");
|
||||
expect(resolveMessagePrefix(cfg, "main", { hasAllowFrom: false })).toBe(
|
||||
"[Richbot]",
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to [clawdbot] when identity is missing", () => {
|
||||
const cfg: ClawdbotConfig = {};
|
||||
expect(resolveMessagePrefix(cfg, "main", { hasAllowFrom: false })).toBe(
|
||||
"[clawdbot]",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("response prefix resolution", () => {
|
||||
it("does not apply any default when unset", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
agents: { list: [{ id: "main", identity: { name: "Richbot" } }] },
|
||||
};
|
||||
expect(resolveResponsePrefix(cfg, "main")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns explicit responsePrefix when set", () => {
|
||||
const cfg: ClawdbotConfig = { messages: { responsePrefix: "PFX" } };
|
||||
expect(resolveResponsePrefix(cfg, "main")).toBe("PFX");
|
||||
});
|
||||
|
||||
it("supports responsePrefix: auto (identity-derived opt-in)", () => {
|
||||
const withIdentity: ClawdbotConfig = {
|
||||
agents: { list: [{ id: "main", identity: { name: "Richbot" } }] },
|
||||
messages: { responsePrefix: "auto" },
|
||||
};
|
||||
expect(resolveResponsePrefix(withIdentity, "main")).toBe("[Richbot]");
|
||||
|
||||
const withoutIdentity: ClawdbotConfig = {
|
||||
messages: { responsePrefix: "auto" },
|
||||
};
|
||||
expect(resolveResponsePrefix(withoutIdentity, "main")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -32,9 +32,9 @@ export function resolveIdentityNamePrefix(
|
||||
export function resolveMessagePrefix(
|
||||
cfg: ClawdbotConfig,
|
||||
agentId: string,
|
||||
opts?: { hasAllowFrom?: boolean; fallback?: string },
|
||||
opts?: { configured?: string; hasAllowFrom?: boolean; fallback?: string },
|
||||
): string {
|
||||
const configured = cfg.messages?.messagePrefix;
|
||||
const configured = opts?.configured ?? cfg.messages?.messagePrefix;
|
||||
if (configured !== undefined) return configured;
|
||||
|
||||
const hasAllowFrom = opts?.hasAllowFrom === true;
|
||||
@@ -47,10 +47,15 @@ export function resolveMessagePrefix(
|
||||
|
||||
export function resolveResponsePrefix(
|
||||
cfg: ClawdbotConfig,
|
||||
_agentId: string,
|
||||
agentId: string,
|
||||
): string | undefined {
|
||||
const configured = cfg.messages?.responsePrefix;
|
||||
if (configured !== undefined) return configured;
|
||||
if (configured !== undefined) {
|
||||
if (configured === "auto") {
|
||||
return resolveIdentityNamePrefix(cfg, agentId);
|
||||
}
|
||||
return configured;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,9 @@ export async function routeReply(
|
||||
cfg,
|
||||
resolveAgentIdFromSessionKey(params.sessionKey),
|
||||
).responsePrefix
|
||||
: cfg.messages?.responsePrefix;
|
||||
: cfg.messages?.responsePrefix === "auto"
|
||||
? undefined
|
||||
: cfg.messages?.responsePrefix;
|
||||
const normalized = normalizeReplyPayload(payload, {
|
||||
responsePrefix,
|
||||
});
|
||||
|
||||
@@ -111,6 +111,11 @@ export type WhatsAppActionConfig = {
|
||||
export type WhatsAppConfig = {
|
||||
/** Optional per-account WhatsApp configuration (multi-account). */
|
||||
accounts?: Record<string, WhatsAppAccountConfig>;
|
||||
/**
|
||||
* Inbound message prefix (WhatsApp only).
|
||||
* Default: `[{agents.list[].identity.name}]` (or `[clawdbot]`) when allowFrom is empty, else `""`.
|
||||
*/
|
||||
messagePrefix?: string;
|
||||
/** Direct message access policy (default: pairing). */
|
||||
dmPolicy?: DmPolicy;
|
||||
/**
|
||||
@@ -152,6 +157,8 @@ export type WhatsAppAccountConfig = {
|
||||
name?: string;
|
||||
/** If false, do not start this WhatsApp account provider. Default: true. */
|
||||
enabled?: boolean;
|
||||
/** Inbound message prefix override for this account (WhatsApp only). */
|
||||
messagePrefix?: string;
|
||||
/** Override auth directory (Baileys multi-file auth state). */
|
||||
authDir?: string;
|
||||
/** Direct message access policy (default: pairing). */
|
||||
@@ -931,8 +938,15 @@ export type AudioConfig = {
|
||||
};
|
||||
|
||||
export type MessagesConfig = {
|
||||
messagePrefix?: string; // Prefix added to all inbound messages (default: "[{agents.list[].identity.name}]" or "[clawdbot]" if no allowFrom, else "")
|
||||
responsePrefix?: string; // Prefix auto-added to all outbound replies (default: none)
|
||||
/** @deprecated Use `whatsapp.messagePrefix` (WhatsApp-only inbound prefix). */
|
||||
messagePrefix?: string;
|
||||
/**
|
||||
* Prefix auto-added to all outbound replies.
|
||||
* - string: explicit prefix
|
||||
* - special value: `"auto"` derives `[{agents.list[].identity.name}]` for the routed agent (when set)
|
||||
* Default: none
|
||||
*/
|
||||
responsePrefix?: string;
|
||||
groupChat?: GroupChatConfig;
|
||||
queue?: QueueConfig;
|
||||
/** Emoji reaction used to acknowledge inbound messages (empty disables). */
|
||||
|
||||
@@ -1229,6 +1229,7 @@ export const ClawdbotSchema = z.object({
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
messagePrefix: z.string().optional(),
|
||||
/** Override auth directory for this WhatsApp account (Baileys multi-file auth state). */
|
||||
authDir: z.string().optional(),
|
||||
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
||||
@@ -1268,6 +1269,7 @@ export const ClawdbotSchema = z.object({
|
||||
)
|
||||
.optional(),
|
||||
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
||||
messagePrefix: z.string().optional(),
|
||||
selfChatMode: z.boolean().optional(),
|
||||
allowFrom: z.array(z.string()).optional(),
|
||||
groupAllowFrom: z.array(z.string()).optional(),
|
||||
|
||||
@@ -15,6 +15,7 @@ export type ResolvedWhatsAppAccount = {
|
||||
accountId: string;
|
||||
name?: string;
|
||||
enabled: boolean;
|
||||
messagePrefix?: string;
|
||||
authDir: string;
|
||||
isLegacyAuthDir: boolean;
|
||||
selfChatMode?: boolean;
|
||||
@@ -111,6 +112,10 @@ export function resolveWhatsAppAccount(params: {
|
||||
accountId,
|
||||
name: accountCfg?.name?.trim() || undefined,
|
||||
enabled,
|
||||
messagePrefix:
|
||||
accountCfg?.messagePrefix ??
|
||||
params.cfg.whatsapp?.messagePrefix ??
|
||||
params.cfg.messages?.messagePrefix,
|
||||
authDir,
|
||||
isLegacyAuthDir: isLegacy,
|
||||
selfChatMode: accountCfg?.selfChatMode ?? params.cfg.whatsapp?.selfChatMode,
|
||||
|
||||
@@ -784,6 +784,7 @@ export async function monitorWebProvider(
|
||||
...baseCfg,
|
||||
whatsapp: {
|
||||
...baseCfg.whatsapp,
|
||||
messagePrefix: account.messagePrefix,
|
||||
allowFrom: account.allowFrom,
|
||||
groupAllowFrom: account.groupAllowFrom,
|
||||
groupPolicy: account.groupPolicy,
|
||||
@@ -1039,8 +1040,9 @@ export async function monitorWebProvider(
|
||||
};
|
||||
|
||||
const buildLine = (msg: WebInboundMsg, agentId: string) => {
|
||||
// Build message prefix: explicit config > identity name > default based on allowFrom
|
||||
// WhatsApp inbound prefix: whatsapp.messagePrefix > legacy messages.messagePrefix > identity/defaults
|
||||
const messagePrefix = resolveMessagePrefix(cfg, agentId, {
|
||||
configured: cfg.whatsapp?.messagePrefix,
|
||||
hasAllowFrom: (cfg.whatsapp?.allowFrom?.length ?? 0) > 0,
|
||||
});
|
||||
const prefixStr = messagePrefix ? `${messagePrefix} ` : "";
|
||||
|
||||
Reference in New Issue
Block a user