diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cb1685e8..7f38e9d34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### Breaking - **BREAKING:** Channel auth now prefers config over env for Discord/Telegram/Matrix (env is fallback only). (#1040) — thanks @thewilloftheshadow. - **BREAKING:** `clawdbot message` and message tool now require `target` (dropping `to`/`channelId` for destinations). (#1034) — thanks @tobalsan. +- **BREAKING:** Drop legacy `chatType: "room"` support; use `chatType: "channel"`. ### Changes - Tools: improve `web_fetch` extraction using Readability (with fallback). diff --git a/src/agents/tools/session-status-tool.ts b/src/agents/tools/session-status-tool.ts index 45bfe4328..bb1f2616c 100644 --- a/src/agents/tools/session-status-tool.ts +++ b/src/agents/tools/session-status-tool.ts @@ -309,7 +309,6 @@ export function createSessionStatusTool(opts?: { const isGroup = resolved.entry.chatType === "group" || resolved.entry.chatType === "channel" || - resolved.entry.chatType === "room" || resolved.key.startsWith("group:") || resolved.key.includes(":group:") || resolved.key.includes(":channel:"); diff --git a/src/auto-reply/reply/get-reply-run.ts b/src/auto-reply/reply/get-reply-run.ts index 1cf023312..c6e5d1091 100644 --- a/src/auto-reply/reply/get-reply-run.ts +++ b/src/auto-reply/reply/get-reply-run.ts @@ -299,8 +299,7 @@ export async function runPreparedReply( }); const isGroupSession = sessionEntry?.chatType === "group" || - sessionEntry?.chatType === "channel" || - sessionEntry?.chatType === "room"; + sessionEntry?.chatType === "channel"; const isMainSession = !isGroupSession && sessionKey === normalizeMainKey(sessionCfg?.mainKey); prefixedBodyBase = await prependSystemEvents({ cfg, diff --git a/src/auto-reply/reply/inbound-context.test.ts b/src/auto-reply/reply/inbound-context.test.ts index 2cfbbb805..a8155122d 100644 --- a/src/auto-reply/reply/inbound-context.test.ts +++ b/src/auto-reply/reply/inbound-context.test.ts @@ -8,7 +8,7 @@ describe("finalizeInboundContext", () => { const ctx: MsgContext = { Body: "a\\nb\r\nc", RawBody: "raw\\nline", - ChatType: "room", + ChatType: "channel", From: "group:123@g.us", GroupSubject: "Test", }; @@ -35,4 +35,3 @@ describe("finalizeInboundContext", () => { expect(ctx.BodyForCommands).toBe("say hi"); }); }); - diff --git a/src/auto-reply/status.ts b/src/auto-reply/status.ts index b77280c30..66e444456 100644 --- a/src/auto-reply/status.ts +++ b/src/auto-reply/status.ts @@ -254,7 +254,6 @@ export function buildStatusMessage(args: StatusArgs): string { const isGroupSession = entry?.chatType === "group" || entry?.chatType === "channel" || - entry?.chatType === "room" || Boolean(args.sessionKey?.includes(":group:")) || Boolean(args.sessionKey?.includes(":channel:")) || Boolean(args.sessionKey?.startsWith("group:")); diff --git a/src/channels/chat-type.test.ts b/src/channels/chat-type.test.ts index dff0d1f7e..c7ceef7e9 100644 --- a/src/channels/chat-type.test.ts +++ b/src/channels/chat-type.test.ts @@ -8,13 +8,12 @@ describe("normalizeChatType", () => { expect(normalizeChatType("dm")).toBe("direct"); expect(normalizeChatType("group")).toBe("group"); expect(normalizeChatType("channel")).toBe("channel"); - expect(normalizeChatType("room")).toBe("channel"); }); it("returns undefined for empty/unknown values", () => { expect(normalizeChatType(undefined)).toBeUndefined(); expect(normalizeChatType("")).toBeUndefined(); expect(normalizeChatType("nope")).toBeUndefined(); + expect(normalizeChatType("room")).toBeUndefined(); }); }); - diff --git a/src/channels/chat-type.ts b/src/channels/chat-type.ts index 9e13a0ae5..5aba48dbf 100644 --- a/src/channels/chat-type.ts +++ b/src/channels/chat-type.ts @@ -5,7 +5,6 @@ export function normalizeChatType(raw?: string): NormalizedChatType | undefined if (!value) return undefined; if (value === "direct" || value === "dm") return "direct"; if (value === "group") return "group"; - if (value === "channel" || value === "room") return "channel"; + if (value === "channel") return "channel"; return undefined; } - diff --git a/src/commands/sessions.ts b/src/commands/sessions.ts index e18451924..11a1bc884 100644 --- a/src/commands/sessions.ts +++ b/src/commands/sessions.ts @@ -110,7 +110,7 @@ const formatAge = (ms: number | null | undefined) => { function classifyKey(key: string, entry?: SessionEntry): SessionRow["kind"] { if (key === "global") return "global"; if (key === "unknown") return "unknown"; - if (entry?.chatType === "group" || entry?.chatType === "channel" || entry?.chatType === "room") { + if (entry?.chatType === "group" || entry?.chatType === "channel") { return "group"; } if (key.startsWith("group:") || key.includes(":group:") || key.includes(":channel:")) { diff --git a/src/commands/status.summary.ts b/src/commands/status.summary.ts index f310bacc2..8eb73b74b 100644 --- a/src/commands/status.summary.ts +++ b/src/commands/status.summary.ts @@ -19,7 +19,7 @@ import type { HeartbeatStatus, SessionStatus, StatusSummary } from "./status.typ const classifyKey = (key: string, entry?: SessionEntry): SessionStatus["kind"] => { if (key === "global") return "global"; if (key === "unknown") return "unknown"; - if (entry?.chatType === "group" || entry?.chatType === "channel" || entry?.chatType === "room") { + if (entry?.chatType === "group" || entry?.chatType === "channel") { return "group"; } if (key.startsWith("group:") || key.includes(":group:") || key.includes(":channel:")) { diff --git a/src/config/sessions/types.ts b/src/config/sessions/types.ts index a95e33ab2..f30354680 100644 --- a/src/config/sessions/types.ts +++ b/src/config/sessions/types.ts @@ -10,9 +10,7 @@ export type SessionChannelId = ChannelId | "webchat"; export type SessionChatType = | "direct" | "group" - | "channel" - // Legacy alias for "channel". - | "room"; + | "channel"; export type SessionEntry = { /** diff --git a/src/config/types.base.ts b/src/config/types.base.ts index e8778fd89..0757783b1 100644 --- a/src/config/types.base.ts +++ b/src/config/types.base.ts @@ -41,7 +41,7 @@ export type HumanDelayConfig = { export type SessionSendPolicyAction = "allow" | "deny"; export type SessionSendPolicyMatch = { channel?: string; - chatType?: "direct" | "group" | "channel" | "room"; + chatType?: "direct" | "group" | "channel"; keyPrefix?: string; }; export type SessionSendPolicyRule = { diff --git a/src/config/types.tools.ts b/src/config/types.tools.ts index 1b4c32418..f16bdfb44 100644 --- a/src/config/types.tools.ts +++ b/src/config/types.tools.ts @@ -2,7 +2,7 @@ import type { AgentElevatedAllowFromConfig, SessionSendPolicyAction } from "./ty export type MediaUnderstandingScopeMatch = { channel?: string; - chatType?: "direct" | "group" | "channel" | "room"; + chatType?: "direct" | "group" | "channel"; keyPrefix?: string; }; diff --git a/src/config/zod-schema.core.ts b/src/config/zod-schema.core.ts index c8479ef37..ce5a637ec 100644 --- a/src/config/zod-schema.core.ts +++ b/src/config/zod-schema.core.ts @@ -255,7 +255,6 @@ export const MediaUnderstandingScopeSchema = z z.literal("direct"), z.literal("group"), z.literal("channel"), - z.literal("room"), ]) .optional(), keyPrefix: z.string().optional(), diff --git a/src/config/zod-schema.session.ts b/src/config/zod-schema.session.ts index 1f3e6ff9f..bba312ad0 100644 --- a/src/config/zod-schema.session.ts +++ b/src/config/zod-schema.session.ts @@ -43,8 +43,6 @@ export const SessionSchema = z z.literal("direct"), z.literal("group"), z.literal("channel"), - // Legacy alias for "channel". - z.literal("room"), ]) .optional(), keyPrefix: z.string().optional(), diff --git a/src/gateway/session-utils.ts b/src/gateway/session-utils.ts index 2e8847067..099de7eeb 100644 --- a/src/gateway/session-utils.ts +++ b/src/gateway/session-utils.ts @@ -59,7 +59,7 @@ export function loadSessionEntry(sessionKey: string) { export function classifySessionKey(key: string, entry?: SessionEntry): GatewaySessionRow["kind"] { if (key === "global") return "global"; if (key === "unknown") return "unknown"; - if (entry?.chatType === "group" || entry?.chatType === "channel" || entry?.chatType === "room") { + if (entry?.chatType === "group" || entry?.chatType === "channel") { return "group"; } if (key.startsWith("group:") || key.includes(":group:") || key.includes(":channel:")) { diff --git a/src/gateway/session-utils.types.ts b/src/gateway/session-utils.types.ts index 50a60b8c9..d69c7b342 100644 --- a/src/gateway/session-utils.types.ts +++ b/src/gateway/session-utils.types.ts @@ -15,7 +15,7 @@ export type GatewaySessionRow = { subject?: string; room?: string; space?: string; - chatType?: "direct" | "group" | "channel" | "room"; + chatType?: "direct" | "group" | "channel"; updatedAt: number | null; sessionId?: string; systemSent?: boolean; diff --git a/src/media-understanding/scope.test.ts b/src/media-understanding/scope.test.ts index c2c16249f..3f9bff48a 100644 --- a/src/media-understanding/scope.test.ts +++ b/src/media-understanding/scope.test.ts @@ -3,17 +3,10 @@ import { describe, expect, it } from "vitest"; import { normalizeMediaUnderstandingChatType, resolveMediaUnderstandingScope } from "./scope.js"; describe("media understanding scope", () => { - it("normalizes channel/room", () => { + it("normalizes chatType", () => { expect(normalizeMediaUnderstandingChatType("channel")).toBe("channel"); - expect(normalizeMediaUnderstandingChatType("room")).toBe("channel"); - }); - - it("treats room match as channel", () => { - const scope = { - rules: [{ action: "deny", match: { chatType: "room" } }], - } as const; - - expect(resolveMediaUnderstandingScope({ scope, chatType: "channel" })).toBe("deny"); + expect(normalizeMediaUnderstandingChatType("dm")).toBe("direct"); + expect(normalizeMediaUnderstandingChatType("room")).toBeUndefined(); }); it("matches channel chatType explicitly", () => { @@ -24,4 +17,3 @@ describe("media understanding scope", () => { expect(resolveMediaUnderstandingScope({ scope, chatType: "channel" })).toBe("deny"); }); }); - diff --git a/src/media-understanding/scope.ts b/src/media-understanding/scope.ts index 9b8cf2eba..7aa71cc5a 100644 --- a/src/media-understanding/scope.ts +++ b/src/media-understanding/scope.ts @@ -1,4 +1,5 @@ import type { MediaUnderstandingScopeConfig } from "../config/types.tools.js"; +import { normalizeChatType } from "../channels/chat-type.js"; export type MediaUnderstandingScopeDecision = "allow" | "deny"; @@ -15,12 +16,7 @@ function normalizeMatch(value?: string | null): string | undefined { } export function normalizeMediaUnderstandingChatType(raw?: string | null): string | undefined { - const value = raw?.trim().toLowerCase(); - if (!value) return undefined; - if (value === "dm" || value === "direct_message" || value === "private") return "direct"; - if (value === "groups") return "group"; - if (value === "room") return "channel"; - return value; + return normalizeChatType(raw ?? undefined); } export function resolveMediaUnderstandingScope(params: { @@ -33,7 +29,7 @@ export function resolveMediaUnderstandingScope(params: { if (!scope) return "allow"; const channel = normalizeMatch(params.channel); - const chatType = normalizeMediaUnderstandingChatType(params.chatType) ?? normalizeMatch(params.chatType); + const chatType = normalizeMediaUnderstandingChatType(params.chatType); const sessionKey = normalizeMatch(params.sessionKey) ?? ""; for (const rule of scope.rules ?? []) { @@ -41,8 +37,7 @@ export function resolveMediaUnderstandingScope(params: { const action = normalizeDecision(rule.action) ?? "allow"; const match = rule.match ?? {}; const matchChannel = normalizeMatch(match.channel); - const matchChatType = - normalizeMediaUnderstandingChatType(match.chatType) ?? normalizeMatch(match.chatType); + const matchChatType = normalizeMediaUnderstandingChatType(match.chatType); const matchPrefix = normalizeMatch(match.keyPrefix); if (matchChannel && matchChannel !== channel) continue;