refactor!: rename chat providers to channels

This commit is contained in:
Peter Steinberger
2026-01-13 06:16:43 +00:00
parent 0cd632ba84
commit 90342a4f3a
393 changed files with 8004 additions and 6737 deletions

View File

@@ -126,18 +126,20 @@ describe("resolveTextChunkLimit", () => {
});
it("supports provider overrides", () => {
const cfg = { telegram: { textChunkLimit: 1234 } };
const cfg = { channels: { telegram: { textChunkLimit: 1234 } } };
expect(resolveTextChunkLimit(cfg, "whatsapp")).toBe(4000);
expect(resolveTextChunkLimit(cfg, "telegram")).toBe(1234);
});
it("prefers account overrides when provided", () => {
const cfg = {
telegram: {
textChunkLimit: 2000,
accounts: {
default: { textChunkLimit: 1234 },
primary: { textChunkLimit: 777 },
channels: {
telegram: {
textChunkLimit: 2000,
accounts: {
default: { textChunkLimit: 1234 },
primary: { textChunkLimit: 777 },
},
},
},
};
@@ -147,8 +149,10 @@ describe("resolveTextChunkLimit", () => {
it("uses the matching provider override", () => {
const cfg = {
discord: { textChunkLimit: 111 },
slack: { textChunkLimit: 222 },
channels: {
discord: { textChunkLimit: 111 },
slack: { textChunkLimit: 222 },
},
};
expect(resolveTextChunkLimit(cfg, "discord")).toBe(111);
expect(resolveTextChunkLimit(cfg, "slack")).toBe(222);

View File

@@ -2,17 +2,17 @@
// unintentionally breaking on newlines. Using [\s\S] keeps newlines inside
// the chunk so messages are only split when they truly exceed the limit.
import type { ChannelId } from "../channels/plugins/types.js";
import type { ClawdbotConfig } from "../config/config.js";
import {
findFenceSpanAt,
isSafeFenceBreak,
parseFenceSpans,
} from "../markdown/fences.js";
import type { ProviderId } from "../providers/plugins/types.js";
import { normalizeAccountId } from "../routing/session-key.js";
import { INTERNAL_MESSAGE_PROVIDER } from "../utils/message-provider.js";
import { INTERNAL_MESSAGE_CHANNEL } from "../utils/message-channel.js";
export type TextChunkProvider = ProviderId | typeof INTERNAL_MESSAGE_PROVIDER;
export type TextChunkProvider = ChannelId | typeof INTERNAL_MESSAGE_CHANNEL;
const DEFAULT_CHUNK_LIMIT = 4000;
@@ -55,10 +55,12 @@ export function resolveTextChunkLimit(
? opts.fallbackLimit
: DEFAULT_CHUNK_LIMIT;
const providerOverride = (() => {
if (!provider || provider === INTERNAL_MESSAGE_PROVIDER) return undefined;
const providerConfig = (cfg as Record<string, unknown> | undefined)?.[
provider
] as ProviderChunkConfig | undefined;
if (!provider || provider === INTERNAL_MESSAGE_CHANNEL) return undefined;
const channelsConfig = cfg?.channels as Record<string, unknown> | undefined;
const providerConfig = (channelsConfig?.[provider] ??
(cfg as Record<string, unknown> | undefined)?.[provider]) as
| ProviderChunkConfig
| undefined;
return resolveChunkLimitForProvider(providerConfig, accountId);
})();
if (typeof providerOverride === "number" && providerOverride > 0) {

View File

@@ -7,7 +7,7 @@ import type { MsgContext } from "./templating.js";
describe("resolveCommandAuthorization", () => {
it("falls back from empty SenderId to SenderE164", () => {
const cfg = {
whatsapp: { allowFrom: ["+123"] },
channels: { whatsapp: { allowFrom: ["+123"] } },
} as ClawdbotConfig;
const ctx = {

View File

@@ -1,12 +1,12 @@
import type { ChannelDock } from "../channels/dock.js";
import { getChannelDock, listChannelDocks } from "../channels/dock.js";
import type { ChannelId } from "../channels/plugins/types.js";
import { normalizeChannelId } from "../channels/registry.js";
import type { ClawdbotConfig } from "../config/config.js";
import type { ProviderDock } from "../providers/dock.js";
import { getProviderDock, listProviderDocks } from "../providers/dock.js";
import type { ProviderId } from "../providers/plugins/types.js";
import { normalizeProviderId } from "../providers/registry.js";
import type { MsgContext } from "./templating.js";
export type CommandAuthorization = {
providerId?: ProviderId;
providerId?: ChannelId;
ownerList: string[];
senderId?: string;
isAuthorizedSender: boolean;
@@ -17,20 +17,20 @@ export type CommandAuthorization = {
function resolveProviderFromContext(
ctx: MsgContext,
cfg: ClawdbotConfig,
): ProviderId | undefined {
): ChannelId | undefined {
const direct =
normalizeProviderId(ctx.Provider) ??
normalizeProviderId(ctx.Surface) ??
normalizeProviderId(ctx.OriginatingChannel);
normalizeChannelId(ctx.Provider) ??
normalizeChannelId(ctx.Surface) ??
normalizeChannelId(ctx.OriginatingChannel);
if (direct) return direct;
const candidates = [ctx.From, ctx.To]
.filter((value): value is string => Boolean(value?.trim()))
.flatMap((value) => value.split(":").map((part) => part.trim()));
for (const candidate of candidates) {
const normalized = normalizeProviderId(candidate);
const normalized = normalizeChannelId(candidate);
if (normalized) return normalized;
}
const configured = listProviderDocks()
const configured = listChannelDocks()
.map((dock) => {
if (!dock.config?.resolveAllowFrom) return null;
const allowFrom = dock.config.resolveAllowFrom({
@@ -40,13 +40,13 @@ function resolveProviderFromContext(
if (!Array.isArray(allowFrom) || allowFrom.length === 0) return null;
return dock.id;
})
.filter((value): value is ProviderId => Boolean(value));
.filter((value): value is ChannelId => Boolean(value));
if (configured.length === 1) return configured[0];
return undefined;
}
function formatAllowFromList(params: {
dock?: ProviderDock;
dock?: ChannelDock;
cfg: ClawdbotConfig;
accountId?: string | null;
allowFrom: Array<string | number>;
@@ -66,7 +66,7 @@ export function resolveCommandAuthorization(params: {
}): CommandAuthorization {
const { ctx, cfg, commandAuthorized } = params;
const providerId = resolveProviderFromContext(ctx, cfg);
const dock = providerId ? getProviderDock(providerId) : undefined;
const dock = providerId ? getChannelDock(providerId) : undefined;
const from = (ctx.From ?? "").trim();
const to = (ctx.To ?? "").trim();
const allowFromRaw = dock?.config?.resolveAllowFrom

View File

@@ -1,5 +1,5 @@
import { listChannelDocks } from "../channels/dock.js";
import type { ClawdbotConfig } from "../config/types.js";
import { listProviderDocks } from "../providers/dock.js";
export type CommandScope = "text" | "native" | "both";
@@ -276,7 +276,7 @@ let cachedNativeCommandSurfaces: Set<string> | null = null;
const getNativeCommandSurfaces = (): Set<string> => {
if (!cachedNativeCommandSurfaces) {
cachedNativeCommandSurfaces = new Set(
listProviderDocks()
listChannelDocks()
.filter((dock) => dock.capabilities.nativeCommands)
.map((dock) => dock.id),
);

View File

@@ -3,13 +3,13 @@ import { describe, expect, it } from "vitest";
import { formatAgentEnvelope } from "./envelope.js";
describe("formatAgentEnvelope", () => {
it("includes provider, from, ip, host, and timestamp", () => {
it("includes channel, from, ip, host, and timestamp", () => {
const originalTz = process.env.TZ;
process.env.TZ = "UTC";
const ts = Date.UTC(2025, 0, 2, 3, 4); // 2025-01-02T03:04:00Z
const body = formatAgentEnvelope({
provider: "WebChat",
channel: "WebChat",
from: "user1",
host: "mac-mini",
ip: "10.0.0.5",
@@ -30,7 +30,7 @@ describe("formatAgentEnvelope", () => {
const ts = Date.UTC(2025, 0, 2, 3, 4); // 2025-01-02T03:04:00Z
const body = formatAgentEnvelope({
provider: "WebChat",
channel: "WebChat",
timestamp: ts,
body: "hello",
});
@@ -41,7 +41,7 @@ describe("formatAgentEnvelope", () => {
});
it("handles missing optional fields", () => {
const body = formatAgentEnvelope({ provider: "Telegram", body: "hi" });
const body = formatAgentEnvelope({ channel: "Telegram", body: "hi" });
expect(body).toBe("[Telegram] hi");
});
});

View File

@@ -1,5 +1,5 @@
export type AgentEnvelopeParams = {
provider: string;
channel: string;
from?: string;
timestamp?: number | Date;
host?: string;
@@ -24,8 +24,8 @@ function formatTimestamp(ts?: number | Date): string | undefined {
}
export function formatAgentEnvelope(params: AgentEnvelopeParams): string {
const provider = params.provider?.trim() || "Provider";
const parts: string[] = [provider];
const channel = params.channel?.trim() || "Channel";
const parts: string[] = [channel];
if (params.from?.trim()) parts.push(params.from.trim());
if (params.host?.trim()) parts.push(params.host.trim());
if (params.ip?.trim()) parts.push(params.ip.trim());
@@ -36,13 +36,13 @@ export function formatAgentEnvelope(params: AgentEnvelopeParams): string {
}
export function formatThreadStarterEnvelope(params: {
provider: string;
channel: string;
author?: string;
timestamp?: number | Date;
body: string;
}): string {
return formatAgentEnvelope({
provider: params.provider,
channel: params.channel,
from: params.author,
timestamp: params.timestamp,
body: params.body,

View File

@@ -97,7 +97,7 @@ describe("block streaming", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -156,7 +156,7 @@ describe("block streaming", () => {
workspace: path.join(home, "clawd"),
},
},
telegram: { allowFrom: ["*"] },
channels: { telegram: { allowFrom: ["*"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -205,7 +205,7 @@ describe("block streaming", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -263,7 +263,7 @@ describe("block streaming", () => {
workspace: path.join(home, "clawd"),
},
},
telegram: { allowFrom: ["*"] },
channels: { telegram: { allowFrom: ["*"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -305,7 +305,7 @@ describe("block streaming", () => {
workspace: path.join(home, "clawd"),
},
},
telegram: { allowFrom: ["*"], streamMode: "block" },
channels: { telegram: { allowFrom: ["*"], streamMode: "block" } },
session: { store: path.join(home, "sessions.json") },
},
);

View File

@@ -181,7 +181,7 @@ describe("directive behavior", () => {
},
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -210,7 +210,7 @@ describe("directive behavior", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -250,7 +250,7 @@ describe("directive behavior", () => {
drop: "summarize",
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -383,7 +383,7 @@ describe("directive behavior", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -419,7 +419,7 @@ describe("directive behavior", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -459,7 +459,7 @@ describe("directive behavior", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -494,9 +494,7 @@ describe("directive behavior", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: {
allowFrom: ["*"],
},
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -541,9 +539,7 @@ describe("directive behavior", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: {
allowFrom: ["*"],
},
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: storePath },
},
);
@@ -589,7 +585,7 @@ describe("directive behavior", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: storePath },
},
);
@@ -613,7 +609,7 @@ describe("directive behavior", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: storePath },
},
);
@@ -803,7 +799,7 @@ describe("directive behavior", () => {
allowFrom: { whatsapp: ["+1222"] },
},
},
whatsapp: { allowFrom: ["+1222"] },
channels: { whatsapp: { allowFrom: ["+1222"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -842,7 +838,7 @@ describe("directive behavior", () => {
allowFrom: { whatsapp: ["+1222"] },
},
},
whatsapp: { allowFrom: ["+1222"] },
channels: { whatsapp: { allowFrom: ["+1222"] } },
session: { store: storePath },
},
);
@@ -894,7 +890,7 @@ describe("directive behavior", () => {
allowFrom: { whatsapp: ["+1222"] },
},
},
whatsapp: { allowFrom: ["+1222"] },
channels: { whatsapp: { allowFrom: ["+1222"] } },
session: { store: storePath },
},
);
@@ -937,7 +933,7 @@ describe("directive behavior", () => {
allowFrom: { whatsapp: ["+1222"] },
},
},
whatsapp: { allowFrom: ["+1222"] },
channels: { whatsapp: { allowFrom: ["+1222"] } },
session: { store: storePath },
},
);
@@ -964,7 +960,7 @@ describe("directive behavior", () => {
allowFrom: { whatsapp: ["+1222"] },
},
},
whatsapp: { allowFrom: ["+1222"] },
channels: { whatsapp: { allowFrom: ["+1222"] } },
session: { store: storePath },
},
);
@@ -993,7 +989,7 @@ describe("directive behavior", () => {
allowFrom: { whatsapp: ["+1222"] },
},
},
whatsapp: { allowFrom: ["+1222"] },
channels: { whatsapp: { allowFrom: ["+1222"] } },
session: { store: storePath },
} as const;
@@ -1079,7 +1075,7 @@ describe("directive behavior", () => {
allowFrom: { whatsapp: ["+1222"] },
},
},
whatsapp: { allowFrom: ["+1222"] },
channels: { whatsapp: { allowFrom: ["+1222"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -1126,7 +1122,7 @@ describe("directive behavior", () => {
allowFrom: { whatsapp: ["+1222", "+1333"] },
},
},
whatsapp: { allowFrom: ["+1222", "+1333"] },
channels: { whatsapp: { allowFrom: ["+1222", "+1333"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -1173,7 +1169,7 @@ describe("directive behavior", () => {
allowFrom: { whatsapp: ["+1222", "+1333"] },
},
},
whatsapp: { allowFrom: ["+1222", "+1333"] },
channels: { whatsapp: { allowFrom: ["+1222", "+1333"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -1210,7 +1206,7 @@ describe("directive behavior", () => {
allowFrom: { whatsapp: ["+1222"] },
},
},
whatsapp: { allowFrom: ["+1222"] },
channels: { whatsapp: { allowFrom: ["+1222"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -1247,7 +1243,7 @@ describe("directive behavior", () => {
allowFrom: { whatsapp: ["+1222"] },
},
},
whatsapp: { allowFrom: ["+1222"] },
channels: { whatsapp: { allowFrom: ["+1222"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -1283,7 +1279,7 @@ describe("directive behavior", () => {
allowFrom: { whatsapp: ["+1222"] },
},
},
whatsapp: { allowFrom: ["+1222"] },
channels: { whatsapp: { allowFrom: ["+1222"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -1321,7 +1317,7 @@ describe("directive behavior", () => {
allowFrom: { whatsapp: ["+1222"] },
},
},
whatsapp: { allowFrom: ["+1222"] },
channels: { whatsapp: { allowFrom: ["+1222"] } },
session: { store: storePath },
},
);
@@ -1375,7 +1371,7 @@ describe("directive behavior", () => {
allowFrom: { whatsapp: ["+1222"] },
},
},
whatsapp: { allowFrom: ["+1222"] },
channels: { whatsapp: { allowFrom: ["+1222"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -1401,7 +1397,7 @@ describe("directive behavior", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: storePath },
},
);
@@ -1434,7 +1430,7 @@ describe("directive behavior", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: storePath },
},
);
@@ -1469,7 +1465,7 @@ describe("directive behavior", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: storePath },
},
);
@@ -1484,7 +1480,7 @@ describe("directive behavior", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: storePath },
},
);
@@ -1545,9 +1541,7 @@ describe("directive behavior", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: {
allowFrom: ["*"],
},
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: storePath },
},
);
@@ -1608,9 +1602,7 @@ describe("directive behavior", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: {
allowFrom: ["*"],
},
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: storePath },
},
);
@@ -1625,9 +1617,7 @@ describe("directive behavior", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: {
allowFrom: ["*"],
},
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: storePath },
},
);
@@ -2283,7 +2273,7 @@ describe("directive behavior", () => {
},
},
tools: { elevated: { allowFrom: { whatsapp: ["*"] } } },
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: storePath },
},
);
@@ -2313,7 +2303,7 @@ describe("directive behavior", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: storePath },
},
);
@@ -2352,9 +2342,7 @@ describe("directive behavior", () => {
},
},
},
whatsapp: {
allowFrom: ["*"],
},
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: storePath },
},
);
@@ -2403,9 +2391,7 @@ describe("directive behavior", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: {
allowFrom: ["*"],
},
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: storePath },
},
);
@@ -2448,9 +2434,7 @@ describe("directive behavior", () => {
allowFrom: { whatsapp: ["+1004"] },
},
},
whatsapp: {
allowFrom: ["*"],
},
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: storePath },
},
);

View File

@@ -60,8 +60,10 @@ function makeCfg(home: string) {
workspace: join(home, "clawd"),
},
},
whatsapp: {
allowFrom: ["*"],
channels: {
whatsapp: {
allowFrom: ["*"],
},
},
session: { store: join(home, "sessions.json") },
};

View File

@@ -50,7 +50,7 @@ function makeCfg(home: string) {
workspace: path.join(home, "clawd"),
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: path.join(home, "sessions.json") },
};
}

View File

@@ -48,7 +48,7 @@ function makeCfg(home: string, queue?: Record<string, unknown>) {
workspace: path.join(home, "clawd"),
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: path.join(home, "sessions.json") },
messages: queue ? { queue } : undefined,
};

View File

@@ -67,7 +67,7 @@ describe("RawBody directive parsing", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -103,7 +103,7 @@ describe("RawBody directive parsing", () => {
},
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -136,7 +136,7 @@ describe("RawBody directive parsing", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -173,7 +173,7 @@ describe("RawBody directive parsing", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: { allowFrom: ["+1222"] },
channels: { whatsapp: { allowFrom: ["+1222"] } },
session: { store: path.join(home, "sessions.json") },
},
);
@@ -220,7 +220,7 @@ describe("RawBody directive parsing", () => {
workspace: path.join(home, "clawd"),
},
},
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: path.join(home, "sessions.json") },
},
);

View File

@@ -95,8 +95,10 @@ function makeCfg(home: string) {
workspace: join(home, "clawd"),
},
},
whatsapp: {
allowFrom: ["*"],
channels: {
whatsapp: {
allowFrom: ["*"],
},
},
session: { store: join(home, "sessions.json") },
};
@@ -856,8 +858,10 @@ describe("trigger handling", () => {
workspace: join(home, "clawd"),
},
},
whatsapp: {
allowFrom: ["+1000"],
channels: {
whatsapp: {
allowFrom: ["+1000"],
},
},
session: { store: join(home, "sessions.json") },
};
@@ -886,8 +890,10 @@ describe("trigger handling", () => {
workspace: join(home, "clawd"),
},
},
whatsapp: {
allowFrom: ["+1000"],
channels: {
whatsapp: {
allowFrom: ["+1000"],
},
},
session: { store: join(home, "sessions.json") },
};
@@ -923,8 +929,10 @@ describe("trigger handling", () => {
workspace: join(home, "clawd"),
},
},
whatsapp: {
allowFrom: ["+1000"],
channels: {
whatsapp: {
allowFrom: ["+1000"],
},
},
session: { store: join(home, "sessions.json") },
};
@@ -965,8 +973,10 @@ describe("trigger handling", () => {
workspace: join(home, "clawd"),
},
},
whatsapp: {
allowFrom: ["+1000"],
channels: {
whatsapp: {
allowFrom: ["+1000"],
},
},
session: { store: join(home, "sessions.json") },
};
@@ -1017,8 +1027,10 @@ describe("trigger handling", () => {
workspace: join(home, "clawd"),
},
},
whatsapp: {
allowFrom: ["+1000"],
channels: {
whatsapp: {
allowFrom: ["+1000"],
},
},
session: { store: join(home, "sessions.json") },
};
@@ -1060,8 +1072,10 @@ describe("trigger handling", () => {
allowFrom: { whatsapp: ["+1000"] },
},
},
whatsapp: {
allowFrom: ["+1000"],
channels: {
whatsapp: {
allowFrom: ["+1000"],
},
},
session: { store: join(home, "sessions.json") },
};
@@ -1104,8 +1118,10 @@ describe("trigger handling", () => {
allowFrom: { whatsapp: ["+1000"] },
},
},
whatsapp: {
allowFrom: ["+1000"],
channels: {
whatsapp: {
allowFrom: ["+1000"],
},
},
session: { store: join(home, "sessions.json") },
};
@@ -1154,9 +1170,11 @@ describe("trigger handling", () => {
allowFrom: { whatsapp: ["+1000"] },
},
},
whatsapp: {
allowFrom: ["+1000"],
groups: { "*": { requireMention: false } },
channels: {
whatsapp: {
allowFrom: ["+1000"],
groups: { "*": { requireMention: false } },
},
},
session: { store: join(home, "sessions.json") },
};
@@ -1201,9 +1219,11 @@ describe("trigger handling", () => {
allowFrom: { whatsapp: ["+1000"] },
},
},
whatsapp: {
allowFrom: ["+1000"],
groups: { "*": { requireMention: false } },
channels: {
whatsapp: {
allowFrom: ["+1000"],
groups: { "*": { requireMention: false } },
},
},
session: { store: join(home, "sessions.json") },
};
@@ -1245,9 +1265,11 @@ describe("trigger handling", () => {
allowFrom: { whatsapp: ["+1000"] },
},
},
whatsapp: {
allowFrom: ["+1000"],
groups: { "*": { requireMention: true } },
channels: {
whatsapp: {
allowFrom: ["+1000"],
groups: { "*": { requireMention: true } },
},
},
session: { store: join(home, "sessions.json") },
};
@@ -1293,8 +1315,10 @@ describe("trigger handling", () => {
allowFrom: { whatsapp: ["+1000"] },
},
},
whatsapp: {
allowFrom: ["+1000"],
channels: {
whatsapp: {
allowFrom: ["+1000"],
},
},
session: { store: join(home, "sessions.json") },
};
@@ -1343,8 +1367,10 @@ describe("trigger handling", () => {
allowFrom: { whatsapp: ["+1000"] },
},
},
whatsapp: {
allowFrom: ["+1000"],
channels: {
whatsapp: {
allowFrom: ["+1000"],
},
},
session: { store: join(home, "sessions.json") },
};
@@ -1648,9 +1674,11 @@ describe("trigger handling", () => {
workspace: join(home, "clawd"),
},
},
whatsapp: {
allowFrom: ["*"],
groups: { "*": { requireMention: false } },
channels: {
whatsapp: {
allowFrom: ["*"],
groups: { "*": { requireMention: false } },
},
},
messages: {
groupChat: {},
@@ -1694,8 +1722,10 @@ describe("trigger handling", () => {
workspace: join(home, "clawd"),
},
},
whatsapp: {
allowFrom: ["*"],
channels: {
whatsapp: {
allowFrom: ["*"],
},
},
session: {
store: join(tmpdir(), `clawdbot-session-test-${Date.now()}.json`),
@@ -1735,8 +1765,10 @@ describe("trigger handling", () => {
workspace: join(home, "clawd"),
},
},
whatsapp: {
allowFrom: ["*"],
channels: {
whatsapp: {
allowFrom: ["*"],
},
},
session: {
store: join(tmpdir(), `clawdbot-session-test-${Date.now()}.json`),
@@ -1769,8 +1801,10 @@ describe("trigger handling", () => {
workspace: join(home, "clawd"),
},
},
whatsapp: {
allowFrom: ["+1999"],
channels: {
whatsapp: {
allowFrom: ["+1999"],
},
},
session: {
store: join(tmpdir(), `clawdbot-session-test-${Date.now()}.json`),
@@ -1798,8 +1832,10 @@ describe("trigger handling", () => {
workspace: join(home, "clawd"),
},
},
whatsapp: {
allowFrom: ["+1999"],
channels: {
whatsapp: {
allowFrom: ["+1999"],
},
},
session: {
store: join(tmpdir(), `clawdbot-session-test-${Date.now()}.json`),
@@ -1841,8 +1877,10 @@ describe("trigger handling", () => {
workspace: join(home, "clawd"),
},
},
whatsapp: {
allowFrom: ["*"],
channels: {
whatsapp: {
allowFrom: ["*"],
},
},
session: {
store: storePath,
@@ -1956,8 +1994,10 @@ describe("trigger handling", () => {
},
},
},
whatsapp: {
allowFrom: ["*"],
channels: {
whatsapp: {
allowFrom: ["*"],
},
},
session: {
store: join(home, "sessions.json"),

View File

@@ -24,6 +24,11 @@ import {
DEFAULT_AGENT_WORKSPACE_DIR,
ensureAgentWorkspace,
} from "../agents/workspace.js";
import { getChannelDock } from "../channels/dock.js";
import {
CHAT_CHANNEL_ORDER,
normalizeChannelId,
} from "../channels/registry.js";
import {
type AgentElevatedAllowFromConfig,
type ClawdbotConfig,
@@ -35,14 +40,9 @@ import {
} from "../config/sessions.js";
import { logVerbose } from "../globals.js";
import { clearCommandLane, getQueueSize } from "../process/command-queue.js";
import { getProviderDock } from "../providers/dock.js";
import {
CHAT_PROVIDER_ORDER,
normalizeProviderId,
} from "../providers/registry.js";
import { normalizeMainKey } from "../routing/session-key.js";
import { defaultRuntime } from "../runtime.js";
import { INTERNAL_MESSAGE_PROVIDER } from "../utils/message-provider.js";
import { INTERNAL_MESSAGE_CHANNEL } from "../utils/message-channel.js";
import { isReasoningTagProvider } from "../utils/provider-utils.js";
import { resolveCommandAuthorization } from "./command-auth.js";
import { hasControlCommand } from "./command-detection.js";
@@ -141,8 +141,8 @@ function slugAllowToken(value?: string) {
}
const SENDER_PREFIXES = [
...CHAT_PROVIDER_ORDER,
INTERNAL_MESSAGE_PROVIDER,
...CHAT_CHANNEL_ORDER,
INTERNAL_MESSAGE_CHANNEL,
"user",
"group",
"channel",
@@ -287,9 +287,9 @@ function resolveElevatedPermissions(params: {
return { enabled, allowed: false, failures };
}
const normalizedProvider = normalizeProviderId(params.provider);
const normalizedProvider = normalizeChannelId(params.provider);
const dockFallbackAllowFrom = normalizedProvider
? getProviderDock(normalizedProvider)?.elevated?.allowFromFallback?.({
? getChannelDock(normalizedProvider)?.elevated?.allowFromFallback?.({
cfg: params.cfg,
accountId: params.ctx.AccountId,
})
@@ -1017,10 +1017,8 @@ export async function getReplyFromConfig(
}
const isEmptyConfig = Object.keys(cfg).length === 0;
const skipWhenConfigEmpty = command.providerId
? Boolean(
getProviderDock(command.providerId)?.commands?.skipWhenConfigEmpty,
)
const skipWhenConfigEmpty = command.channelId
? Boolean(getChannelDock(command.channelId)?.commands?.skipWhenConfigEmpty)
: false;
if (
skipWhenConfigEmpty &&
@@ -1255,7 +1253,7 @@ export async function getReplyFromConfig(
: queueBodyBase;
const resolvedQueue = resolveQueueSettings({
cfg,
provider: sessionCtx.Provider,
channel: sessionCtx.Provider,
sessionEntry,
inlineMode: perMessageQueueMode,
inlineOptions: perMessageQueueOptions,

View File

@@ -21,6 +21,9 @@ import {
resolveSandboxRuntimeStatus,
} from "../../agents/sandbox.js";
import { hasNonzeroUsage, type NormalizedUsage } from "../../agents/usage.js";
import { getChannelDock } from "../../channels/dock.js";
import type { ChannelThreadingToolContext } from "../../channels/plugins/types.js";
import { normalizeChannelId } from "../../channels/registry.js";
import type { ClawdbotConfig } from "../../config/config.js";
import {
loadSessionStore,
@@ -37,9 +40,6 @@ import {
registerAgentRunContext,
} from "../../infra/agent-events.js";
import { isAudioFileName } from "../../media/mime.js";
import { getProviderDock } from "../../providers/dock.js";
import type { ProviderThreadingToolContext } from "../../providers/plugins/types.js";
import { normalizeProviderId } from "../../providers/registry.js";
import { defaultRuntime } from "../../runtime.js";
import { isReasoningTagProvider } from "../../utils/provider-utils.js";
import {
@@ -96,19 +96,19 @@ function buildThreadingToolContext(params: {
sessionCtx: TemplateContext;
config: ClawdbotConfig | undefined;
hasRepliedRef: { value: boolean } | undefined;
}): ProviderThreadingToolContext {
}): ChannelThreadingToolContext {
const { sessionCtx, config, hasRepliedRef } = params;
if (!config) return {};
const provider = normalizeProviderId(sessionCtx.Provider);
const provider = normalizeChannelId(sessionCtx.Provider);
if (!provider) return {};
const dock = getProviderDock(provider);
const dock = getChannelDock(provider);
if (!dock?.threading?.buildToolContext) return {};
return (
dock.threading.buildToolContext({
cfg: config,
accountId: sessionCtx.AccountId,
context: {
Provider: sessionCtx.Provider,
Channel: sessionCtx.Provider,
To: sessionCtx.To,
ReplyToId: sessionCtx.ReplyToId,
ThreadLabel: sessionCtx.ThreadLabel,

View File

@@ -1,17 +1,17 @@
import { getChannelDock } from "../../channels/dock.js";
import { CHANNEL_IDS, normalizeChannelId } from "../../channels/registry.js";
import type { ClawdbotConfig } from "../../config/config.js";
import type { BlockStreamingCoalesceConfig } from "../../config/types.js";
import { getProviderDock } from "../../providers/dock.js";
import { normalizeProviderId, PROVIDER_IDS } from "../../providers/registry.js";
import { normalizeAccountId } from "../../routing/session-key.js";
import { INTERNAL_MESSAGE_PROVIDER } from "../../utils/message-provider.js";
import { INTERNAL_MESSAGE_CHANNEL } from "../../utils/message-channel.js";
import { resolveTextChunkLimit, type TextChunkProvider } from "../chunk.js";
const DEFAULT_BLOCK_STREAM_MIN = 800;
const DEFAULT_BLOCK_STREAM_MAX = 1200;
const DEFAULT_BLOCK_STREAM_COALESCE_IDLE_MS = 1000;
const BLOCK_CHUNK_PROVIDERS = new Set<TextChunkProvider>([
...PROVIDER_IDS,
INTERNAL_MESSAGE_PROVIDER,
...CHANNEL_IDS,
INTERNAL_MESSAGE_CHANNEL,
]);
function normalizeChunkProvider(
@@ -64,9 +64,9 @@ export function resolveBlockStreamingChunking(
breakPreference: "paragraph" | "newline" | "sentence";
} {
const providerKey = normalizeChunkProvider(provider);
const providerId = providerKey ? normalizeProviderId(providerKey) : null;
const providerId = providerKey ? normalizeChannelId(providerKey) : null;
const providerChunkLimit = providerId
? getProviderDock(providerId)?.outbound?.textChunkLimit
? getChannelDock(providerId)?.outbound?.textChunkLimit
: undefined;
const textLimit = resolveTextChunkLimit(cfg, providerKey, accountId, {
fallbackLimit: providerChunkLimit,
@@ -102,15 +102,15 @@ export function resolveBlockStreamingCoalescing(
},
): BlockStreamingCoalescing | undefined {
const providerKey = normalizeChunkProvider(provider);
const providerId = providerKey ? normalizeProviderId(providerKey) : null;
const providerId = providerKey ? normalizeChannelId(providerKey) : null;
const providerChunkLimit = providerId
? getProviderDock(providerId)?.outbound?.textChunkLimit
? getChannelDock(providerId)?.outbound?.textChunkLimit
: undefined;
const textLimit = resolveTextChunkLimit(cfg, providerKey, accountId, {
fallbackLimit: providerChunkLimit,
});
const providerDefaults = providerId
? getProviderDock(providerId)?.streaming?.blockStreamingCoalesceDefaults
? getChannelDock(providerId)?.streaming?.blockStreamingCoalesceDefaults
: undefined;
const providerCfg = resolveProviderBlockStreamingCoalesce({
cfg,

View File

@@ -83,7 +83,7 @@ describe("handleCommands gating", () => {
it("blocks /config when disabled", async () => {
const cfg = {
commands: { config: false, debug: false, text: true },
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
} as ClawdbotConfig;
const params = buildParams("/config show", cfg);
const result = await handleCommands(params);
@@ -94,7 +94,7 @@ describe("handleCommands gating", () => {
it("blocks /debug when disabled", async () => {
const cfg = {
commands: { config: false, debug: false, text: true },
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
} as ClawdbotConfig;
const params = buildParams("/debug show", cfg);
const result = await handleCommands(params);
@@ -133,7 +133,7 @@ describe("handleCommands identity", () => {
it("returns sender details for /whoami", async () => {
const cfg = {
commands: { text: true },
whatsapp: { allowFrom: ["*"] },
channels: { whatsapp: { allowFrom: ["*"] } },
} as ClawdbotConfig;
const params = buildParams("/whoami", cfg, {
SenderId: "12345",
@@ -142,7 +142,7 @@ describe("handleCommands identity", () => {
});
const result = await handleCommands(params);
expect(result.shouldContinue).toBe(false);
expect(result.reply?.text).toContain("Provider: whatsapp");
expect(result.reply?.text).toContain("Channel: whatsapp");
expect(result.reply?.text).toContain("User id: 12345");
expect(result.reply?.text).toContain("Username: @TestUser");
expect(result.reply?.text).toContain("AllowFrom: 12345");

View File

@@ -19,6 +19,7 @@ import {
isEmbeddedPiRunActive,
waitForEmbeddedPiRunEnd,
} from "../../agents/pi-embedded.js";
import type { ChannelId } from "../../channels/plugins/types.js";
import type { ClawdbotConfig } from "../../config/config.js";
import {
readConfigFileSnapshot,
@@ -54,7 +55,6 @@ import {
triggerClawdbotRestart,
} from "../../infra/restart.js";
import { enqueueSystemEvent } from "../../infra/system-events.js";
import type { ProviderId } from "../../providers/plugins/types.js";
import { parseAgentSessionKey } from "../../routing/session-key.js";
import { resolveSendPolicy } from "../../sessions/send-policy.js";
import { resolveCommandAuthorization } from "../command-auth.js";
@@ -108,8 +108,8 @@ function resolveSessionEntryForKey(
export type CommandContext = {
surface: string;
provider: string;
providerId?: ProviderId;
channel: string;
channelId?: ChannelId;
ownerList: string[];
isAuthorizedSender: boolean;
senderId?: string;
@@ -189,7 +189,7 @@ export async function buildStatusReply(params: {
}
const queueSettings = resolveQueueSettings({
cfg,
provider: command.provider,
channel: command.channel,
sessionEntry,
});
const queueKey = sessionKey ?? sessionEntry?.sessionId;
@@ -347,7 +347,7 @@ export function buildCommandContext(params: {
commandAuthorized: params.commandAuthorized,
});
const surface = (ctx.Surface ?? ctx.Provider ?? "").trim().toLowerCase();
const provider = (ctx.Provider ?? surface).trim().toLowerCase();
const channel = (ctx.Provider ?? surface).trim().toLowerCase();
const abortKey =
sessionKey ?? (auth.from || undefined) ?? (auth.to || undefined);
const rawBodyNormalized = triggerBodyNormalized;
@@ -359,8 +359,8 @@ export function buildCommandContext(params: {
return {
surface,
provider,
providerId: auth.providerId,
channel,
channelId: auth.providerId,
ownerList: auth.ownerList,
isAuthorizedSender: auth.isAuthorizedSender,
senderId: auth.senderId,
@@ -677,7 +677,7 @@ export async function handleCommands(params: {
}
const senderId = ctx.SenderId ?? "";
const senderUsername = ctx.SenderUsername ?? "";
const lines = ["🧭 Identity", `Provider: ${command.provider}`];
const lines = ["🧭 Identity", `Channel: ${command.channel}`];
if (senderId) lines.push(`User id: ${senderId}`);
if (senderUsername) {
const handle = senderUsername.startsWith("@")
@@ -980,7 +980,7 @@ export async function handleCommands(params: {
const result = await compactEmbeddedPiSession({
sessionId,
sessionKey,
messageProvider: command.provider,
messageChannel: command.channel,
sessionFile: resolveSessionFilePath(sessionId, sessionEntry),
workspaceDir,
config: cfg,
@@ -1056,7 +1056,7 @@ export async function handleCommands(params: {
cfg,
entry: sessionEntry,
sessionKey,
provider: sessionEntry?.provider ?? command.provider,
channel: sessionEntry?.channel ?? command.channel,
chatType: sessionEntry?.chatType,
});
if (sendPolicy === "deny") {

View File

@@ -1150,7 +1150,7 @@ export async function handleDirectiveOnly(params: {
) {
const settings = resolveQueueSettings({
cfg: params.cfg,
provider,
channel: provider,
sessionEntry,
});
const debounceLabel =

View File

@@ -7,12 +7,14 @@ import { resolveGroupRequireMention } from "./groups.js";
describe("resolveGroupRequireMention", () => {
it("respects Discord guild/channel requireMention settings", () => {
const cfg: ClawdbotConfig = {
discord: {
guilds: {
"145": {
requireMention: false,
channels: {
general: { allow: true },
channels: {
discord: {
guilds: {
"145": {
requireMention: false,
channels: {
general: { allow: true },
},
},
},
},
@@ -25,7 +27,7 @@ describe("resolveGroupRequireMention", () => {
GroupSpace: "145",
};
const groupResolution: GroupKeyResolution = {
provider: "discord",
channel: "discord",
id: "123",
chatType: "group",
};
@@ -37,9 +39,11 @@ describe("resolveGroupRequireMention", () => {
it("respects Slack channel requireMention settings", () => {
const cfg: ClawdbotConfig = {
slack: {
channels: {
C123: { requireMention: false },
channels: {
slack: {
channels: {
C123: { requireMention: false },
},
},
},
};
@@ -49,7 +53,7 @@ describe("resolveGroupRequireMention", () => {
GroupSubject: "#general",
};
const groupResolution: GroupKeyResolution = {
provider: "slack",
channel: "slack",
id: "C123",
chatType: "group",
};

View File

@@ -1,14 +1,14 @@
import { getChannelDock } from "../../channels/dock.js";
import {
getChatChannelMeta,
normalizeChannelId,
} from "../../channels/registry.js";
import type { ClawdbotConfig } from "../../config/config.js";
import type {
GroupKeyResolution,
SessionEntry,
} from "../../config/sessions.js";
import { getProviderDock } from "../../providers/dock.js";
import {
getChatProviderMeta,
normalizeProviderId,
} from "../../providers/registry.js";
import { isInternalMessageProvider } from "../../utils/message-provider.js";
import { isInternalMessageChannel } from "../../utils/message-channel.js";
import { normalizeGroupActivation } from "../group-activation.js";
import type { TemplateContext } from "../templating.js";
@@ -18,14 +18,14 @@ export function resolveGroupRequireMention(params: {
groupResolution?: GroupKeyResolution;
}): boolean {
const { cfg, ctx, groupResolution } = params;
const rawProvider = groupResolution?.provider ?? ctx.Provider?.trim();
const provider = normalizeProviderId(rawProvider);
if (!provider) return true;
const rawChannel = groupResolution?.channel ?? ctx.Provider?.trim();
const channel = normalizeChannelId(rawChannel);
if (!channel) return true;
const groupId = groupResolution?.id ?? ctx.From?.replace(/^group:/, "");
const groupRoom = ctx.GroupRoom?.trim() ?? ctx.GroupSubject?.trim();
const groupSpace = ctx.GroupSpace?.trim();
const requireMention = getProviderDock(
provider,
const requireMention = getChannelDock(
channel,
)?.groups?.resolveRequireMention?.({
cfg,
groupId,
@@ -57,11 +57,11 @@ export function buildGroupIntro(params: {
const members = params.sessionCtx.GroupMembers?.trim();
const rawProvider = params.sessionCtx.Provider?.trim();
const providerKey = rawProvider?.toLowerCase() ?? "";
const providerId = normalizeProviderId(rawProvider);
const providerId = normalizeChannelId(rawProvider);
const providerLabel = (() => {
if (!providerKey) return "chat";
if (isInternalMessageProvider(providerKey)) return "WebChat";
if (providerId) return getChatProviderMeta(providerId).label;
if (isInternalMessageChannel(providerKey)) return "WebChat";
if (providerId) return getChatChannelMeta(providerId).label;
return `${providerKey.at(0)?.toUpperCase() ?? ""}${providerKey.slice(1)}`;
})();
const subjectLine = subject
@@ -76,7 +76,7 @@ export function buildGroupIntro(params: {
const groupRoom = params.sessionCtx.GroupRoom?.trim() ?? subject;
const groupSpace = params.sessionCtx.GroupSpace?.trim();
const providerIdsLine = providerId
? getProviderDock(providerId)?.groups?.resolveGroupIntroHint?.({
? getChannelDock(providerId)?.groups?.resolveGroupIntroHint?.({
cfg: params.cfg,
groupId,
groupRoom,

View File

@@ -1,7 +1,7 @@
import { resolveAgentConfig } from "../../agents/agent-scope.js";
import { getChannelDock } from "../../channels/dock.js";
import { normalizeChannelId } from "../../channels/registry.js";
import type { ClawdbotConfig } from "../../config/config.js";
import { getProviderDock } from "../../providers/dock.js";
import { normalizeProviderId } from "../../providers/registry.js";
import type { MsgContext } from "../templating.js";
function escapeRegExp(text: string): string {
@@ -114,9 +114,9 @@ export function stripMentions(
agentId?: string,
): string {
let result = text;
const providerId = ctx.Provider ? normalizeProviderId(ctx.Provider) : null;
const providerId = ctx.Provider ? normalizeChannelId(ctx.Provider) : null;
const providerMentions = providerId
? getProviderDock(providerId)?.mentions
? getChannelDock(providerId)?.mentions
: undefined;
const patterns = normalizeMentionPatterns([
...resolveMentionPatterns(cfg, agentId),

View File

@@ -577,28 +577,28 @@ export function scheduleFollowupDrain(
}
})();
}
function defaultQueueModeForProvider(_provider?: string): QueueMode {
function defaultQueueModeForChannel(_channel?: string): QueueMode {
return "collect";
}
export function resolveQueueSettings(params: {
cfg: ClawdbotConfig;
provider?: string;
channel?: string;
sessionEntry?: SessionEntry;
inlineMode?: QueueMode;
inlineOptions?: Partial<QueueSettings>;
}): QueueSettings {
const providerKey = params.provider?.trim().toLowerCase();
const channelKey = params.channel?.trim().toLowerCase();
const queueCfg = params.cfg.messages?.queue;
const providerModeRaw =
providerKey && queueCfg?.byProvider
? (queueCfg.byProvider as Record<string, string | undefined>)[providerKey]
channelKey && queueCfg?.byChannel
? (queueCfg.byChannel as Record<string, string | undefined>)[channelKey]
: undefined;
const resolvedMode =
params.inlineMode ??
normalizeQueueMode(params.sessionEntry?.queueMode) ??
normalizeQueueMode(providerModeRaw) ??
normalizeQueueMode(queueCfg?.mode) ??
defaultQueueModeForProvider(providerKey);
defaultQueueModeForChannel(channelKey);
const debounceRaw =
params.inlineOptions?.debounceMs ??
params.sessionEntry?.queueDebounceMs ??

View File

@@ -24,9 +24,11 @@ describe("resolveReplyToMode", () => {
it("uses configured value when present", () => {
const cfg = {
telegram: { replyToMode: "all" },
discord: { replyToMode: "first" },
slack: { replyToMode: "all" },
channels: {
telegram: { replyToMode: "all" },
discord: { replyToMode: "first" },
slack: { replyToMode: "all" },
},
} as ClawdbotConfig;
expect(resolveReplyToMode(cfg, "telegram")).toBe("all");
expect(resolveReplyToMode(cfg, "discord")).toBe("first");

View File

@@ -1,7 +1,7 @@
import { getChannelDock } from "../../channels/dock.js";
import { normalizeChannelId } from "../../channels/registry.js";
import type { ClawdbotConfig } from "../../config/config.js";
import type { ReplyToMode } from "../../config/types.js";
import { getProviderDock } from "../../providers/dock.js";
import { normalizeProviderId } from "../../providers/registry.js";
import type { OriginatingChannelType } from "../templating.js";
import type { ReplyPayload } from "../types.js";
@@ -10,9 +10,9 @@ export function resolveReplyToMode(
channel?: OriginatingChannelType,
accountId?: string | null,
): ReplyToMode {
const provider = normalizeProviderId(channel);
const provider = normalizeChannelId(channel);
if (!provider) return "all";
const resolved = getProviderDock(provider)?.threading?.resolveReplyToMode?.({
const resolved = getChannelDock(provider)?.threading?.resolveReplyToMode?.({
cfg,
accountId,
});
@@ -43,9 +43,9 @@ export function createReplyToModeFilterForChannel(
mode: ReplyToMode,
channel?: OriginatingChannelType,
) {
const provider = normalizeProviderId(channel);
const provider = normalizeChannelId(channel);
const allowTagsWhenOff = provider
? Boolean(getProviderDock(provider)?.threading?.allowTagsWhenOff)
? Boolean(getChannelDock(provider)?.threading?.allowTagsWhenOff)
: false;
return createReplyToModeFilter(mode, {
allowTagsWhenOff,

View File

@@ -226,8 +226,10 @@ describe("routeReply", () => {
it("routes MS Teams via proactive sender", async () => {
mocks.sendMessageMSTeams.mockClear();
const cfg = {
msteams: {
enabled: true,
channels: {
msteams: {
enabled: true,
},
},
} as unknown as ClawdbotConfig;
await routeReply({

View File

@@ -9,9 +9,9 @@
import { resolveSessionAgentId } from "../../agents/agent-scope.js";
import { resolveEffectiveMessagesConfig } from "../../agents/identity.js";
import { normalizeChannelId } from "../../channels/registry.js";
import type { ClawdbotConfig } from "../../config/config.js";
import { normalizeProviderId } from "../../providers/registry.js";
import { INTERNAL_MESSAGE_PROVIDER } from "../../utils/message-provider.js";
import { INTERNAL_MESSAGE_CHANNEL } from "../../utils/message-channel.js";
import type { OriginatingChannelType } from "../templating.js";
import type { ReplyPayload } from "../types.js";
import { normalizeReplyPayload } from "./normalize-reply.js";
@@ -88,15 +88,15 @@ export async function routeReply(
return { ok: true };
}
if (channel === INTERNAL_MESSAGE_PROVIDER) {
if (channel === INTERNAL_MESSAGE_CHANNEL) {
return {
ok: false,
error: "Webchat routing not supported for queued replies",
};
}
const provider = normalizeProviderId(channel) ?? null;
if (!provider) {
const channelId = normalizeChannelId(channel) ?? null;
if (!channelId) {
return { ok: false, error: `Unknown channel: ${String(channel)}` };
}
if (abortSignal?.aborted) {
@@ -111,7 +111,7 @@ export async function routeReply(
);
const results = await deliverOutboundPayloads({
cfg,
provider,
channel: channelId,
to,
accountId: accountId ?? undefined,
payloads: [normalized],
@@ -138,10 +138,7 @@ export async function routeReply(
*/
export function isRoutableChannel(
channel: OriginatingChannelType | undefined,
): channel is Exclude<
OriginatingChannelType,
typeof INTERNAL_MESSAGE_PROVIDER
> {
if (!channel || channel === INTERNAL_MESSAGE_PROVIDER) return false;
return normalizeProviderId(channel) !== null;
): channel is Exclude<OriginatingChannelType, typeof INTERNAL_MESSAGE_CHANNEL> {
if (!channel || channel === INTERNAL_MESSAGE_CHANNEL) return false;
return normalizeChannelId(channel) !== null;
}

View File

@@ -3,8 +3,8 @@ import crypto from "node:crypto";
import { buildWorkspaceSkillSnapshot } from "../../agents/skills.js";
import type { ClawdbotConfig } from "../../config/config.js";
import { type SessionEntry, saveSessionStore } from "../../config/sessions.js";
import { buildProviderSummary } from "../../infra/provider-summary.js";
import { drainSystemEventEntries } from "../../infra/system-events.js";
import { buildChannelSummary } from "../../infra/channel-summary.js";
export async function prependSystemEvents(params: {
cfg: ClawdbotConfig;
@@ -48,7 +48,7 @@ export async function prependSystemEvents(params: {
.filter((v): v is string => Boolean(v)),
);
if (params.isMainSession && params.isNewSession) {
const summary = await buildProviderSummary(params.cfg);
const summary = await buildChannelSummary(params.cfg);
if (summary.length > 0) systemLines.unshift(...summary);
}
if (systemLines.length === 0) return params.prefixedBodyBase;

View File

@@ -7,6 +7,8 @@ import {
SessionManager,
} from "@mariozechner/pi-coding-agent";
import { resolveSessionAgentId } from "../../agents/agent-scope.js";
import { getChannelDock } from "../../channels/dock.js";
import { normalizeChannelId } from "../../channels/registry.js";
import type { ClawdbotConfig } from "../../config/config.js";
import {
buildGroupDisplayName,
@@ -23,8 +25,6 @@ import {
type SessionScope,
saveSessionStore,
} from "../../config/sessions.js";
import { getProviderDock } from "../../providers/dock.js";
import { normalizeProviderId } from "../../providers/registry.js";
import { normalizeMainKey } from "../../routing/session-key.js";
import { resolveCommandAuthorization } from "../command-auth.js";
import type { MsgContext, TemplateContext } from "../templating.js";
@@ -228,20 +228,20 @@ export async function initSessionState(params: {
queueDrop: baseEntry?.queueDrop,
displayName: baseEntry?.displayName,
chatType: baseEntry?.chatType,
provider: baseEntry?.provider,
channel: baseEntry?.channel,
subject: baseEntry?.subject,
room: baseEntry?.room,
space: baseEntry?.space,
};
if (groupResolution?.provider) {
const provider = groupResolution.provider;
if (groupResolution?.channel) {
const channel = groupResolution.channel;
const subject = ctx.GroupSubject?.trim();
const space = ctx.GroupSpace?.trim();
const explicitRoom = ctx.GroupRoom?.trim();
const normalizedProvider = normalizeProviderId(provider);
const normalizedChannel = normalizeChannelId(channel);
const isRoomProvider = Boolean(
normalizedProvider &&
getProviderDock(normalizedProvider)?.capabilities.chatTypes.includes(
normalizedChannel &&
getChannelDock(normalizedChannel)?.capabilities.chatTypes.includes(
"channel",
),
);
@@ -252,12 +252,12 @@ export async function initSessionState(params: {
: undefined);
const nextSubject = nextRoom ? undefined : subject;
sessionEntry.chatType = groupResolution.chatType ?? "group";
sessionEntry.provider = provider;
sessionEntry.channel = channel;
if (nextSubject) sessionEntry.subject = nextSubject;
if (nextRoom) sessionEntry.room = nextRoom;
if (space) sessionEntry.space = space;
sessionEntry.displayName = buildGroupDisplayName({
provider: sessionEntry.provider,
provider: sessionEntry.channel,
subject: sessionEntry.subject,
room: sessionEntry.room,
space: sessionEntry.space,

View File

@@ -1,8 +1,8 @@
import type { ProviderId } from "../providers/plugins/types.js";
import type { InternalMessageProvider } from "../utils/message-provider.js";
import type { ChannelId } from "../channels/plugins/types.js";
import type { InternalMessageChannel } from "../utils/message-channel.js";
/** Valid provider channels for message routing. */
export type OriginatingChannelType = ProviderId | InternalMessageProvider;
export type OriginatingChannelType = ChannelId | InternalMessageChannel;
export type MsgContext = {
Body?: string;