From 43848b7b432cbed12e8e4c7cc767afb93f3fc0ac Mon Sep 17 00:00:00 2001 From: Richard Poelderl Date: Fri, 9 Jan 2026 16:06:02 +0100 Subject: [PATCH] feat(messages): also derive responsePrefix from identity.name When identity.name is configured and responsePrefix is not explicitly set, automatically default responsePrefix to [identity.name]. This means users only need to set their identity once: { identity: { name: "MyBot" } } And outbound messages will automatically be prefixed with [MyBot]. --- src/config/types.ts | 4 ++-- src/web/auto-reply.test.ts | 39 ++++++++++++++++++++++++++++++++++++++ src/web/auto-reply.ts | 8 +++++++- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/config/types.ts b/src/config/types.ts index aa971f707..334f44a02 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -891,8 +891,8 @@ export type AudioConfig = { }; export type MessagesConfig = { - messagePrefix?: string; // Prefix added to all inbound messages (default: "[{identity.name}]" or "[clawdbot]" if no allowFrom, else "") - responsePrefix?: string; // Prefix auto-added to all outbound replies (e.g., "🦞") + 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: "[{agents.list[].identity.name}]" when set, else none) groupChat?: GroupChatConfig; queue?: QueueConfig; /** Emoji reaction used to acknowledge inbound messages (empty disables). */ diff --git a/src/web/auto-reply.test.ts b/src/web/auto-reply.test.ts index 03cf9f2c5..c26470544 100644 --- a/src/web/auto-reply.test.ts +++ b/src/web/auto-reply.test.ts @@ -2000,4 +2000,43 @@ describe("web auto-reply", () => { expect(resolverArg.Body).not.toContain("[clawdbot]"); resetLoadConfigMock(); }); + + it("uses identity.name for responsePrefix when set", async () => { + setLoadConfigMock(() => ({ + identity: { name: "Richbot", emoji: "🦁" }, + whatsapp: { allowFrom: ["*"] }, + })); + + let capturedOnMessage: + | ((msg: import("./inbound.js").WebInboundMessage) => Promise) + | undefined; + const reply = vi.fn(); + const listenerFactory = async (opts: { + onMessage: ( + msg: import("./inbound.js").WebInboundMessage, + ) => Promise; + }) => { + capturedOnMessage = opts.onMessage; + return { close: vi.fn() }; + }; + + const resolver = vi.fn().mockResolvedValue({ text: "hello there" }); + + await monitorWebProvider(false, listenerFactory, false, resolver); + expect(capturedOnMessage).toBeDefined(); + + await capturedOnMessage?.({ + body: "hi", + from: "+1555", + to: "+2666", + id: "msg1", + sendComposing: vi.fn(), + reply, + sendMedia: vi.fn(), + }); + + // Reply should have identity-based responsePrefix prepended + expect(reply).toHaveBeenCalledWith("[Richbot] hello there"); + resetLoadConfigMock(); + }); }); diff --git a/src/web/auto-reply.ts b/src/web/auto-reply.ts index cc3435fb1..cb573ce26 100644 --- a/src/web/auto-reply.ts +++ b/src/web/auto-reply.ts @@ -1170,9 +1170,15 @@ export async function monitorWebProvider( const textLimit = resolveTextChunkLimit(cfg, "whatsapp"); let didLogHeartbeatStrip = false; let didSendReply = false; + // Derive responsePrefix from identity.name if not explicitly set + const responsePrefix = + cfg.messages?.responsePrefix ?? + (cfg.identity?.name?.trim() + ? `[${cfg.identity.name.trim()}]` + : undefined); const { dispatcher, replyOptions, markDispatchIdle } = createReplyDispatcherWithTyping({ - responsePrefix: cfg.messages?.responsePrefix, + responsePrefix, onHeartbeatStrip: () => { if (!didLogHeartbeatStrip) { didLogHeartbeatStrip = true;