fix: derive prefixes from routed identity (#578) (thanks @p6l-richard)
This commit is contained in:
@@ -56,6 +56,7 @@
|
|||||||
- Telegram: fix grammY fetch type mismatch when injecting `fetch`. (#512) — thanks @YuriNachos
|
- Telegram: fix grammY fetch type mismatch when injecting `fetch`. (#512) — thanks @YuriNachos
|
||||||
- WhatsApp: resolve @lid JIDs via Baileys mapping to unblock inbound messages. (#415)
|
- WhatsApp: resolve @lid JIDs via Baileys mapping to unblock inbound messages. (#415)
|
||||||
- Pairing: replies now include sender ids for Discord/Slack/Signal/iMessage/WhatsApp; pairing list labels them explicitly.
|
- Pairing: replies now include sender ids for Discord/Slack/Signal/iMessage/WhatsApp; pairing list labels them explicitly.
|
||||||
|
- Messages: default inbound/outbound prefixes from the routed agent’s `identity.name` when set. (#578) — thanks @p6l-richard
|
||||||
- Signal: accept UUID-only senders for pairing/allowlists/routing when sourceNumber is missing. (#523) — thanks @neist
|
- Signal: accept UUID-only senders for pairing/allowlists/routing when sourceNumber is missing. (#523) — thanks @neist
|
||||||
- Agent system prompt: avoid automatic self-updates unless explicitly requested.
|
- Agent system prompt: avoid automatic self-updates unless explicitly requested.
|
||||||
- Onboarding: tighten QuickStart hint copy for configuring later.
|
- Onboarding: tighten QuickStart hint copy for configuring later.
|
||||||
|
|||||||
@@ -935,6 +935,14 @@ Controls inbound/outbound prefixes and optional ack reactions.
|
|||||||
`responsePrefix` is applied to **all outbound replies** (tool summaries, block
|
`responsePrefix` is applied to **all outbound replies** (tool summaries, block
|
||||||
streaming, final replies) across providers unless already present.
|
streaming, final replies) across providers unless already present.
|
||||||
|
|
||||||
|
If `messages.responsePrefix` is unset and the routed agent has `identity.name`
|
||||||
|
set, Clawdbot defaults the prefix to `[{identity.name}]`.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
`ackReaction` sends a best-effort emoji reaction to acknowledge inbound messages
|
`ackReaction` sends a best-effort emoji reaction to acknowledge inbound messages
|
||||||
on providers that support reactions (Slack/Discord/Telegram). Defaults to the
|
on providers that support reactions (Slack/Discord/Telegram). Defaults to the
|
||||||
active agent’s `identity.emoji` when set, otherwise `"👀"`. Set it to `""` to disable.
|
active agent’s `identity.emoji` when set, otherwise `"👀"`. Set it to `""` to disable.
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ When the wizard asks for your personal WhatsApp number, enter the phone you will
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Tip: if you set the routed agent’s `identity.name`, you can omit
|
||||||
|
`messages.responsePrefix` and it will default to `[{identity.name}]`.
|
||||||
|
|
||||||
### Number sourcing tips
|
### Number sourcing tips
|
||||||
- **Local eSIM** from your country's mobile carrier (most reliable)
|
- **Local eSIM** from your country's mobile carrier (most reliable)
|
||||||
- Austria: [hot.at](https://www.hot.at)
|
- Austria: [hot.at](https://www.hot.at)
|
||||||
|
|||||||
@@ -19,3 +19,21 @@ export function resolveAckReaction(
|
|||||||
const emoji = resolveAgentIdentity(cfg, agentId)?.emoji?.trim();
|
const emoji = resolveAgentIdentity(cfg, agentId)?.emoji?.trim();
|
||||||
return emoji || DEFAULT_ACK_REACTION;
|
return emoji || DEFAULT_ACK_REACTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveIdentityNamePrefix(
|
||||||
|
cfg: ClawdbotConfig,
|
||||||
|
agentId: string,
|
||||||
|
): string | undefined {
|
||||||
|
const name = resolveAgentIdentity(cfg, agentId)?.name?.trim();
|
||||||
|
if (!name) return undefined;
|
||||||
|
return `[${name}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveResponsePrefix(
|
||||||
|
cfg: ClawdbotConfig,
|
||||||
|
agentId: string,
|
||||||
|
): string | undefined {
|
||||||
|
const configured = cfg.messages?.responsePrefix;
|
||||||
|
if (configured !== undefined) return configured;
|
||||||
|
return resolveIdentityNamePrefix(cfg, agentId);
|
||||||
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export async function dispatchReplyFromConfig(params: {
|
|||||||
payload,
|
payload,
|
||||||
channel: originatingChannel,
|
channel: originatingChannel,
|
||||||
to: originatingTo,
|
to: originatingTo,
|
||||||
|
sessionKey: ctx.SessionKey,
|
||||||
accountId: ctx.AccountId,
|
accountId: ctx.AccountId,
|
||||||
threadId: ctx.MessageThreadId,
|
threadId: ctx.MessageThreadId,
|
||||||
cfg,
|
cfg,
|
||||||
@@ -106,6 +107,7 @@ export async function dispatchReplyFromConfig(params: {
|
|||||||
payload: reply,
|
payload: reply,
|
||||||
channel: originatingChannel,
|
channel: originatingChannel,
|
||||||
to: originatingTo,
|
to: originatingTo,
|
||||||
|
sessionKey: ctx.SessionKey,
|
||||||
accountId: ctx.AccountId,
|
accountId: ctx.AccountId,
|
||||||
threadId: ctx.MessageThreadId,
|
threadId: ctx.MessageThreadId,
|
||||||
cfg,
|
cfg,
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ export function createFollowupRunner(params: {
|
|||||||
payload,
|
payload,
|
||||||
channel: originatingChannel,
|
channel: originatingChannel,
|
||||||
to: originatingTo,
|
to: originatingTo,
|
||||||
|
sessionKey: queued.run.sessionKey,
|
||||||
accountId: queued.originatingAccountId,
|
accountId: queued.originatingAccountId,
|
||||||
threadId: queued.originatingThreadId,
|
threadId: queued.originatingThreadId,
|
||||||
cfg: queued.run.config,
|
cfg: queued.run.config,
|
||||||
|
|||||||
@@ -99,6 +99,33 @@ describe("routeReply", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("derives responsePrefix from agent identity when routing", async () => {
|
||||||
|
mocks.sendMessageSlack.mockClear();
|
||||||
|
const cfg = {
|
||||||
|
agents: {
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
id: "rich",
|
||||||
|
identity: { name: "Richbot", theme: "lion bot", emoji: "🦁" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
messages: {},
|
||||||
|
} as unknown as ClawdbotConfig;
|
||||||
|
await routeReply({
|
||||||
|
payload: { text: "hi" },
|
||||||
|
channel: "slack",
|
||||||
|
to: "channel:C123",
|
||||||
|
sessionKey: "agent:rich:main",
|
||||||
|
cfg,
|
||||||
|
});
|
||||||
|
expect(mocks.sendMessageSlack).toHaveBeenCalledWith(
|
||||||
|
"channel:C123",
|
||||||
|
"[Richbot] hi",
|
||||||
|
expect.any(Object),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("passes thread id to Telegram sends", async () => {
|
it("passes thread id to Telegram sends", async () => {
|
||||||
mocks.sendMessageTelegram.mockClear();
|
mocks.sendMessageTelegram.mockClear();
|
||||||
await routeReply({
|
await routeReply({
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
* across multiple providers.
|
* across multiple providers.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { resolveAgentIdFromSessionKey } from "../../agents/agent-scope.js";
|
||||||
|
import { resolveResponsePrefix } from "../../agents/identity.js";
|
||||||
import type { ClawdbotConfig } from "../../config/config.js";
|
import type { ClawdbotConfig } from "../../config/config.js";
|
||||||
import { sendMessageDiscord } from "../../discord/send.js";
|
import { sendMessageDiscord } from "../../discord/send.js";
|
||||||
import { sendMessageIMessage } from "../../imessage/send.js";
|
import { sendMessageIMessage } from "../../imessage/send.js";
|
||||||
@@ -26,6 +28,8 @@ export type RouteReplyParams = {
|
|||||||
channel: OriginatingChannelType;
|
channel: OriginatingChannelType;
|
||||||
/** The destination chat/channel/user ID. */
|
/** The destination chat/channel/user ID. */
|
||||||
to: string;
|
to: string;
|
||||||
|
/** Session key for deriving agent identity defaults (multi-agent). */
|
||||||
|
sessionKey?: string;
|
||||||
/** Provider account id (multi-account). */
|
/** Provider account id (multi-account). */
|
||||||
accountId?: string;
|
accountId?: string;
|
||||||
/** Telegram message thread id (forum topics). */
|
/** Telegram message thread id (forum topics). */
|
||||||
@@ -60,8 +64,14 @@ export async function routeReply(
|
|||||||
params;
|
params;
|
||||||
|
|
||||||
// Debug: `pnpm test src/auto-reply/reply/route-reply.test.ts`
|
// Debug: `pnpm test src/auto-reply/reply/route-reply.test.ts`
|
||||||
|
const responsePrefix = params.sessionKey
|
||||||
|
? resolveResponsePrefix(
|
||||||
|
cfg,
|
||||||
|
resolveAgentIdFromSessionKey(params.sessionKey),
|
||||||
|
)
|
||||||
|
: cfg.messages?.responsePrefix;
|
||||||
const normalized = normalizeReplyPayload(payload, {
|
const normalized = normalizeReplyPayload(payload, {
|
||||||
responsePrefix: cfg.messages?.responsePrefix,
|
responsePrefix,
|
||||||
});
|
});
|
||||||
if (!normalized) return { ok: true };
|
if (!normalized) return { ok: true };
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ import { GatewayIntents, GatewayPlugin } from "@buape/carbon/gateway";
|
|||||||
import type { APIAttachment } from "discord-api-types/v10";
|
import type { APIAttachment } from "discord-api-types/v10";
|
||||||
import { ApplicationCommandOptionType, Routes } from "discord-api-types/v10";
|
import { ApplicationCommandOptionType, Routes } from "discord-api-types/v10";
|
||||||
|
|
||||||
import { resolveAckReaction } from "../agents/identity.js";
|
import {
|
||||||
|
resolveAckReaction,
|
||||||
|
resolveResponsePrefix,
|
||||||
|
} from "../agents/identity.js";
|
||||||
import { resolveTextChunkLimit } from "../auto-reply/chunk.js";
|
import { resolveTextChunkLimit } from "../auto-reply/chunk.js";
|
||||||
import { hasControlCommand } from "../auto-reply/command-detection.js";
|
import { hasControlCommand } from "../auto-reply/command-detection.js";
|
||||||
import {
|
import {
|
||||||
@@ -1030,7 +1033,7 @@ export function createDiscordMessageHandler(params: {
|
|||||||
let didSendReply = false;
|
let didSendReply = false;
|
||||||
const { dispatcher, replyOptions, markDispatchIdle } =
|
const { dispatcher, replyOptions, markDispatchIdle } =
|
||||||
createReplyDispatcherWithTyping({
|
createReplyDispatcherWithTyping({
|
||||||
responsePrefix: cfg.messages?.responsePrefix,
|
responsePrefix: resolveResponsePrefix(cfg, route.agentId),
|
||||||
deliver: async (payload) => {
|
deliver: async (payload) => {
|
||||||
await deliverDiscordReply({
|
await deliverDiscordReply({
|
||||||
replies: [payload],
|
replies: [payload],
|
||||||
@@ -1510,7 +1513,7 @@ function createDiscordNativeCommand(params: {
|
|||||||
|
|
||||||
let didReply = false;
|
let didReply = false;
|
||||||
const dispatcher = createReplyDispatcher({
|
const dispatcher = createReplyDispatcher({
|
||||||
responsePrefix: cfg.messages?.responsePrefix,
|
responsePrefix: resolveResponsePrefix(cfg, route.agentId),
|
||||||
deliver: async (payload, _info) => {
|
deliver: async (payload, _info) => {
|
||||||
await deliverDiscordInteractionReply({
|
await deliverDiscordInteractionReply({
|
||||||
interaction,
|
interaction,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { resolveResponsePrefix } from "../agents/identity.js";
|
||||||
import { chunkText, resolveTextChunkLimit } from "../auto-reply/chunk.js";
|
import { chunkText, resolveTextChunkLimit } from "../auto-reply/chunk.js";
|
||||||
import { hasControlCommand } from "../auto-reply/command-detection.js";
|
import { hasControlCommand } from "../auto-reply/command-detection.js";
|
||||||
import { formatAgentEnvelope } from "../auto-reply/envelope.js";
|
import { formatAgentEnvelope } from "../auto-reply/envelope.js";
|
||||||
@@ -421,7 +422,7 @@ export async function monitorIMessageProvider(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dispatcher = createReplyDispatcher({
|
const dispatcher = createReplyDispatcher({
|
||||||
responsePrefix: cfg.messages?.responsePrefix,
|
responsePrefix: resolveResponsePrefix(cfg, route.agentId),
|
||||||
deliver: async (payload) => {
|
deliver: async (payload) => {
|
||||||
await deliverReplies({
|
await deliverReplies({
|
||||||
replies: [payload],
|
replies: [payload],
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { resolveResponsePrefix } from "../agents/identity.js";
|
||||||
import {
|
import {
|
||||||
DEFAULT_HEARTBEAT_ACK_MAX_CHARS,
|
DEFAULT_HEARTBEAT_ACK_MAX_CHARS,
|
||||||
DEFAULT_HEARTBEAT_EVERY,
|
DEFAULT_HEARTBEAT_EVERY,
|
||||||
@@ -268,7 +269,7 @@ export async function runHeartbeatOnce(opts: {
|
|||||||
const ackMaxChars = resolveHeartbeatAckMaxChars(cfg);
|
const ackMaxChars = resolveHeartbeatAckMaxChars(cfg);
|
||||||
const normalized = normalizeHeartbeatReply(
|
const normalized = normalizeHeartbeatReply(
|
||||||
replyPayload,
|
replyPayload,
|
||||||
cfg.messages?.responsePrefix,
|
resolveResponsePrefix(cfg, resolveAgentIdFromSessionKey(sessionKey)),
|
||||||
ackMaxChars,
|
ackMaxChars,
|
||||||
);
|
);
|
||||||
if (normalized.shouldSkip && !normalized.hasMedia) {
|
if (normalized.shouldSkip && !normalized.hasMedia) {
|
||||||
|
|||||||
@@ -448,6 +448,7 @@ function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
|||||||
const { dispatcher, replyOptions, markDispatchIdle } =
|
const { dispatcher, replyOptions, markDispatchIdle } =
|
||||||
createMSTeamsReplyDispatcher({
|
createMSTeamsReplyDispatcher({
|
||||||
cfg,
|
cfg,
|
||||||
|
agentId: route.agentId,
|
||||||
runtime,
|
runtime,
|
||||||
log,
|
log,
|
||||||
adapter,
|
adapter,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { resolveResponsePrefix } from "../agents/identity.js";
|
||||||
import { createReplyDispatcherWithTyping } from "../auto-reply/reply/reply-dispatcher.js";
|
import { createReplyDispatcherWithTyping } from "../auto-reply/reply/reply-dispatcher.js";
|
||||||
import type { ClawdbotConfig, MSTeamsReplyStyle } from "../config/types.js";
|
import type { ClawdbotConfig, MSTeamsReplyStyle } from "../config/types.js";
|
||||||
import { danger } from "../globals.js";
|
import { danger } from "../globals.js";
|
||||||
@@ -18,6 +19,7 @@ import type { MSTeamsTurnContext } from "./sdk-types.js";
|
|||||||
|
|
||||||
export function createMSTeamsReplyDispatcher(params: {
|
export function createMSTeamsReplyDispatcher(params: {
|
||||||
cfg: ClawdbotConfig;
|
cfg: ClawdbotConfig;
|
||||||
|
agentId: string;
|
||||||
runtime: RuntimeEnv;
|
runtime: RuntimeEnv;
|
||||||
log: MSTeamsMonitorLogger;
|
log: MSTeamsMonitorLogger;
|
||||||
adapter: MSTeamsAdapter;
|
adapter: MSTeamsAdapter;
|
||||||
@@ -36,7 +38,7 @@ export function createMSTeamsReplyDispatcher(params: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return createReplyDispatcherWithTyping({
|
return createReplyDispatcherWithTyping({
|
||||||
responsePrefix: params.cfg.messages?.responsePrefix,
|
responsePrefix: resolveResponsePrefix(params.cfg, params.agentId),
|
||||||
deliver: async (payload) => {
|
deliver: async (payload) => {
|
||||||
const messages = renderReplyPayloadsToMessages([payload], {
|
const messages = renderReplyPayloadsToMessages([payload], {
|
||||||
textChunkLimit: params.textLimit,
|
textChunkLimit: params.textLimit,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { resolveResponsePrefix } from "../agents/identity.js";
|
||||||
import { chunkText, resolveTextChunkLimit } from "../auto-reply/chunk.js";
|
import { chunkText, resolveTextChunkLimit } from "../auto-reply/chunk.js";
|
||||||
import { formatAgentEnvelope } from "../auto-reply/envelope.js";
|
import { formatAgentEnvelope } from "../auto-reply/envelope.js";
|
||||||
import { dispatchReplyFromConfig } from "../auto-reply/reply/dispatch-from-config.js";
|
import { dispatchReplyFromConfig } from "../auto-reply/reply/dispatch-from-config.js";
|
||||||
@@ -507,7 +508,7 @@ export async function monitorSignalProvider(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dispatcher = createReplyDispatcher({
|
const dispatcher = createReplyDispatcher({
|
||||||
responsePrefix: cfg.messages?.responsePrefix,
|
responsePrefix: resolveResponsePrefix(cfg, route.agentId),
|
||||||
deliver: async (payload) => {
|
deliver: async (payload) => {
|
||||||
await deliverReplies({
|
await deliverReplies({
|
||||||
replies: [payload],
|
replies: [payload],
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import {
|
|||||||
type SlackEventMiddlewareArgs,
|
type SlackEventMiddlewareArgs,
|
||||||
} from "@slack/bolt";
|
} from "@slack/bolt";
|
||||||
import type { WebClient as SlackWebClient } from "@slack/web-api";
|
import type { WebClient as SlackWebClient } from "@slack/web-api";
|
||||||
import { resolveAckReaction } from "../agents/identity.js";
|
import {
|
||||||
|
resolveAckReaction,
|
||||||
|
resolveResponsePrefix,
|
||||||
|
} from "../agents/identity.js";
|
||||||
import {
|
import {
|
||||||
chunkMarkdownText,
|
chunkMarkdownText,
|
||||||
resolveTextChunkLimit,
|
resolveTextChunkLimit,
|
||||||
@@ -1110,7 +1113,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
|||||||
};
|
};
|
||||||
const { dispatcher, replyOptions, markDispatchIdle } =
|
const { dispatcher, replyOptions, markDispatchIdle } =
|
||||||
createReplyDispatcherWithTyping({
|
createReplyDispatcherWithTyping({
|
||||||
responsePrefix: cfg.messages?.responsePrefix,
|
responsePrefix: resolveResponsePrefix(cfg, route.agentId),
|
||||||
deliver: async (payload) => {
|
deliver: async (payload) => {
|
||||||
await deliverReplies({
|
await deliverReplies({
|
||||||
replies: [payload],
|
replies: [payload],
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import { apiThrottler } from "@grammyjs/transformer-throttler";
|
|||||||
import type { ApiClientOptions, Message } from "grammy";
|
import type { ApiClientOptions, Message } from "grammy";
|
||||||
import { Bot, InputFile, webhookCallback } from "grammy";
|
import { Bot, InputFile, webhookCallback } from "grammy";
|
||||||
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||||
import { resolveAckReaction } from "../agents/identity.js";
|
import {
|
||||||
|
resolveAckReaction,
|
||||||
|
resolveResponsePrefix,
|
||||||
|
} from "../agents/identity.js";
|
||||||
import { EmbeddedBlockChunker } from "../agents/pi-embedded-block-chunker.js";
|
import { EmbeddedBlockChunker } from "../agents/pi-embedded-block-chunker.js";
|
||||||
import {
|
import {
|
||||||
chunkMarkdownText,
|
chunkMarkdownText,
|
||||||
@@ -726,7 +729,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
|||||||
|
|
||||||
const { dispatcher, replyOptions, markDispatchIdle } =
|
const { dispatcher, replyOptions, markDispatchIdle } =
|
||||||
createReplyDispatcherWithTyping({
|
createReplyDispatcherWithTyping({
|
||||||
responsePrefix: cfg.messages?.responsePrefix,
|
responsePrefix: resolveResponsePrefix(cfg, route.agentId),
|
||||||
deliver: async (payload, info) => {
|
deliver: async (payload, info) => {
|
||||||
if (info.kind === "final") {
|
if (info.kind === "final") {
|
||||||
await flushDraft();
|
await flushDraft();
|
||||||
|
|||||||
@@ -1962,7 +1962,28 @@ describe("web auto-reply", () => {
|
|||||||
|
|
||||||
it("uses identity.name for messagePrefix when set", async () => {
|
it("uses identity.name for messagePrefix when set", async () => {
|
||||||
setLoadConfigMock(() => ({
|
setLoadConfigMock(() => ({
|
||||||
identity: { name: "Richbot", emoji: "🦁" },
|
agents: {
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
id: "main",
|
||||||
|
default: true,
|
||||||
|
identity: { name: "Mainbot", emoji: "🦞", theme: "space lobster" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "rich",
|
||||||
|
identity: { name: "Richbot", emoji: "🦁", theme: "lion bot" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
bindings: [
|
||||||
|
{
|
||||||
|
agentId: "rich",
|
||||||
|
match: {
|
||||||
|
provider: "whatsapp",
|
||||||
|
peer: { kind: "dm", id: "+1555" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let capturedOnMessage:
|
let capturedOnMessage:
|
||||||
@@ -2003,8 +2024,28 @@ describe("web auto-reply", () => {
|
|||||||
|
|
||||||
it("uses identity.name for responsePrefix when set", async () => {
|
it("uses identity.name for responsePrefix when set", async () => {
|
||||||
setLoadConfigMock(() => ({
|
setLoadConfigMock(() => ({
|
||||||
identity: { name: "Richbot", emoji: "🦁" },
|
agents: {
|
||||||
whatsapp: { allowFrom: ["*"] },
|
list: [
|
||||||
|
{
|
||||||
|
id: "main",
|
||||||
|
default: true,
|
||||||
|
identity: { name: "Mainbot", emoji: "🦞", theme: "space lobster" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "rich",
|
||||||
|
identity: { name: "Richbot", emoji: "🦁", theme: "lion bot" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
bindings: [
|
||||||
|
{
|
||||||
|
agentId: "rich",
|
||||||
|
match: {
|
||||||
|
provider: "whatsapp",
|
||||||
|
peer: { kind: "dm", id: "+1555" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let capturedOnMessage:
|
let capturedOnMessage:
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import {
|
||||||
|
resolveIdentityNamePrefix,
|
||||||
|
resolveResponsePrefix,
|
||||||
|
} from "../agents/identity.js";
|
||||||
import {
|
import {
|
||||||
chunkMarkdownText,
|
chunkMarkdownText,
|
||||||
resolveTextChunkLimit,
|
resolveTextChunkLimit,
|
||||||
@@ -1032,13 +1036,14 @@ export async function monitorWebProvider(
|
|||||||
return `[Replying to ${sender}${idPart}]\n${msg.replyToBody}\n[/Replying]`;
|
return `[Replying to ${sender}${idPart}]\n${msg.replyToBody}\n[/Replying]`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildLine = (msg: WebInboundMsg) => {
|
const buildLine = (msg: WebInboundMsg, agentId: string) => {
|
||||||
// Build message prefix: explicit config > identity name > default "clawdbot"
|
// Build message prefix: explicit config > identity name > default based on allowFrom
|
||||||
let messagePrefix = cfg.messages?.messagePrefix;
|
let messagePrefix = cfg.messages?.messagePrefix;
|
||||||
if (messagePrefix === undefined) {
|
if (messagePrefix === undefined) {
|
||||||
const hasAllowFrom = (cfg.whatsapp?.allowFrom?.length ?? 0) > 0;
|
const hasAllowFrom = (cfg.whatsapp?.allowFrom?.length ?? 0) > 0;
|
||||||
const identityName = cfg.identity?.name?.trim() || "clawdbot";
|
messagePrefix = hasAllowFrom
|
||||||
messagePrefix = hasAllowFrom ? "" : `[${identityName}]`;
|
? ""
|
||||||
|
: (resolveIdentityNamePrefix(cfg, agentId) ?? "[clawdbot]");
|
||||||
}
|
}
|
||||||
const prefixStr = messagePrefix ? `${messagePrefix} ` : "";
|
const prefixStr = messagePrefix ? `${messagePrefix} ` : "";
|
||||||
const senderLabel =
|
const senderLabel =
|
||||||
@@ -1070,7 +1075,7 @@ export async function monitorWebProvider(
|
|||||||
status.lastEventAt = status.lastMessageAt;
|
status.lastEventAt = status.lastMessageAt;
|
||||||
emitStatus();
|
emitStatus();
|
||||||
const conversationId = msg.conversationId ?? msg.from;
|
const conversationId = msg.conversationId ?? msg.from;
|
||||||
let combinedBody = buildLine(msg);
|
let combinedBody = buildLine(msg, route.agentId);
|
||||||
let shouldClearGroupHistory = false;
|
let shouldClearGroupHistory = false;
|
||||||
|
|
||||||
if (msg.chatType === "group") {
|
if (msg.chatType === "group") {
|
||||||
@@ -1088,7 +1093,10 @@ export async function monitorWebProvider(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.join("\\n");
|
.join("\\n");
|
||||||
combinedBody = `[Chat messages since your last reply - for context]\\n${historyText}\\n\\n[Current message - respond to this]\\n${buildLine(msg)}`;
|
combinedBody = `[Chat messages since your last reply - for context]\\n${historyText}\\n\\n[Current message - respond to this]\\n${buildLine(
|
||||||
|
msg,
|
||||||
|
route.agentId,
|
||||||
|
)}`;
|
||||||
}
|
}
|
||||||
// Always surface who sent the triggering message so the agent can address them.
|
// Always surface who sent the triggering message so the agent can address them.
|
||||||
const senderLabel =
|
const senderLabel =
|
||||||
@@ -1170,12 +1178,7 @@ export async function monitorWebProvider(
|
|||||||
const textLimit = resolveTextChunkLimit(cfg, "whatsapp");
|
const textLimit = resolveTextChunkLimit(cfg, "whatsapp");
|
||||||
let didLogHeartbeatStrip = false;
|
let didLogHeartbeatStrip = false;
|
||||||
let didSendReply = false;
|
let didSendReply = false;
|
||||||
// Derive responsePrefix from identity.name if not explicitly set
|
const responsePrefix = resolveResponsePrefix(cfg, route.agentId);
|
||||||
const responsePrefix =
|
|
||||||
cfg.messages?.responsePrefix ??
|
|
||||||
(cfg.identity?.name?.trim()
|
|
||||||
? `[${cfg.identity.name.trim()}]`
|
|
||||||
: undefined);
|
|
||||||
const { dispatcher, replyOptions, markDispatchIdle } =
|
const { dispatcher, replyOptions, markDispatchIdle } =
|
||||||
createReplyDispatcherWithTyping({
|
createReplyDispatcherWithTyping({
|
||||||
responsePrefix,
|
responsePrefix,
|
||||||
|
|||||||
Reference in New Issue
Block a user