From 87cecd026857b95a5b771aa20c3245c4b31df435 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 17 Jan 2026 04:13:06 +0000 Subject: [PATCH] refactor: align channel chatType --- src/config/types.base.ts | 2 +- src/config/types.tools.ts | 2 +- src/config/zod-schema.core.ts | 7 +++- src/media-understanding/scope.test.ts | 55 +++++++++------------------ src/media-understanding/scope.ts | 7 ++-- src/sessions/send-policy.ts | 9 +++-- src/slack/monitor/context.ts | 2 +- 7 files changed, 36 insertions(+), 48 deletions(-) diff --git a/src/config/types.base.ts b/src/config/types.base.ts index 3c33f16aa..e8778fd89 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" | "room"; + chatType?: "direct" | "group" | "channel" | "room"; keyPrefix?: string; }; export type SessionSendPolicyRule = { diff --git a/src/config/types.tools.ts b/src/config/types.tools.ts index 80f7dab8e..f749c1814 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" | "room"; + chatType?: "direct" | "group" | "channel" | "room"; keyPrefix?: string; }; diff --git a/src/config/zod-schema.core.ts b/src/config/zod-schema.core.ts index 330d55338..1af60b7a5 100644 --- a/src/config/zod-schema.core.ts +++ b/src/config/zod-schema.core.ts @@ -251,7 +251,12 @@ export const MediaUnderstandingScopeSchema = z .object({ channel: z.string().optional(), chatType: z - .union([z.literal("direct"), z.literal("group"), z.literal("room")]) + .union([ + z.literal("direct"), + z.literal("group"), + z.literal("channel"), + z.literal("room"), + ]) .optional(), keyPrefix: z.string().optional(), }) diff --git a/src/media-understanding/scope.test.ts b/src/media-understanding/scope.test.ts index 9331d8577..c2c16249f 100644 --- a/src/media-understanding/scope.test.ts +++ b/src/media-understanding/scope.test.ts @@ -1,46 +1,27 @@ import { describe, expect, it } from "vitest"; -import { resolveMediaUnderstandingScope } from "./scope.js"; -describe("resolveMediaUnderstandingScope", () => { - it("defaults to allow when scope is undefined", () => { - expect(resolveMediaUnderstandingScope({})).toBe("allow"); +import { normalizeMediaUnderstandingChatType, resolveMediaUnderstandingScope } from "./scope.js"; + +describe("media understanding scope", () => { + it("normalizes channel/room", () => { + expect(normalizeMediaUnderstandingChatType("channel")).toBe("channel"); + expect(normalizeMediaUnderstandingChatType("room")).toBe("channel"); }); - it("uses first matching rule", () => { - const decision = resolveMediaUnderstandingScope({ - scope: { - default: "deny", - rules: [ - { action: "allow", match: { channel: "whatsapp" } }, - { action: "deny", match: { channel: "whatsapp", chatType: "direct" } }, - ], - }, - channel: "whatsapp", - chatType: "direct", - sessionKey: "whatsapp:direct:123", - }); - expect(decision).toBe("allow"); + it("treats room match as channel", () => { + const scope = { + rules: [{ action: "deny", match: { chatType: "room" } }], + } as const; + + expect(resolveMediaUnderstandingScope({ scope, chatType: "channel" })).toBe("deny"); }); - it("matches keyPrefix when provided", () => { - const decision = resolveMediaUnderstandingScope({ - scope: { - default: "deny", - rules: [{ action: "allow", match: { keyPrefix: "agent:main:" } }], - }, - sessionKey: "agent:main:whatsapp:group:123", - }); - expect(decision).toBe("allow"); - }); + it("matches channel chatType explicitly", () => { + const scope = { + rules: [{ action: "deny", match: { chatType: "channel" } }], + } as const; - it("matches keyPrefix case-insensitively", () => { - const decision = resolveMediaUnderstandingScope({ - scope: { - default: "deny", - rules: [{ action: "allow", match: { keyPrefix: "agent:main:" } }], - }, - sessionKey: "AGENT:MAIN:WHATSAPP:GROUP:123", - }); - expect(decision).toBe("allow"); + expect(resolveMediaUnderstandingScope({ scope, chatType: "channel" })).toBe("deny"); }); }); + diff --git a/src/media-understanding/scope.ts b/src/media-understanding/scope.ts index 55660fafb..9b8cf2eba 100644 --- a/src/media-understanding/scope.ts +++ b/src/media-understanding/scope.ts @@ -19,7 +19,7 @@ export function normalizeMediaUnderstandingChatType(raw?: string | null): string if (!value) return undefined; if (value === "dm" || value === "direct_message" || value === "private") return "direct"; if (value === "groups") return "group"; - if (value === "channel") return "room"; + if (value === "room") return "channel"; return value; } @@ -33,7 +33,7 @@ export function resolveMediaUnderstandingScope(params: { if (!scope) return "allow"; const channel = normalizeMatch(params.channel); - const chatType = normalizeMatch(params.chatType); + const chatType = normalizeMediaUnderstandingChatType(params.chatType) ?? normalizeMatch(params.chatType); const sessionKey = normalizeMatch(params.sessionKey) ?? ""; for (const rule of scope.rules ?? []) { @@ -41,7 +41,8 @@ export function resolveMediaUnderstandingScope(params: { const action = normalizeDecision(rule.action) ?? "allow"; const match = rule.match ?? {}; const matchChannel = normalizeMatch(match.channel); - const matchChatType = normalizeMatch(match.chatType); + const matchChatType = + normalizeMediaUnderstandingChatType(match.chatType) ?? normalizeMatch(match.chatType); const matchPrefix = normalizeMatch(match.keyPrefix); if (matchChannel && matchChannel !== channel) continue; diff --git a/src/sessions/send-policy.ts b/src/sessions/send-policy.ts index e1da5a2ca..16b4240c0 100644 --- a/src/sessions/send-policy.ts +++ b/src/sessions/send-policy.ts @@ -1,5 +1,6 @@ import type { ClawdbotConfig } from "../config/config.js"; import type { SessionChatType, SessionEntry } from "../config/sessions.js"; +import { normalizeChatType } from "../channels/chat-type.js"; export type SessionSendPolicyDecision = "allow" | "deny"; @@ -27,7 +28,7 @@ function deriveChannelFromKey(key?: string) { function deriveChatTypeFromKey(key?: string): SessionChatType | undefined { if (!key) return undefined; if (key.startsWith("group:") || key.includes(":group:")) return "group"; - if (key.includes(":channel:")) return "room"; + if (key.includes(":channel:")) return "channel"; return undefined; } @@ -50,8 +51,8 @@ export function resolveSendPolicy(params: { normalizeMatchValue(params.entry?.lastChannel) ?? deriveChannelFromKey(params.sessionKey); const chatType = - normalizeMatchValue(params.chatType ?? params.entry?.chatType) ?? - normalizeMatchValue(deriveChatTypeFromKey(params.sessionKey)); + normalizeChatType(params.chatType ?? params.entry?.chatType) ?? + normalizeChatType(deriveChatTypeFromKey(params.sessionKey)); const sessionKey = params.sessionKey ?? ""; let allowedMatch = false; @@ -60,7 +61,7 @@ export function resolveSendPolicy(params: { const action = normalizeSendPolicy(rule.action) ?? "allow"; const match = rule.match ?? {}; const matchChannel = normalizeMatchValue(match.channel); - const matchChatType = normalizeMatchValue(match.chatType); + const matchChatType = normalizeChatType(match.chatType); const matchPrefix = normalizeMatchValue(match.keyPrefix); if (matchChannel && matchChannel !== channel) continue; diff --git a/src/slack/monitor/context.ts b/src/slack/monitor/context.ts index 1b703aa1d..4120a068e 100644 --- a/src/slack/monitor/context.ts +++ b/src/slack/monitor/context.ts @@ -186,7 +186,7 @@ export function createSlackMonitorContext(params: { : isGroup ? `slack:group:${channelId}` : `slack:channel:${channelId}`; - const chatType = isDirectMessage ? "direct" : isGroup ? "group" : "room"; + const chatType = isDirectMessage ? "direct" : isGroup ? "group" : "channel"; return resolveSessionKey( params.sessionScope, { From: from, ChatType: chatType, Provider: "slack" },