diff --git a/docs/channels/feishu.md b/docs/channels/feishu.md index e15feafe34..461facdbb2 100644 --- a/docs/channels/feishu.md +++ b/docs/channels/feishu.md @@ -490,14 +490,14 @@ Use `bindings` to route Feishu DMs or groups to different agents. agentId: "main", match: { channel: "feishu", - peer: { kind: "dm", id: "ou_xxx" }, + peer: { kind: "direct", id: "ou_xxx" }, }, }, { agentId: "clawd-fan", match: { channel: "feishu", - peer: { kind: "dm", id: "ou_yyy" }, + peer: { kind: "direct", id: "ou_yyy" }, }, }, { @@ -514,7 +514,7 @@ Use `bindings` to route Feishu DMs or groups to different agents. Routing fields: - `match.channel`: `"feishu"` -- `match.peer.kind`: `"dm"` or `"group"` +- `match.peer.kind`: `"direct"` or `"group"` - `match.peer.id`: user Open ID (`ou_xxx`) or group ID (`oc_xxx`) See [Get group/user IDs](#get-groupuser-ids) for lookup tips. diff --git a/docs/channels/whatsapp.md b/docs/channels/whatsapp.md index 966c0902aa..0586c5ad17 100644 --- a/docs/channels/whatsapp.md +++ b/docs/channels/whatsapp.md @@ -196,7 +196,7 @@ Pairing is a DM gate for unknown senders: - Codes expire after 1 hour; pending requests are capped at 3 per channel. **Can multiple people use different OpenClaw instances on one WhatsApp number?** -Yes, by routing each sender to a different agent via `bindings` (peer `kind: "dm"`, sender E.164 like `+15551234567`). Replies still come from the **same WhatsApp account**, and direct chats collapse to each agent’s main session, so use **one agent per person**. DM access control (`dmPolicy`/`allowFrom`) is global per WhatsApp account. See [Multi-Agent Routing](/concepts/multi-agent). +Yes, by routing each sender to a different agent via `bindings` (peer `kind: "direct"`, sender E.164 like `+15551234567`). Replies still come from the **same WhatsApp account**, and direct chats collapse to each agent's main session, so use **one agent per person**. DM access control (`dmPolicy`/`allowFrom`) is global per WhatsApp account. See [Multi-Agent Routing](/concepts/multi-agent). **Why do you ask for my phone number in the wizard?** The wizard uses it to set your **allowlist/owner** so your own DMs are permitted. It’s not used for auto-sending. If you run on your personal WhatsApp number, use that same number and enable `channels.whatsapp.selfChatMode`. diff --git a/docs/concepts/multi-agent.md b/docs/concepts/multi-agent.md index 6f8bad589d..027654a900 100644 --- a/docs/concepts/multi-agent.md +++ b/docs/concepts/multi-agent.md @@ -82,7 +82,7 @@ This lets **multiple people** share one Gateway server while keeping their AI ## One WhatsApp number, multiple people (DM split) -You can route **different WhatsApp DMs** to different agents while staying on **one WhatsApp account**. Match on sender E.164 (like `+15551234567`) with `peer.kind: "dm"`. Replies still come from the same WhatsApp number (no per‑agent sender identity). +You can route **different WhatsApp DMs** to different agents while staying on **one WhatsApp account**. Match on sender E.164 (like `+15551234567`) with `peer.kind: "direct"`. Replies still come from the same WhatsApp number (no per‑agent sender identity). Important detail: direct chats collapse to the agent’s **main session key**, so true isolation requires **one agent per person**. @@ -97,8 +97,14 @@ Example: ], }, bindings: [ - { agentId: "alex", match: { channel: "whatsapp", peer: { kind: "dm", id: "+15551230001" } } }, - { agentId: "mia", match: { channel: "whatsapp", peer: { kind: "dm", id: "+15551230002" } } }, + { + agentId: "alex", + match: { channel: "whatsapp", peer: { kind: "direct", id: "+15551230001" } }, + }, + { + agentId: "mia", + match: { channel: "whatsapp", peer: { kind: "direct", id: "+15551230002" } }, + }, ], channels: { whatsapp: { @@ -260,7 +266,10 @@ Keep WhatsApp on the fast agent, but route one DM to Opus: ], }, bindings: [ - { agentId: "opus", match: { channel: "whatsapp", peer: { kind: "dm", id: "+15551234567" } } }, + { + agentId: "opus", + match: { channel: "whatsapp", peer: { kind: "direct", id: "+15551234567" } }, + }, { agentId: "chat", match: { channel: "whatsapp" } }, ], } diff --git a/docs/concepts/session.md b/docs/concepts/session.md index 503dcf37f4..54dfb21327 100644 --- a/docs/concepts/session.md +++ b/docs/concepts/session.md @@ -106,7 +106,7 @@ the workspace is writable. See [Memory](/concepts/memory) and - Daily reset: defaults to **4:00 AM local time on the gateway host**. A session is stale once its last update is earlier than the most recent daily reset time. - Idle reset (optional): `idleMinutes` adds a sliding idle window. When both daily and idle resets are configured, **whichever expires first** forces a new session. - Legacy idle-only: if you set `session.idleMinutes` without any `session.reset`/`resetByType` config, OpenClaw stays in idle-only mode for backward compatibility. -- Per-type overrides (optional): `resetByType` lets you override the policy for `dm`, `group`, and `thread` sessions (thread = Slack/Discord threads, Telegram topics, Matrix threads when provided by the connector). +- Per-type overrides (optional): `resetByType` lets you override the policy for `direct`, `group`, and `thread` sessions (thread = Slack/Discord threads, Telegram topics, Matrix threads when provided by the connector). - Per-channel overrides (optional): `resetByChannel` overrides the reset policy for a channel (applies to all session types for that channel and takes precedence over `reset`/`resetByType`). - Reset triggers: exact `/new` or `/reset` (plus any extras in `resetTriggers`) start a fresh session id and pass the remainder of the message through. `/new ` accepts a model alias, `provider/model`, or provider name (fuzzy match) to set the new session model. If `/new` or `/reset` is sent alone, OpenClaw runs a short “hello” greeting turn to confirm the reset. - Manual reset: delete specific keys from the store or remove the JSONL transcript; the next message recreates them. @@ -157,7 +157,7 @@ Runtime override (owner only): }, resetByType: { thread: { mode: "daily", atHour: 4 }, - dm: { mode: "idle", idleMinutes: 240 }, + direct: { mode: "idle", idleMinutes: 240 }, group: { mode: "idle", idleMinutes: 120 }, }, resetByChannel: { diff --git a/docs/gateway/configuration.md b/docs/gateway/configuration.md index 35302f9bc3..8bb61e65c0 100644 --- a/docs/gateway/configuration.md +++ b/docs/gateway/configuration.md @@ -765,7 +765,7 @@ Inbound messages are routed to an agent via bindings. - `bindings[]`: routes inbound messages to an `agentId`. - `match.channel` (required) - `match.accountId` (optional; `*` = any account; omitted = default account) - - `match.peer` (optional; `{ kind: dm|group|channel, id }`) + - `match.peer` (optional; `{ kind: direct|group|channel, id }`) - `match.guildId` / `match.teamId` (optional; channel-specific) Deterministic match order: @@ -2760,7 +2760,7 @@ Controls session scoping, reset policy, reset triggers, and where the session st }, resetByType: { thread: { mode: "daily", atHour: 4 }, - dm: { mode: "idle", idleMinutes: 240 }, + direct: { mode: "idle", idleMinutes: 240 }, group: { mode: "idle", idleMinutes: 120 }, }, resetTriggers: ["/new", "/reset"], @@ -2797,7 +2797,7 @@ Fields: - `mode`: `daily` or `idle` (default: `daily` when `reset` is present). - `atHour`: local hour (0-23) for the daily reset boundary. - `idleMinutes`: sliding idle window in minutes. When daily + idle are both configured, whichever expires first wins. -- `resetByType`: per-session overrides for `dm`, `group`, and `thread`. +- `resetByType`: per-session overrides for `direct`, `group`, and `thread`. Legacy `dm` key is accepted as an alias for `direct`. - If you only set legacy `session.idleMinutes` without any `reset`/`resetByType`, OpenClaw stays in idle-only mode for backward compatibility. - `heartbeatIdleMinutes`: optional idle override for heartbeat checks (daily reset still applies when enabled). - `agentToAgent.maxPingPongTurns`: max reply-back turns between requester/target (0–5, default 5). diff --git a/docs/help/faq.md b/docs/help/faq.md index a2179cf0bf..dd24ff2b41 100644 --- a/docs/help/faq.md +++ b/docs/help/faq.md @@ -803,7 +803,7 @@ See [/channels/telegram](/channels/telegram#access-control-dms--groups). ### Can multiple people use one WhatsApp number with different OpenClaw instances -Yes, via **multi-agent routing**. Bind each sender's WhatsApp **DM** (peer `kind: "dm"`, sender E.164 like `+15551234567`) to a different `agentId`, so each person gets their own workspace and session store. Replies still come from the **same WhatsApp account**, and DM access control (`channels.whatsapp.dmPolicy` / `channels.whatsapp.allowFrom`) is global per WhatsApp account. See [Multi-Agent Routing](/concepts/multi-agent) and [WhatsApp](/channels/whatsapp). +Yes, via **multi-agent routing**. Bind each sender's WhatsApp **DM** (peer `kind: "direct"`, sender E.164 like `+15551234567`) to a different `agentId`, so each person gets their own workspace and session store. Replies still come from the **same WhatsApp account**, and DM access control (`channels.whatsapp.dmPolicy` / `channels.whatsapp.allowFrom`) is global per WhatsApp account. See [Multi-Agent Routing](/concepts/multi-agent) and [WhatsApp](/channels/whatsapp). ### Can I run a fast chat agent and an Opus for coding agent diff --git a/docs/refactor/plugin-sdk.md b/docs/refactor/plugin-sdk.md index 4c9d5910ce..4722644083 100644 --- a/docs/refactor/plugin-sdk.md +++ b/docs/refactor/plugin-sdk.md @@ -72,7 +72,7 @@ export type PluginRuntime = { cfg: unknown; channel: string; accountId: string; - peer: { kind: "dm" | "group" | "channel"; id: string }; + peer: { kind: RoutePeerKind; id: string }; }): { sessionKey: string; accountId: string }; }; pairing: { diff --git a/docs/zh-CN/refactor/plugin-sdk.md b/docs/zh-CN/refactor/plugin-sdk.md index 7142aa9f8b..fc2e742059 100644 --- a/docs/zh-CN/refactor/plugin-sdk.md +++ b/docs/zh-CN/refactor/plugin-sdk.md @@ -79,7 +79,7 @@ export type PluginRuntime = { cfg: unknown; channel: string; accountId: string; - peer: { kind: "dm" | "group" | "channel"; id: string }; + peer: { kind: RoutePeerKind; id: string }; }): { sessionKey: string; accountId: string }; }; pairing: { diff --git a/extensions/bluebubbles/src/monitor.ts b/extensions/bluebubbles/src/monitor.ts index 8738f64304..173337bfe9 100644 --- a/extensions/bluebubbles/src/monitor.ts +++ b/extensions/bluebubbles/src/monitor.ts @@ -1804,7 +1804,7 @@ async function processMessage( channel: "bluebubbles", accountId: account.accountId, peer: { - kind: isGroup ? "group" : "dm", + kind: isGroup ? "group" : "direct", id: peerId, }, }); @@ -2442,7 +2442,7 @@ async function processReaction( channel: "bluebubbles", accountId: account.accountId, peer: { - kind: reaction.isGroup ? "group" : "dm", + kind: reaction.isGroup ? "group" : "direct", id: peerId, }, }); diff --git a/extensions/feishu/src/bot.ts b/extensions/feishu/src/bot.ts index f90b2d4d37..c2fda3ea1d 100644 --- a/extensions/feishu/src/bot.ts +++ b/extensions/feishu/src/bot.ts @@ -652,7 +652,7 @@ export async function handleFeishuMessage(params: { channel: "feishu", accountId: account.accountId, peer: { - kind: isGroup ? "group" : "dm", + kind: isGroup ? "group" : "direct", id: isGroup ? ctx.chatId : ctx.senderOpenId, }, }); diff --git a/extensions/googlechat/src/monitor.ts b/extensions/googlechat/src/monitor.ts index 7ff5e92cd5..431de0a3a3 100644 --- a/extensions/googlechat/src/monitor.ts +++ b/extensions/googlechat/src/monitor.ts @@ -615,7 +615,7 @@ async function processMessageWithPipeline(params: { channel: "googlechat", accountId: account.accountId, peer: { - kind: isGroup ? "group" : "dm", + kind: isGroup ? "group" : "direct", id: spaceId, }, }); diff --git a/extensions/matrix/src/matrix/monitor/handler.ts b/extensions/matrix/src/matrix/monitor/handler.ts index a9a4e373ca..367f60a195 100644 --- a/extensions/matrix/src/matrix/monitor/handler.ts +++ b/extensions/matrix/src/matrix/monitor/handler.ts @@ -453,7 +453,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam cfg, channel: "matrix", peer: { - kind: isDirectMessage ? "dm" : "channel", + kind: isDirectMessage ? "direct" : "channel", id: isDirectMessage ? senderId : roomId, }, }); diff --git a/extensions/mattermost/src/mattermost/monitor.ts b/extensions/mattermost/src/mattermost/monitor.ts index 93ab3067eb..1a6c9d9e5e 100644 --- a/extensions/mattermost/src/mattermost/monitor.ts +++ b/extensions/mattermost/src/mattermost/monitor.ts @@ -1,5 +1,6 @@ import type { ChannelAccountSnapshot, + ChatType, OpenClawConfig, ReplyPayload, RuntimeEnv, @@ -131,13 +132,13 @@ function isSystemPost(post: MattermostPost): boolean { return Boolean(type); } -function channelKind(channelType?: string | null): "dm" | "group" | "channel" { +function channelKind(channelType?: string | null): ChatType { if (!channelType) { return "channel"; } const normalized = channelType.trim().toUpperCase(); if (normalized === "D") { - return "dm"; + return "direct"; } if (normalized === "G") { return "group"; @@ -145,8 +146,8 @@ function channelKind(channelType?: string | null): "dm" | "group" | "channel" { return "channel"; } -function channelChatType(kind: "dm" | "group" | "channel"): "direct" | "group" | "channel" { - if (kind === "dm") { +function channelChatType(kind: ChatType): "direct" | "group" | "channel" { + if (kind === "direct") { return "direct"; } if (kind === "group") { @@ -469,11 +470,11 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} hasControlCommand, }); const commandAuthorized = - kind === "dm" + kind === "direct" ? dmPolicy === "open" || senderAllowedForCommands : commandGate.commandAuthorized; - if (kind === "dm") { + if (kind === "direct") { if (dmPolicy === "disabled") { logVerboseMessage(`mattermost: drop dm (dmPolicy=disabled sender=${senderId})`); return; @@ -524,7 +525,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} } } - if (kind !== "dm" && commandGate.shouldBlock) { + if (kind !== "direct" && commandGate.shouldBlock) { logInboundDrop({ log: logVerboseMessage, channel: "mattermost", @@ -547,7 +548,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} teamId, peer: { kind, - id: kind === "dm" ? senderId : channelId, + id: kind === "direct" ? senderId : channelId, }, }); @@ -559,11 +560,11 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} parentSessionKey: threadRootId ? baseSessionKey : undefined, }); const sessionKey = threadKeys.sessionKey; - const historyKey = kind === "dm" ? null : sessionKey; + const historyKey = kind === "direct" ? null : sessionKey; const mentionRegexes = core.channel.mentions.buildMentionRegexes(cfg, route.agentId); const wasMentioned = - kind !== "dm" && + kind !== "direct" && ((botUsername ? rawText.toLowerCase().includes(`@${botUsername.toLowerCase()}`) : false) || core.channel.mentions.matchesMentionPatterns(rawText, mentionRegexes)); const pendingBody = @@ -590,7 +591,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} }); }; - const oncharEnabled = account.chatmode === "onchar" && kind !== "dm"; + const oncharEnabled = account.chatmode === "onchar" && kind !== "direct"; const oncharPrefixes = oncharEnabled ? resolveOncharPrefixes(account.oncharPrefixes) : []; const oncharResult = oncharEnabled ? stripOncharPrefix(rawText, oncharPrefixes) @@ -598,7 +599,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} const oncharTriggered = oncharResult.triggered; const shouldRequireMention = - kind !== "dm" && + kind !== "direct" && core.channel.groups.resolveRequireMention({ cfg, channel: "mattermost", @@ -615,7 +616,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} return; } - if (kind !== "dm" && shouldRequireMention && canDetectMention) { + if (kind !== "direct" && shouldRequireMention && canDetectMention) { if (!effectiveWasMentioned) { recordPendingHistory(); return; @@ -637,7 +638,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} }); const fromLabel = formatInboundFromLabel({ - isGroup: kind !== "dm", + isGroup: kind !== "direct", groupLabel: channelDisplay || roomLabel, groupId: channelId, groupFallback: roomLabel || "Channel", @@ -647,7 +648,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} const preview = bodyText.replace(/\s+/g, " ").slice(0, 160); const inboundLabel = - kind === "dm" + kind === "direct" ? `Mattermost DM from ${senderName}` : `Mattermost message in ${roomLabel} from ${senderName}`; core.system.enqueueSystemEvent(`${inboundLabel}: ${preview}`, { @@ -685,14 +686,14 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} }); } - const to = kind === "dm" ? `user:${senderId}` : `channel:${channelId}`; + const to = kind === "direct" ? `user:${senderId}` : `channel:${channelId}`; const mediaPayload = buildMattermostMediaPayload(mediaList); const ctxPayload = core.channel.reply.finalizeInboundContext({ Body: combinedBody, RawBody: bodyText, CommandBody: bodyText, From: - kind === "dm" + kind === "direct" ? `mattermost:${senderId}` : kind === "group" ? `mattermost:group:${channelId}` @@ -703,7 +704,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} AccountId: route.accountId, ChatType: chatType, ConversationLabel: fromLabel, - GroupSubject: kind !== "dm" ? channelDisplay || roomLabel : undefined, + GroupSubject: kind !== "direct" ? channelDisplay || roomLabel : undefined, GroupChannel: channelName ? `#${channelName}` : undefined, GroupSpace: teamId, SenderName: senderName, @@ -718,14 +719,14 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} ReplyToId: threadRootId, MessageThreadId: threadRootId, Timestamp: typeof post.create_at === "number" ? post.create_at : undefined, - WasMentioned: kind !== "dm" ? effectiveWasMentioned : undefined, + WasMentioned: kind !== "direct" ? effectiveWasMentioned : undefined, CommandAuthorized: commandAuthorized, OriginatingChannel: "mattermost" as const, OriginatingTo: to, ...mediaPayload, }); - if (kind === "dm") { + if (kind === "direct") { const sessionCfg = cfg.session; const storePath = core.channel.session.resolveStorePath(sessionCfg?.store, { agentId: route.agentId, diff --git a/extensions/msteams/src/monitor-handler/message-handler.ts b/extensions/msteams/src/monitor-handler/message-handler.ts index c4b84a352f..a24cc05617 100644 --- a/extensions/msteams/src/monitor-handler/message-handler.ts +++ b/extensions/msteams/src/monitor-handler/message-handler.ts @@ -342,7 +342,7 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) { cfg, channel: "msteams", peer: { - kind: isDirectMessage ? "dm" : isChannel ? "channel" : "group", + kind: isDirectMessage ? "direct" : isChannel ? "channel" : "group", id: isDirectMessage ? senderId : conversationId, }, }); diff --git a/extensions/nextcloud-talk/src/inbound.ts b/extensions/nextcloud-talk/src/inbound.ts index 16c477bf6f..983ad3fb9b 100644 --- a/extensions/nextcloud-talk/src/inbound.ts +++ b/extensions/nextcloud-talk/src/inbound.ts @@ -228,7 +228,7 @@ export async function handleNextcloudTalkInbound(params: { channel: CHANNEL_ID, accountId: account.accountId, peer: { - kind: isGroup ? "group" : "dm", + kind: isGroup ? "group" : "direct", id: isGroup ? roomToken : senderId, }, }); diff --git a/extensions/tlon/src/channel.ts b/extensions/tlon/src/channel.ts index a8ab218260..c3e25f49e6 100644 --- a/extensions/tlon/src/channel.ts +++ b/extensions/tlon/src/channel.ts @@ -101,7 +101,7 @@ const tlonOutbound: ChannelOutboundAdapter = { error: new Error(`Invalid Tlon target. Use ${formatTargetHint()}`), }; } - if (parsed.kind === "dm") { + if (parsed.kind === "direct") { return { ok: true, to: parsed.ship }; } return { ok: true, to: parsed.nest }; @@ -127,7 +127,7 @@ const tlonOutbound: ChannelOutboundAdapter = { try { const fromShip = normalizeShip(account.ship); - if (parsed.kind === "dm") { + if (parsed.kind === "direct") { return await sendDm({ api, fromShip, @@ -298,7 +298,7 @@ export const tlonPlugin: ChannelPlugin = { if (!parsed) { return target.trim(); } - if (parsed.kind === "dm") { + if (parsed.kind === "direct") { return parsed.ship; } return parsed.nest; diff --git a/extensions/tlon/src/monitor/index.ts b/extensions/tlon/src/monitor/index.ts index 7a696fab30..f4e13ad7ac 100644 --- a/extensions/tlon/src/monitor/index.ts +++ b/extensions/tlon/src/monitor/index.ts @@ -343,7 +343,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise { } as OpenClawConfig; expect(getDmHistoryLimitFromSessionKey("telegram:dm:123", config)).toBe(5); }); + + describe("backward compatibility", () => { + it("accepts both legacy :dm: and new :direct: session keys", () => { + const config = { + channels: { telegram: { dmHistoryLimit: 10 } }, + } as OpenClawConfig; + // Legacy format with :dm: + expect(getDmHistoryLimitFromSessionKey("telegram:dm:123", config)).toBe(10); + expect(getDmHistoryLimitFromSessionKey("agent:main:telegram:dm:123", config)).toBe(10); + // New format with :direct: + expect(getDmHistoryLimitFromSessionKey("telegram:direct:123", config)).toBe(10); + expect(getDmHistoryLimitFromSessionKey("agent:main:telegram:direct:123", config)).toBe(10); + }); + }); }); diff --git a/src/agents/pi-embedded-runner/history.ts b/src/agents/pi-embedded-runner/history.ts index e34ee4ab89..0340c315cc 100644 --- a/src/agents/pi-embedded-runner/history.ts +++ b/src/agents/pi-embedded-runner/history.ts @@ -58,7 +58,8 @@ export function getDmHistoryLimitFromSessionKey( const kind = providerParts[1]?.toLowerCase(); const userIdRaw = providerParts.slice(2).join(":"); const userId = stripThreadSuffix(userIdRaw); - if (kind !== "dm") { + // Accept both "direct" (new) and "dm" (legacy) for backward compat + if (kind !== "direct" && kind !== "dm") { return undefined; } diff --git a/src/agents/tools/cron-tool.ts b/src/agents/tools/cron-tool.ts index cc5cab54f6..137fdd8749 100644 --- a/src/agents/tools/cron-tool.ts +++ b/src/agents/tools/cron-tool.ts @@ -190,15 +190,16 @@ function inferDeliveryFromSessionKey(agentSessionKey?: string): CronDelivery | n } // buildAgentPeerSessionKey encodes peers as: - // - dm: - // - :dm: - // - ::dm: + // - direct: + // - :direct: + // - ::direct: // - :group: // - :channel: + // Note: legacy keys may use "dm" instead of "direct". // Threaded sessions append :thread:, which we strip so delivery targets the parent peer. // NOTE: Telegram forum topics encode as :topic: and should be preserved. const markerIndex = parts.findIndex( - (part) => part === "dm" || part === "group" || part === "channel", + (part) => part === "direct" || part === "dm" || part === "group" || part === "channel", ); if (markerIndex === -1) { return null; diff --git a/src/auto-reply/reply/session.test.ts b/src/auto-reply/reply/session.test.ts index 6d6b93d5f8..41fb3e9611 100644 --- a/src/auto-reply/reply/session.test.ts +++ b/src/auto-reply/reply/session.test.ts @@ -454,7 +454,7 @@ describe("initSessionState channel reset overrides", () => { session: { store: storePath, idleMinutes: 60, - resetByType: { dm: { mode: "idle", idleMinutes: 10 } }, + resetByType: { direct: { mode: "idle", idleMinutes: 10 } }, resetByChannel: { discord: { mode: "idle", idleMinutes: 10080 } }, }, } as OpenClawConfig; diff --git a/src/channels/chat-type.test.ts b/src/channels/chat-type.test.ts index e5893419a7..6775c8cac8 100644 --- a/src/channels/chat-type.test.ts +++ b/src/channels/chat-type.test.ts @@ -15,4 +15,13 @@ describe("normalizeChatType", () => { expect(normalizeChatType("nope")).toBeUndefined(); expect(normalizeChatType("room")).toBeUndefined(); }); + + describe("backward compatibility", () => { + it("accepts legacy 'dm' value and normalizes to 'direct'", () => { + // Legacy config/input may use "dm" - ensure smooth upgrade path + expect(normalizeChatType("dm")).toBe("direct"); + expect(normalizeChatType("DM")).toBe("direct"); + expect(normalizeChatType(" dm ")).toBe("direct"); + }); + }); }); diff --git a/src/channels/chat-type.ts b/src/channels/chat-type.ts index 3cc49e2c60..c9be141adf 100644 --- a/src/channels/chat-type.ts +++ b/src/channels/chat-type.ts @@ -1,6 +1,6 @@ -export type NormalizedChatType = "direct" | "group" | "channel"; +export type ChatType = "direct" | "group" | "channel"; -export function normalizeChatType(raw?: string): NormalizedChatType | undefined { +export function normalizeChatType(raw?: string): ChatType | undefined { const value = raw?.trim().toLowerCase(); if (!value) { return undefined; diff --git a/src/channels/plugins/types.core.ts b/src/channels/plugins/types.core.ts index d640b389fe..b46150cbf0 100644 --- a/src/channels/plugins/types.core.ts +++ b/src/channels/plugins/types.core.ts @@ -4,7 +4,7 @@ import type { MsgContext } from "../../auto-reply/templating.js"; import type { OpenClawConfig } from "../../config/config.js"; import type { PollInput } from "../../polls.js"; import type { GatewayClientMode, GatewayClientName } from "../../utils/message-channel.js"; -import type { NormalizedChatType } from "../chat-type.js"; +import type { ChatType } from "../chat-type.js"; import type { ChatChannelId } from "../registry.js"; import type { ChannelMessageActionName as ChannelMessageActionNameFromList } from "./message-action-names.js"; @@ -162,7 +162,7 @@ export type ChannelGroupContext = { }; export type ChannelCapabilities = { - chatTypes: Array; + chatTypes: Array; polls?: boolean; reactions?: boolean; edit?: boolean; diff --git a/src/config/config.nix-integration-u3-u5-u9.test.ts b/src/config/config.nix-integration-u3-u5-u9.test.ts index 8dc7cc9fe0..499ddad36d 100644 --- a/src/config/config.nix-integration-u3-u5-u9.test.ts +++ b/src/config/config.nix-integration-u3-u5-u9.test.ts @@ -49,29 +49,37 @@ describe("Nix integration (U3, U5, U9)", () => { }); }); - it("STATE_DIR respects OPENCLAW_HOME when state override is unset", async () => { - await withEnvOverride( - { OPENCLAW_HOME: "/custom/home", OPENCLAW_STATE_DIR: undefined }, - async () => { - const { STATE_DIR } = await import("./config.js"); - expect(STATE_DIR).toBe(path.resolve("/custom/home/.openclaw")); - }, - ); - }); + // Skip on Windows: these tests use Unix-style paths that only make sense on Nix/Unix + it.skipIf(process.platform === "win32")( + "STATE_DIR respects OPENCLAW_HOME when state override is unset", + async () => { + await withEnvOverride( + { OPENCLAW_HOME: "/custom/home", OPENCLAW_STATE_DIR: undefined }, + async () => { + const { STATE_DIR } = await import("./config.js"); + expect(STATE_DIR).toBe(path.resolve("/custom/home/.openclaw")); + }, + ); + }, + ); - it("CONFIG_PATH defaults to OPENCLAW_HOME/.openclaw/openclaw.json", async () => { - await withEnvOverride( - { - OPENCLAW_HOME: "/custom/home", - OPENCLAW_CONFIG_PATH: undefined, - OPENCLAW_STATE_DIR: undefined, - }, - async () => { - const { CONFIG_PATH } = await import("./config.js"); - expect(CONFIG_PATH).toBe(path.resolve("/custom/home/.openclaw/openclaw.json")); - }, - ); - }); + // Skip on Windows: these tests use Unix-style paths that only make sense on Nix/Unix + it.skipIf(process.platform === "win32")( + "CONFIG_PATH defaults to OPENCLAW_HOME/.openclaw/openclaw.json", + async () => { + await withEnvOverride( + { + OPENCLAW_HOME: "/custom/home", + OPENCLAW_CONFIG_PATH: undefined, + OPENCLAW_STATE_DIR: undefined, + }, + async () => { + const { CONFIG_PATH } = await import("./config.js"); + expect(CONFIG_PATH).toBe(path.resolve("/custom/home/.openclaw/openclaw.json")); + }, + ); + }, + ); it("CONFIG_PATH defaults to ~/.openclaw/openclaw.json when env not set", async () => { await withEnvOverride( diff --git a/src/config/sessions/reset.test.ts b/src/config/sessions/reset.test.ts new file mode 100644 index 0000000000..01962a887e --- /dev/null +++ b/src/config/sessions/reset.test.ts @@ -0,0 +1,72 @@ +import { describe, expect, it } from "vitest"; +import type { SessionConfig } from "../types.base.js"; +import { resolveSessionResetPolicy } from "./reset.js"; + +describe("resolveSessionResetPolicy", () => { + describe("backward compatibility: resetByType.dm → direct", () => { + it("uses resetByType.direct when available", () => { + const sessionCfg = { + resetByType: { + direct: { mode: "idle" as const, idleMinutes: 30 }, + }, + } satisfies SessionConfig; + + const policy = resolveSessionResetPolicy({ + sessionCfg, + resetType: "direct", + }); + + expect(policy.mode).toBe("idle"); + expect(policy.idleMinutes).toBe(30); + }); + + it("falls back to resetByType.dm (legacy) when direct is missing", () => { + // Simulating legacy config with "dm" key instead of "direct" + const sessionCfg = { + resetByType: { + dm: { mode: "idle" as const, idleMinutes: 45 }, + }, + } as unknown as SessionConfig; + + const policy = resolveSessionResetPolicy({ + sessionCfg, + resetType: "direct", + }); + + expect(policy.mode).toBe("idle"); + expect(policy.idleMinutes).toBe(45); + }); + + it("prefers resetByType.direct over resetByType.dm when both present", () => { + const sessionCfg = { + resetByType: { + direct: { mode: "daily" as const }, + dm: { mode: "idle" as const, idleMinutes: 99 }, + }, + } as unknown as SessionConfig; + + const policy = resolveSessionResetPolicy({ + sessionCfg, + resetType: "direct", + }); + + expect(policy.mode).toBe("daily"); + }); + + it("does not use dm fallback for group/thread types", () => { + const sessionCfg = { + resetByType: { + dm: { mode: "idle" as const, idleMinutes: 45 }, + }, + } as unknown as SessionConfig; + + const groupPolicy = resolveSessionResetPolicy({ + sessionCfg, + resetType: "group", + }); + + // Should use default mode since group has no config and dm doesn't apply + expect(groupPolicy.mode).toBe("daily"); + }); + }); +}); diff --git a/src/config/sessions/reset.ts b/src/config/sessions/reset.ts index ca856fb788..50fb911148 100644 --- a/src/config/sessions/reset.ts +++ b/src/config/sessions/reset.ts @@ -3,7 +3,7 @@ import { normalizeMessageChannel } from "../../utils/message-channel.js"; import { DEFAULT_IDLE_MINUTES } from "./types.js"; export type SessionResetMode = "daily" | "idle"; -export type SessionResetType = "dm" | "group" | "thread"; +export type SessionResetType = "direct" | "group" | "thread"; export type SessionResetPolicy = { mode: SessionResetMode; @@ -46,7 +46,7 @@ export function resolveSessionResetType(params: { if (GROUP_SESSION_MARKERS.some((marker) => normalized.includes(marker))) { return "group"; } - return "dm"; + return "direct"; } export function resolveThreadFlag(params: { @@ -88,7 +88,13 @@ export function resolveSessionResetPolicy(params: { }): SessionResetPolicy { const sessionCfg = params.sessionCfg; const baseReset = params.resetOverride ?? sessionCfg?.reset; - const typeReset = params.resetOverride ? undefined : sessionCfg?.resetByType?.[params.resetType]; + // Backward compat: accept legacy "dm" key as alias for "direct" + const typeReset = params.resetOverride + ? undefined + : (sessionCfg?.resetByType?.[params.resetType] ?? + (params.resetType === "direct" + ? (sessionCfg?.resetByType as { dm?: SessionResetConfig } | undefined)?.dm + : undefined)); const hasExplicitReset = Boolean(baseReset || sessionCfg?.resetByType); const legacyIdleMinutes = params.resetOverride ? undefined : sessionCfg?.idleMinutes; const mode = diff --git a/src/config/sessions/types.ts b/src/config/sessions/types.ts index ec0fadf7c0..fdd77233cc 100644 --- a/src/config/sessions/types.ts +++ b/src/config/sessions/types.ts @@ -1,6 +1,6 @@ import type { Skill } from "@mariozechner/pi-coding-agent"; import crypto from "node:crypto"; -import type { NormalizedChatType } from "../../channels/chat-type.js"; +import type { ChatType } from "../../channels/chat-type.js"; import type { ChannelId } from "../../channels/plugins/types.js"; import type { DeliveryContext } from "../../utils/delivery-context.js"; import type { TtsAutoMode } from "../types.tts.js"; @@ -9,7 +9,7 @@ export type SessionScope = "per-sender" | "global"; export type SessionChannelId = ChannelId | "webchat"; -export type SessionChatType = NormalizedChatType; +export type SessionChatType = ChatType; export type SessionOrigin = { label?: string; diff --git a/src/config/types.agents.ts b/src/config/types.agents.ts index b6cb71bf58..ad4fa78534 100644 --- a/src/config/types.agents.ts +++ b/src/config/types.agents.ts @@ -1,3 +1,4 @@ +import type { ChatType } from "../channels/chat-type.js"; import type { AgentDefaultsConfig } from "./types.agent-defaults.js"; import type { HumanDelayConfig, IdentityConfig } from "./types.base.js"; import type { GroupChatConfig } from "./types.messages.js"; @@ -74,7 +75,7 @@ export type AgentBinding = { match: { channel: string; accountId?: string; - peer?: { kind: "dm" | "group" | "channel"; id: string }; + peer?: { kind: ChatType; id: string }; guildId?: string; teamId?: string; }; diff --git a/src/config/types.base.ts b/src/config/types.base.ts index e7da1ecd8a..9d713b816d 100644 --- a/src/config/types.base.ts +++ b/src/config/types.base.ts @@ -1,4 +1,4 @@ -import type { NormalizedChatType } from "../channels/chat-type.js"; +import type { ChatType } from "../channels/chat-type.js"; export type ReplyMode = "text" | "command"; export type TypingMode = "never" | "instant" | "thinking" | "message"; @@ -50,7 +50,7 @@ export type HumanDelayConfig = { export type SessionSendPolicyAction = "allow" | "deny"; export type SessionSendPolicyMatch = { channel?: string; - chatType?: NormalizedChatType; + chatType?: ChatType; keyPrefix?: string; }; export type SessionSendPolicyRule = { @@ -71,6 +71,8 @@ export type SessionResetConfig = { idleMinutes?: number; }; export type SessionResetByTypeConfig = { + direct?: SessionResetConfig; + /** @deprecated Use `direct` instead. Kept for backward compatibility. */ dm?: SessionResetConfig; group?: SessionResetConfig; thread?: SessionResetConfig; diff --git a/src/config/types.tools.ts b/src/config/types.tools.ts index 36700b6ce0..9bdc5d7c64 100644 --- a/src/config/types.tools.ts +++ b/src/config/types.tools.ts @@ -1,9 +1,9 @@ -import type { NormalizedChatType } from "../channels/chat-type.js"; +import type { ChatType } from "../channels/chat-type.js"; import type { AgentElevatedAllowFromConfig, SessionSendPolicyAction } from "./types.base.js"; export type MediaUnderstandingScopeMatch = { channel?: string; - chatType?: NormalizedChatType; + chatType?: ChatType; keyPrefix?: string; }; diff --git a/src/config/zod-schema.agents.ts b/src/config/zod-schema.agents.ts index 0656e23f64..92947c2a8e 100644 --- a/src/config/zod-schema.agents.ts +++ b/src/config/zod-schema.agents.ts @@ -22,7 +22,13 @@ export const BindingsSchema = z accountId: z.string().optional(), peer: z .object({ - kind: z.union([z.literal("dm"), z.literal("group"), z.literal("channel")]), + kind: z.union([ + z.literal("direct"), + z.literal("group"), + z.literal("channel"), + /** @deprecated Use `direct` instead. Kept for backward compatibility. */ + z.literal("dm"), + ]), id: z.string(), }) .strict() diff --git a/src/config/zod-schema.core.ts b/src/config/zod-schema.core.ts index 627a898733..721d6252c0 100644 --- a/src/config/zod-schema.core.ts +++ b/src/config/zod-schema.core.ts @@ -378,7 +378,13 @@ export const MediaUnderstandingScopeSchema = z .object({ channel: z.string().optional(), chatType: z - .union([z.literal("direct"), z.literal("group"), z.literal("channel")]) + .union([ + z.literal("direct"), + z.literal("group"), + z.literal("channel"), + /** @deprecated Use `direct` instead. Kept for backward compatibility. */ + z.literal("dm"), + ]) .optional(), keyPrefix: z.string().optional(), }) diff --git a/src/config/zod-schema.session.ts b/src/config/zod-schema.session.ts index 136c82d04c..555b921cda 100644 --- a/src/config/zod-schema.session.ts +++ b/src/config/zod-schema.session.ts @@ -27,7 +27,13 @@ export const SessionSendPolicySchema = z .object({ channel: z.string().optional(), chatType: z - .union([z.literal("direct"), z.literal("group"), z.literal("channel")]) + .union([ + z.literal("direct"), + z.literal("group"), + z.literal("channel"), + /** @deprecated Use `direct` instead. Kept for backward compatibility. */ + z.literal("dm"), + ]) .optional(), keyPrefix: z.string().optional(), }) @@ -57,6 +63,8 @@ export const SessionSchema = z reset: SessionResetConfigSchema.optional(), resetByType: z .object({ + direct: SessionResetConfigSchema.optional(), + /** @deprecated Use `direct` instead. Kept for backward compatibility. */ dm: SessionResetConfigSchema.optional(), group: SessionResetConfigSchema.optional(), thread: SessionResetConfigSchema.optional(), diff --git a/src/discord/monitor/message-handler.inbound-contract.test.ts b/src/discord/monitor/message-handler.inbound-contract.test.ts index 9618a0fd25..06638531e3 100644 --- a/src/discord/monitor/message-handler.inbound-contract.test.ts +++ b/src/discord/monitor/message-handler.inbound-contract.test.ts @@ -87,12 +87,12 @@ describe("discord processDiscordMessage inbound contract", () => { guildInfo: null, guildSlug: "", channelConfig: null, - baseSessionKey: "agent:main:discord:dm:u1", + baseSessionKey: "agent:main:discord:direct:u1", route: { agentId: "main", channel: "discord", accountId: "default", - sessionKey: "agent:main:discord:dm:u1", + sessionKey: "agent:main:discord:direct:u1", mainSessionKey: "agent:main:main", // oxlint-disable-next-line typescript/no-explicit-any } as any, diff --git a/src/discord/monitor/message-handler.preflight.ts b/src/discord/monitor/message-handler.preflight.ts index 726a07decd..9b8648c7f1 100644 --- a/src/discord/monitor/message-handler.preflight.ts +++ b/src/discord/monitor/message-handler.preflight.ts @@ -224,7 +224,7 @@ export async function preflightDiscordMessage( accountId: params.accountId, guildId: params.data.guild_id ?? undefined, peer: { - kind: isDirectMessage ? "dm" : isGroupDm ? "group" : "channel", + kind: isDirectMessage ? "direct" : isGroupDm ? "group" : "channel", id: isDirectMessage ? author.id : message.channelId, }, // Pass parent peer for thread binding inheritance diff --git a/src/discord/monitor/native-command.ts b/src/discord/monitor/native-command.ts index 092f4ee06b..7ac58d6e44 100644 --- a/src/discord/monitor/native-command.ts +++ b/src/discord/monitor/native-command.ts @@ -736,7 +736,7 @@ async function dispatchDiscordCommandInteraction(params: { accountId, guildId: interaction.guild?.id ?? undefined, peer: { - kind: isDirectMessage ? "dm" : isGroupDm ? "group" : "channel", + kind: isDirectMessage ? "direct" : isGroupDm ? "group" : "channel", id: isDirectMessage ? user.id : channelId, }, parentPeer: threadParentId ? { kind: "channel", id: threadParentId } : undefined, diff --git a/src/gateway/session-utils.types.ts b/src/gateway/session-utils.types.ts index 503c7713ab..a7939bd1e5 100644 --- a/src/gateway/session-utils.types.ts +++ b/src/gateway/session-utils.types.ts @@ -1,4 +1,4 @@ -import type { NormalizedChatType } from "../channels/chat-type.js"; +import type { ChatType } from "../channels/chat-type.js"; import type { SessionEntry } from "../config/sessions.js"; import type { DeliveryContext } from "../utils/delivery-context.js"; @@ -19,7 +19,7 @@ export type GatewaySessionRow = { subject?: string; groupChannel?: string; space?: string; - chatType?: NormalizedChatType; + chatType?: ChatType; origin?: SessionEntry["origin"]; updatedAt: number | null; sessionId?: string; diff --git a/src/imessage/monitor/monitor-provider.ts b/src/imessage/monitor/monitor-provider.ts index a25b4644e3..6f09ab3f3f 100644 --- a/src/imessage/monitor/monitor-provider.ts +++ b/src/imessage/monitor/monitor-provider.ts @@ -387,7 +387,7 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P channel: "imessage", accountId: accountInfo.accountId, peer: { - kind: isGroup ? "group" : "dm", + kind: isGroup ? "group" : "direct", id: isGroup ? String(chatId ?? "unknown") : normalizeIMessageHandle(sender), }, }); diff --git a/src/infra/outbound/outbound-session.test.ts b/src/infra/outbound/outbound-session.test.ts index 944dbf8692..48da825a5f 100644 --- a/src/infra/outbound/outbound-session.test.ts +++ b/src/infra/outbound/outbound-session.test.ts @@ -43,7 +43,7 @@ describe("resolveOutboundSessionRoute", () => { target: "@alice", }); - expect(route?.sessionKey).toBe("agent:main:telegram:dm:@alice"); + expect(route?.sessionKey).toBe("agent:main:telegram:direct:@alice"); expect(route?.chatType).toBe("direct"); }); @@ -64,7 +64,7 @@ describe("resolveOutboundSessionRoute", () => { target: "user:123", }); - expect(route?.sessionKey).toBe("agent:main:dm:alice"); + expect(route?.sessionKey).toBe("agent:main:direct:alice"); }); it("strips chat_* prefixes for BlueBubbles group session keys", async () => { @@ -88,7 +88,7 @@ describe("resolveOutboundSessionRoute", () => { target: "123456", }); - expect(route?.sessionKey).toBe("agent:main:zalouser:dm:123456"); + expect(route?.sessionKey).toBe("agent:main:zalouser:direct:123456"); expect(route?.chatType).toBe("direct"); }); diff --git a/src/infra/outbound/outbound-session.ts b/src/infra/outbound/outbound-session.ts index c1538b8424..c6c81f99e4 100644 --- a/src/infra/outbound/outbound-session.ts +++ b/src/infra/outbound/outbound-session.ts @@ -1,4 +1,5 @@ import type { MsgContext } from "../../auto-reply/templating.js"; +import type { ChatType } from "../../channels/chat-type.js"; import type { ChannelId } from "../../channels/plugins/types.js"; import type { OpenClawConfig } from "../../config/config.js"; import type { ResolvedMessagingTarget } from "./target-resolver.js"; @@ -6,11 +7,7 @@ import { getChannelPlugin } from "../../channels/plugins/index.js"; import { recordSessionMetaFromInbound, resolveStorePath } from "../../config/sessions.js"; import { parseDiscordTarget } from "../../discord/targets.js"; import { parseIMessageTarget, normalizeIMessageHandle } from "../../imessage/targets.js"; -import { - buildAgentSessionKey, - type RoutePeer, - type RoutePeerKind, -} from "../../routing/resolve-route.js"; +import { buildAgentSessionKey, type RoutePeer } from "../../routing/resolve-route.js"; import { resolveThreadSessionKeys } from "../../routing/session-key.js"; import { resolveSignalPeerId, @@ -94,10 +91,10 @@ function stripKindPrefix(raw: string): string { function inferPeerKind(params: { channel: ChannelId; resolvedTarget?: ResolvedMessagingTarget; -}): RoutePeerKind { +}): ChatType { const resolvedKind = params.resolvedTarget?.kind; if (resolvedKind === "user") { - return "dm"; + return "direct"; } if (resolvedKind === "channel") { return "channel"; @@ -112,7 +109,7 @@ function inferPeerKind(params: { } return "group"; } - return "dm"; + return "direct"; } function buildBaseSessionKey(params: { @@ -205,7 +202,7 @@ async function resolveSlackSession( return null; } const isDm = parsed.kind === "user"; - let peerKind: RoutePeerKind = isDm ? "dm" : "channel"; + let peerKind: ChatType = isDm ? "direct" : "channel"; if (!isDm && /^G/i.test(parsed.id)) { // Slack mpim/group DMs share the G-prefix; detect to align session keys with inbound. const channelType = await resolveSlackChannelType({ @@ -217,7 +214,7 @@ async function resolveSlackSession( peerKind = "group"; } if (channelType === "dm") { - peerKind = "dm"; + peerKind = "direct"; } } const peer: RoutePeer = { @@ -240,14 +237,14 @@ async function resolveSlackSession( sessionKey: threadKeys.sessionKey, baseSessionKey, peer, - chatType: peerKind === "dm" ? "direct" : "channel", + chatType: peerKind === "direct" ? "direct" : "channel", from: - peerKind === "dm" + peerKind === "direct" ? `slack:${parsed.id}` : peerKind === "group" ? `slack:group:${parsed.id}` : `slack:channel:${parsed.id}`, - to: peerKind === "dm" ? `user:${parsed.id}` : `channel:${parsed.id}`, + to: peerKind === "direct" ? `user:${parsed.id}` : `channel:${parsed.id}`, threadId, }; } @@ -261,7 +258,7 @@ function resolveDiscordSession( } const isDm = parsed.kind === "user"; const peer: RoutePeer = { - kind: isDm ? "dm" : "channel", + kind: isDm ? "direct" : "channel", id: parsed.id, }; const baseSessionKey = buildBaseSessionKey({ @@ -312,7 +309,7 @@ function resolveTelegramSession( params.resolvedTarget.kind !== "user"); const peerId = isGroup ? buildTelegramGroupPeerId(chatId, resolvedThreadId) : chatId; const peer: RoutePeer = { - kind: isGroup ? "group" : "dm", + kind: isGroup ? "group" : "direct", id: peerId, }; const baseSessionKey = buildBaseSessionKey({ @@ -342,7 +339,7 @@ function resolveWhatsAppSession( } const isGroup = isWhatsAppGroupJid(normalized); const peer: RoutePeer = { - kind: isGroup ? "group" : "dm", + kind: isGroup ? "group" : "direct", id: normalized, }; const baseSessionKey = buildBaseSessionKey({ @@ -409,7 +406,7 @@ function resolveSignalSession( }); const peerId = sender ? resolveSignalPeerId(sender) : recipient; const displayRecipient = sender ? resolveSignalRecipient(sender) : recipient; - const peer: RoutePeer = { kind: "dm", id: peerId }; + const peer: RoutePeer = { kind: "direct", id: peerId }; const baseSessionKey = buildBaseSessionKey({ cfg: params.cfg, agentId: params.agentId, @@ -436,7 +433,7 @@ function resolveIMessageSession( if (!handle) { return null; } - const peer: RoutePeer = { kind: "dm", id: handle }; + const peer: RoutePeer = { kind: "direct", id: handle }; const baseSessionKey = buildBaseSessionKey({ cfg: params.cfg, agentId: params.agentId, @@ -497,7 +494,7 @@ function resolveMatrixSession( if (!rawId) { return null; } - const peer: RoutePeer = { kind: isUser ? "dm" : "channel", id: rawId }; + const peer: RoutePeer = { kind: isUser ? "direct" : "channel", id: rawId }; const baseSessionKey = buildBaseSessionKey({ cfg: params.cfg, agentId: params.agentId, @@ -533,7 +530,7 @@ function resolveMSTeamsSession( const conversationId = rawId.split(";")[0] ?? rawId; const isChannel = !isUser && /@thread\.tacv2/i.test(conversationId); const peer: RoutePeer = { - kind: isUser ? "dm" : isChannel ? "channel" : "group", + kind: isUser ? "direct" : isChannel ? "channel" : "group", id: conversationId, }; const baseSessionKey = buildBaseSessionKey({ @@ -574,7 +571,7 @@ function resolveMattermostSession( if (!rawId) { return null; } - const peer: RoutePeer = { kind: isUser ? "dm" : "channel", id: rawId }; + const peer: RoutePeer = { kind: isUser ? "direct" : "channel", id: rawId }; const baseSessionKey = buildBaseSessionKey({ cfg: params.cfg, agentId: params.agentId, @@ -619,7 +616,7 @@ function resolveBlueBubblesSession( return null; } const peer: RoutePeer = { - kind: isGroup ? "group" : "dm", + kind: isGroup ? "group" : "direct", id: peerId, }; const baseSessionKey = buildBaseSessionKey({ @@ -680,7 +677,7 @@ function resolveZaloSession( } const isGroup = trimmed.toLowerCase().startsWith("group:"); const peerId = stripKindPrefix(trimmed); - const peer: RoutePeer = { kind: isGroup ? "group" : "dm", id: peerId }; + const peer: RoutePeer = { kind: isGroup ? "group" : "direct", id: peerId }; const baseSessionKey = buildBaseSessionKey({ cfg: params.cfg, agentId: params.agentId, @@ -710,7 +707,7 @@ function resolveZalouserSession( const isGroup = trimmed.toLowerCase().startsWith("group:"); const peerId = stripKindPrefix(trimmed); // Keep DM vs group aligned with inbound sessions for Zalo Personal. - const peer: RoutePeer = { kind: isGroup ? "group" : "dm", id: peerId }; + const peer: RoutePeer = { kind: isGroup ? "group" : "direct", id: peerId }; const baseSessionKey = buildBaseSessionKey({ cfg: params.cfg, agentId: params.agentId, @@ -735,7 +732,7 @@ function resolveNostrSession( if (!trimmed) { return null; } - const peer: RoutePeer = { kind: "dm", id: trimmed }; + const peer: RoutePeer = { kind: "direct", id: trimmed }; const baseSessionKey = buildBaseSessionKey({ cfg: params.cfg, agentId: params.agentId, @@ -798,7 +795,7 @@ function resolveTlonSession( peerId = normalizeTlonShip(trimmed); } - const peer: RoutePeer = { kind: isGroup ? "group" : "dm", id: peerId }; + const peer: RoutePeer = { kind: isGroup ? "group" : "direct", id: peerId }; const baseSessionKey = buildBaseSessionKey({ cfg: params.cfg, agentId: params.agentId, @@ -851,7 +848,7 @@ function resolveFeishuSession( } const peer: RoutePeer = { - kind: isGroup ? "group" : "dm", + kind: isGroup ? "group" : "direct", id: trimmed, }; const baseSessionKey = buildBaseSessionKey({ @@ -893,10 +890,12 @@ function resolveFallbackSession( channel: params.channel, peer, }); - const chatType = peerKind === "dm" ? "direct" : peerKind === "channel" ? "channel" : "group"; + const chatType = peerKind === "direct" ? "direct" : peerKind === "channel" ? "channel" : "group"; const from = - peerKind === "dm" ? `${params.channel}:${peerId}` : `${params.channel}:${peerKind}:${peerId}`; - const toPrefix = peerKind === "dm" ? "user" : "channel"; + peerKind === "direct" + ? `${params.channel}:${peerId}` + : `${params.channel}:${peerKind}:${peerId}`; + const toPrefix = peerKind === "direct" ? "user" : "channel"; return { sessionKey: baseSessionKey, baseSessionKey, diff --git a/src/line/bot-message-context.ts b/src/line/bot-message-context.ts index f11729adef..cb931f857e 100644 --- a/src/line/bot-message-context.ts +++ b/src/line/bot-message-context.ts @@ -154,7 +154,7 @@ export async function buildLineMessageContext(params: BuildLineMessageContextPar channel: "line", accountId: account.accountId, peer: { - kind: isGroup ? "group" : "dm", + kind: isGroup ? "group" : "direct", id: peerId, }, }); @@ -328,7 +328,7 @@ export async function buildLinePostbackContext(params: { channel: "line", accountId: account.accountId, peer: { - kind: isGroup ? "group" : "dm", + kind: isGroup ? "group" : "direct", id: peerId, }, }); diff --git a/src/media-understanding/apply.test.ts b/src/media-understanding/apply.test.ts index c101f06d29..cedbc1c580 100644 --- a/src/media-understanding/apply.test.ts +++ b/src/media-understanding/apply.test.ts @@ -178,7 +178,7 @@ describe("applyMediaUnderstanding", () => { Body: "", MediaUrl: "https://example.com/note.ogg", MediaType: "audio/ogg", - ChatType: "dm", + ChatType: "direct", }; const cfg: OpenClawConfig = { tools: { diff --git a/src/memory/qmd-manager.ts b/src/memory/qmd-manager.ts index e543c48770..e7931c5a05 100644 --- a/src/memory/qmd-manager.ts +++ b/src/memory/qmd-manager.ts @@ -714,7 +714,7 @@ export class QmdMemoryManager implements MemorySearchManager { const parts = normalized.split(":").filter(Boolean); if ( parts.length >= 2 && - (parts[1] === "group" || parts[1] === "channel" || parts[1] === "dm") + (parts[1] === "group" || parts[1] === "channel" || parts[1] === "direct" || parts[1] === "dm") ) { return parts[0]?.toLowerCase(); } diff --git a/src/plugin-sdk/index.ts b/src/plugin-sdk/index.ts index ed452165bf..abc93716a0 100644 --- a/src/plugin-sdk/index.ts +++ b/src/plugin-sdk/index.ts @@ -118,6 +118,9 @@ export { ToolPolicySchema } from "../config/zod-schema.agent-runtime.js"; export type { RuntimeEnv } from "../runtime.js"; export type { WizardPrompter } from "../wizard/prompts.js"; export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js"; +export type { ChatType } from "../channels/chat-type.js"; +/** @deprecated Use ChatType instead */ +export type { RoutePeerKind } from "../routing/resolve-route.js"; export { resolveAckReaction } from "../agents/identity.js"; export type { ReplyPayload } from "../auto-reply/types.js"; export type { ChunkMode } from "../auto-reply/chunk.js"; diff --git a/src/routing/resolve-route.test.ts b/src/routing/resolve-route.test.ts index 3e484ac727..7d99cdb146 100644 --- a/src/routing/resolve-route.test.ts +++ b/src/routing/resolve-route.test.ts @@ -9,7 +9,7 @@ describe("resolveAgentRoute", () => { cfg, channel: "whatsapp", accountId: null, - peer: { kind: "dm", id: "+15551234567" }, + peer: { kind: "direct", id: "+15551234567" }, }); expect(route.agentId).toBe("main"); expect(route.accountId).toBe("default"); @@ -25,9 +25,9 @@ describe("resolveAgentRoute", () => { cfg, channel: "whatsapp", accountId: null, - peer: { kind: "dm", id: "+15551234567" }, + peer: { kind: "direct", id: "+15551234567" }, }); - expect(route.sessionKey).toBe("agent:main:dm:+15551234567"); + expect(route.sessionKey).toBe("agent:main:direct:+15551234567"); }); test("dmScope=per-channel-peer isolates DM sessions per channel and sender", () => { @@ -38,9 +38,9 @@ describe("resolveAgentRoute", () => { cfg, channel: "whatsapp", accountId: null, - peer: { kind: "dm", id: "+15551234567" }, + peer: { kind: "direct", id: "+15551234567" }, }); - expect(route.sessionKey).toBe("agent:main:whatsapp:dm:+15551234567"); + expect(route.sessionKey).toBe("agent:main:whatsapp:direct:+15551234567"); }); test("identityLinks collapses per-peer DM sessions across providers", () => { @@ -56,9 +56,9 @@ describe("resolveAgentRoute", () => { cfg, channel: "telegram", accountId: null, - peer: { kind: "dm", id: "111111111" }, + peer: { kind: "direct", id: "111111111" }, }); - expect(route.sessionKey).toBe("agent:main:dm:alice"); + expect(route.sessionKey).toBe("agent:main:direct:alice"); }); test("identityLinks applies to per-channel-peer DM sessions", () => { @@ -74,9 +74,9 @@ describe("resolveAgentRoute", () => { cfg, channel: "discord", accountId: null, - peer: { kind: "dm", id: "222222222222222222" }, + peer: { kind: "direct", id: "222222222222222222" }, }); - expect(route.sessionKey).toBe("agent:main:discord:dm:alice"); + expect(route.sessionKey).toBe("agent:main:discord:direct:alice"); }); test("peer binding wins over account binding", () => { @@ -87,7 +87,7 @@ describe("resolveAgentRoute", () => { match: { channel: "whatsapp", accountId: "biz", - peer: { kind: "dm", id: "+1000" }, + peer: { kind: "direct", id: "+1000" }, }, }, { @@ -100,7 +100,7 @@ describe("resolveAgentRoute", () => { cfg, channel: "whatsapp", accountId: "biz", - peer: { kind: "dm", id: "+1000" }, + peer: { kind: "direct", id: "+1000" }, }); expect(route.agentId).toBe("a"); expect(route.sessionKey).toBe("agent:a:main"); @@ -177,7 +177,7 @@ describe("resolveAgentRoute", () => { cfg, channel: "whatsapp", accountId: undefined, - peer: { kind: "dm", id: "+1000" }, + peer: { kind: "direct", id: "+1000" }, }); expect(defaultRoute.agentId).toBe("defaultacct"); expect(defaultRoute.matchedBy).toBe("binding.account"); @@ -186,7 +186,7 @@ describe("resolveAgentRoute", () => { cfg, channel: "whatsapp", accountId: "biz", - peer: { kind: "dm", id: "+1000" }, + peer: { kind: "direct", id: "+1000" }, }); expect(otherRoute.agentId).toBe("main"); }); @@ -204,7 +204,7 @@ describe("resolveAgentRoute", () => { cfg, channel: "whatsapp", accountId: "biz", - peer: { kind: "dm", id: "+1000" }, + peer: { kind: "direct", id: "+1000" }, }); expect(route.agentId).toBe("any"); expect(route.matchedBy).toBe("binding.channel"); @@ -220,7 +220,7 @@ describe("resolveAgentRoute", () => { cfg, channel: "whatsapp", accountId: "biz", - peer: { kind: "dm", id: "+1000" }, + peer: { kind: "direct", id: "+1000" }, }); expect(route.agentId).toBe("home"); expect(route.sessionKey).toBe("agent:home:main"); @@ -235,9 +235,9 @@ test("dmScope=per-account-channel-peer isolates DM sessions per account, channel cfg, channel: "telegram", accountId: "tasks", - peer: { kind: "dm", id: "7550356539" }, + peer: { kind: "direct", id: "7550356539" }, }); - expect(route.sessionKey).toBe("agent:main:telegram:tasks:dm:7550356539"); + expect(route.sessionKey).toBe("agent:main:telegram:tasks:direct:7550356539"); }); test("dmScope=per-account-channel-peer uses default accountId when not provided", () => { @@ -248,9 +248,9 @@ test("dmScope=per-account-channel-peer uses default accountId when not provided" cfg, channel: "telegram", accountId: null, - peer: { kind: "dm", id: "7550356539" }, + peer: { kind: "direct", id: "7550356539" }, }); - expect(route.sessionKey).toBe("agent:main:telegram:default:dm:7550356539"); + expect(route.sessionKey).toBe("agent:main:telegram:default:direct:7550356539"); }); describe("parentPeer binding inheritance (thread support)", () => { @@ -409,3 +409,29 @@ describe("parentPeer binding inheritance (thread support)", () => { expect(route.matchedBy).toBe("default"); }); }); + +describe("backward compatibility: peer.kind dm → direct", () => { + test("legacy dm in config matches runtime direct peer", () => { + const cfg: OpenClawConfig = { + bindings: [ + { + agentId: "alex", + match: { + channel: "whatsapp", + // Legacy config uses "dm" instead of "direct" + peer: { kind: "dm", id: "+15551234567" }, + }, + }, + ], + }; + const route = resolveAgentRoute({ + cfg, + channel: "whatsapp", + accountId: null, + // Runtime uses canonical "direct" + peer: { kind: "direct", id: "+15551234567" }, + }); + expect(route.agentId).toBe("alex"); + expect(route.matchedBy).toBe("binding.peer"); + }); +}); diff --git a/src/routing/resolve-route.ts b/src/routing/resolve-route.ts index cd46a1660c..70917841ab 100644 --- a/src/routing/resolve-route.ts +++ b/src/routing/resolve-route.ts @@ -1,5 +1,7 @@ +import type { ChatType } from "../channels/chat-type.js"; import type { OpenClawConfig } from "../config/config.js"; import { resolveDefaultAgentId } from "../agents/agent-scope.js"; +import { normalizeChatType } from "../channels/chat-type.js"; import { listBindings } from "./bindings.js"; import { buildAgentMainSessionKey, @@ -10,10 +12,11 @@ import { sanitizeAgentId, } from "./session-key.js"; -export type RoutePeerKind = "dm" | "group" | "channel"; +/** @deprecated Use ChatType from channels/chat-type.js */ +export type RoutePeerKind = ChatType; export type RoutePeer = { - kind: RoutePeerKind; + kind: ChatType; id: string; }; @@ -89,7 +92,7 @@ export function buildAgentSessionKey(params: { mainKey: DEFAULT_MAIN_KEY, channel, accountId: params.accountId, - peerKind: peer?.kind ?? "dm", + peerKind: peer?.kind ?? "direct", peerId: peer ? normalizeId(peer.id) || "unknown" : null, dmScope: params.dmScope, identityLinks: params.identityLinks, @@ -137,7 +140,8 @@ function matchesPeer( if (!m) { return false; } - const kind = normalizeToken(m.kind); + // Backward compat: normalize "dm" to "direct" in config match rules + const kind = normalizeChatType(m.kind); const id = normalizeId(m.id); if (!kind || !id) { return false; diff --git a/src/routing/session-key.test.ts b/src/routing/session-key.test.ts index 6c3539f73d..0ed385ab20 100644 --- a/src/routing/session-key.test.ts +++ b/src/routing/session-key.test.ts @@ -23,3 +23,19 @@ describe("classifySessionKeyShape", () => { expect(classifySessionKeyShape("subagent:worker")).toBe("legacy_or_alias"); }); }); + +describe("session key backward compatibility", () => { + it("classifies legacy :dm: session keys as valid agent keys", () => { + // Legacy session keys use :dm: instead of :direct: + // Both should be recognized as valid agent keys + expect(classifySessionKeyShape("agent:main:telegram:dm:123456")).toBe("agent"); + expect(classifySessionKeyShape("agent:main:whatsapp:dm:+15551234567")).toBe("agent"); + expect(classifySessionKeyShape("agent:main:discord:dm:user123")).toBe("agent"); + }); + + it("classifies new :direct: session keys as valid agent keys", () => { + expect(classifySessionKeyShape("agent:main:telegram:direct:123456")).toBe("agent"); + expect(classifySessionKeyShape("agent:main:whatsapp:direct:+15551234567")).toBe("agent"); + expect(classifySessionKeyShape("agent:main:discord:direct:user123")).toBe("agent"); + }); +}); diff --git a/src/routing/session-key.ts b/src/routing/session-key.ts index ad1d16431a..052a1ff2f7 100644 --- a/src/routing/session-key.ts +++ b/src/routing/session-key.ts @@ -1,3 +1,4 @@ +import type { ChatType } from "../channels/chat-type.js"; import { parseAgentSessionKey, type ParsedAgentSessionKey } from "../sessions/session-key-utils.js"; export { @@ -140,14 +141,14 @@ export function buildAgentPeerSessionKey(params: { mainKey?: string | undefined; channel: string; accountId?: string | null; - peerKind?: "dm" | "group" | "channel" | null; + peerKind?: ChatType | null; peerId?: string | null; identityLinks?: Record; /** DM session scope. */ dmScope?: "main" | "per-peer" | "per-channel-peer" | "per-account-channel-peer"; }): string { - const peerKind = params.peerKind ?? "dm"; - if (peerKind === "dm") { + const peerKind = params.peerKind ?? "direct"; + if (peerKind === "direct") { const dmScope = params.dmScope ?? "main"; let peerId = (params.peerId ?? "").trim(); const linkedPeerId = @@ -165,14 +166,14 @@ export function buildAgentPeerSessionKey(params: { if (dmScope === "per-account-channel-peer" && peerId) { const channel = (params.channel ?? "").trim().toLowerCase() || "unknown"; const accountId = normalizeAccountId(params.accountId); - return `agent:${normalizeAgentId(params.agentId)}:${channel}:${accountId}:dm:${peerId}`; + return `agent:${normalizeAgentId(params.agentId)}:${channel}:${accountId}:direct:${peerId}`; } if (dmScope === "per-channel-peer" && peerId) { const channel = (params.channel ?? "").trim().toLowerCase() || "unknown"; - return `agent:${normalizeAgentId(params.agentId)}:${channel}:dm:${peerId}`; + return `agent:${normalizeAgentId(params.agentId)}:${channel}:direct:${peerId}`; } if (dmScope === "per-peer" && peerId) { - return `agent:${normalizeAgentId(params.agentId)}:dm:${peerId}`; + return `agent:${normalizeAgentId(params.agentId)}:direct:${peerId}`; } return buildAgentMainSessionKey({ agentId: params.agentId, diff --git a/src/signal/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts b/src/signal/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts index 5d29bee6d1..b67d30788d 100644 --- a/src/signal/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts +++ b/src/signal/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts @@ -414,7 +414,7 @@ describe("monitorSignalProvider tool results", () => { cfg: config as OpenClawConfig, channel: "signal", accountId: "default", - peer: { kind: "dm", id: normalizeE164("+15550001111") }, + peer: { kind: "direct", id: normalizeE164("+15550001111") }, }); const events = peekSystemEvents(route.sessionKey); expect(events.some((text) => text.includes("Signal reaction added"))).toBe(true); @@ -470,7 +470,7 @@ describe("monitorSignalProvider tool results", () => { cfg: config as OpenClawConfig, channel: "signal", accountId: "default", - peer: { kind: "dm", id: normalizeE164("+15550001111") }, + peer: { kind: "direct", id: normalizeE164("+15550001111") }, }); const events = peekSystemEvents(route.sessionKey); expect(events.some((text) => text.includes("Signal reaction added"))).toBe(true); diff --git a/src/signal/monitor/event-handler.ts b/src/signal/monitor/event-handler.ts index d60658d339..d971392223 100644 --- a/src/signal/monitor/event-handler.ts +++ b/src/signal/monitor/event-handler.ts @@ -77,7 +77,7 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { channel: "signal", accountId: deps.accountId, peer: { - kind: entry.isGroup ? "group" : "dm", + kind: entry.isGroup ? "group" : "direct", id: entry.isGroup ? (entry.groupId ?? "unknown") : entry.senderPeerId, }, }); @@ -370,7 +370,7 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { channel: "signal", accountId: deps.accountId, peer: { - kind: isGroup ? "group" : "dm", + kind: isGroup ? "group" : "direct", id: isGroup ? (groupId ?? "unknown") : senderPeerId, }, }); diff --git a/src/slack/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts b/src/slack/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts index d17684c31f..4e6169ba29 100644 --- a/src/slack/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts +++ b/src/slack/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts @@ -120,7 +120,7 @@ describe("monitorSlackProvider tool results", () => { bindings: [ { agentId: "rich", - match: { channel: "slack", peer: { kind: "dm", id: "U1" } }, + match: { channel: "slack", peer: { kind: "direct", id: "U1" } }, }, ], messages: { diff --git a/src/slack/monitor/message-handler/prepare.ts b/src/slack/monitor/message-handler/prepare.ts index 4ab3ffa7f5..b8dd949f8c 100644 --- a/src/slack/monitor/message-handler/prepare.ts +++ b/src/slack/monitor/message-handler/prepare.ts @@ -188,7 +188,7 @@ export async function prepareSlackMessage(params: { accountId: account.accountId, teamId: ctx.teamId || undefined, peer: { - kind: isDirectMessage ? "dm" : isRoom ? "channel" : "group", + kind: isDirectMessage ? "direct" : isRoom ? "channel" : "group", id: isDirectMessage ? (message.user ?? "unknown") : message.channel, }, }); diff --git a/src/slack/monitor/slash.ts b/src/slack/monitor/slash.ts index b59952b6d2..09c211c8e3 100644 --- a/src/slack/monitor/slash.ts +++ b/src/slack/monitor/slash.ts @@ -373,7 +373,7 @@ export function registerSlackMonitorSlashCommands(params: { accountId: account.accountId, teamId: ctx.teamId || undefined, peer: { - kind: isDirectMessage ? "dm" : isRoom ? "channel" : "group", + kind: isDirectMessage ? "direct" : isRoom ? "channel" : "group", id: isDirectMessage ? command.user_id : command.channel_id, }, }); diff --git a/src/telegram/bot-handlers.ts b/src/telegram/bot-handlers.ts index 58e1a7acab..86abbae7ad 100644 --- a/src/telegram/bot-handlers.ts +++ b/src/telegram/bot-handlers.ts @@ -163,7 +163,7 @@ export const registerTelegramHandlers = ({ channel: "telegram", accountId, peer: { - kind: params.isGroup ? "group" : "dm", + kind: params.isGroup ? "group" : "direct", id: peerId, }, parentPeer, diff --git a/src/telegram/bot-message-context.ts b/src/telegram/bot-message-context.ts index 745100119b..328f6c4b4e 100644 --- a/src/telegram/bot-message-context.ts +++ b/src/telegram/bot-message-context.ts @@ -168,7 +168,7 @@ export const buildTelegramMessageContext = async ({ channel: "telegram", accountId: account.accountId, peer: { - kind: isGroup ? "group" : "dm", + kind: isGroup ? "group" : "direct", id: peerId, }, parentPeer, diff --git a/src/telegram/bot-native-commands.ts b/src/telegram/bot-native-commands.ts index 26eee0e451..d004650c22 100644 --- a/src/telegram/bot-native-commands.ts +++ b/src/telegram/bot-native-commands.ts @@ -495,7 +495,7 @@ export const registerTelegramNativeCommands = ({ channel: "telegram", accountId, peer: { - kind: isGroup ? "group" : "dm", + kind: isGroup ? "group" : "direct", id: isGroup ? buildTelegramGroupPeerId(chatId, resolvedThreadId) : String(chatId), }, parentPeer, diff --git a/src/telegram/bot.ts b/src/telegram/bot.ts index a34cb45ac6..ef03ad343c 100644 --- a/src/telegram/bot.ts +++ b/src/telegram/bot.ts @@ -453,7 +453,7 @@ export function createTelegramBot(opts: TelegramBotOptions) { cfg, channel: "telegram", accountId: account.accountId, - peer: { kind: isGroup ? "group" : "dm", id: peerId }, + peer: { kind: isGroup ? "group" : "direct", id: peerId }, parentPeer, }); const sessionKey = route.sessionKey; diff --git a/src/web/auto-reply.web-auto-reply.sends-tool-summaries-immediately-responseprefix.test.ts b/src/web/auto-reply.web-auto-reply.sends-tool-summaries-immediately-responseprefix.test.ts index f689b7a33d..f7e3405cb0 100644 --- a/src/web/auto-reply.web-auto-reply.sends-tool-summaries-immediately-responseprefix.test.ts +++ b/src/web/auto-reply.web-auto-reply.sends-tool-summaries-immediately-responseprefix.test.ts @@ -164,7 +164,7 @@ describe("web auto-reply", () => { agentId: "rich", match: { channel: "whatsapp", - peer: { kind: "dm", id: "+1555" }, + peer: { kind: "direct", id: "+1555" }, }, }, ], @@ -223,7 +223,7 @@ describe("web auto-reply", () => { agentId: "rich", match: { channel: "whatsapp", - peer: { kind: "dm", id: "+1555" }, + peer: { kind: "direct", id: "+1555" }, }, }, ], diff --git a/src/web/auto-reply/monitor/broadcast.ts b/src/web/auto-reply/monitor/broadcast.ts index f1ed5ac211..058adda23a 100644 --- a/src/web/auto-reply/monitor/broadcast.ts +++ b/src/web/auto-reply/monitor/broadcast.ts @@ -60,7 +60,7 @@ export async function maybeBroadcastMessage(params: { channel: "whatsapp", accountId: params.route.accountId, peer: { - kind: params.msg.chatType === "group" ? "group" : "dm", + kind: params.msg.chatType === "group" ? "group" : "direct", id: params.peerId, }, dmScope: params.cfg.session?.dmScope, diff --git a/src/web/auto-reply/monitor/on-message.ts b/src/web/auto-reply/monitor/on-message.ts index c34ded5d79..28ded02876 100644 --- a/src/web/auto-reply/monitor/on-message.ts +++ b/src/web/auto-reply/monitor/on-message.ts @@ -68,7 +68,7 @@ export function createWebOnMessageHandler(params: { channel: "whatsapp", accountId: msg.accountId, peer: { - kind: msg.chatType === "group" ? "group" : "dm", + kind: msg.chatType === "group" ? "group" : "direct", id: peerId, }, }); diff --git a/tsconfig.json b/tsconfig.json index 2235157723..060982ee20 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,6 @@ "noEmitOnError": true, "outDir": "dist", "resolveJsonModule": true, - "rootDir": "src", "skipLibCheck": true, "strict": true, "target": "es2023",