refactor!: rename chat providers to channels
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import crypto from "node:crypto";
|
||||
|
||||
import { callGateway } from "../../gateway/call.js";
|
||||
import { INTERNAL_MESSAGE_PROVIDER } from "../../utils/message-provider.js";
|
||||
import { INTERNAL_MESSAGE_CHANNEL } from "../../utils/message-channel.js";
|
||||
import { AGENT_LANE_NESTED } from "../lanes.js";
|
||||
import { extractAssistantText, stripToolMessages } from "./sessions-helpers.js";
|
||||
|
||||
@@ -25,7 +25,7 @@ export async function runAgentStep(params: {
|
||||
message: string;
|
||||
extraSystemPrompt: string;
|
||||
timeoutMs: number;
|
||||
provider?: string;
|
||||
channel?: string;
|
||||
lane?: string;
|
||||
}): Promise<string | undefined> {
|
||||
const stepIdem = crypto.randomUUID();
|
||||
@@ -36,7 +36,7 @@ export async function runAgentStep(params: {
|
||||
sessionKey: params.sessionKey,
|
||||
idempotencyKey: stepIdem,
|
||||
deliver: false,
|
||||
provider: params.provider ?? INTERNAL_MESSAGE_PROVIDER,
|
||||
channel: params.channel ?? INTERNAL_MESSAGE_CHANNEL,
|
||||
lane: params.lane ?? AGENT_LANE_NESTED,
|
||||
extraSystemPrompt: params.extraSystemPrompt,
|
||||
},
|
||||
|
||||
@@ -56,7 +56,7 @@ export async function handleDiscordAction(
|
||||
cfg: ClawdbotConfig,
|
||||
): Promise<AgentToolResult<unknown>> {
|
||||
const action = readStringParam(params, "action", { required: true });
|
||||
const isActionEnabled = createActionGate(cfg.discord?.actions);
|
||||
const isActionEnabled = createActionGate(cfg.channels?.discord?.actions);
|
||||
|
||||
if (messagingActions.has(action)) {
|
||||
return await handleDiscordMessagingAction(action, params, isActionEnabled);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { callGateway } from "../../gateway/call.js";
|
||||
import {
|
||||
GATEWAY_CLIENT_MODES,
|
||||
GATEWAY_CLIENT_NAMES,
|
||||
} from "../../utils/message-provider.js";
|
||||
} from "../../utils/message-channel.js";
|
||||
|
||||
export const DEFAULT_GATEWAY_URL = "ws://127.0.0.1:18789";
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { Type } from "@sinclair/typebox";
|
||||
|
||||
import {
|
||||
listChannelMessageActions,
|
||||
supportsChannelMessageButtons,
|
||||
} from "../../channels/plugins/message-actions.js";
|
||||
import {
|
||||
CHANNEL_MESSAGE_ACTION_NAMES,
|
||||
type ChannelMessageActionName,
|
||||
} from "../../channels/plugins/types.js";
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import { loadConfig } from "../../config/config.js";
|
||||
import {
|
||||
@@ -7,23 +14,15 @@ import {
|
||||
GATEWAY_CLIENT_MODES,
|
||||
} from "../../gateway/protocol/client-info.js";
|
||||
import { runMessageAction } from "../../infra/outbound/message-action-runner.js";
|
||||
import {
|
||||
listProviderMessageActions,
|
||||
supportsProviderMessageButtons,
|
||||
} from "../../providers/plugins/message-actions.js";
|
||||
import {
|
||||
PROVIDER_MESSAGE_ACTION_NAMES,
|
||||
type ProviderMessageActionName,
|
||||
} from "../../providers/plugins/types.js";
|
||||
import { normalizeAccountId } from "../../routing/session-key.js";
|
||||
import { stringEnum } from "../schema/typebox.js";
|
||||
import type { AnyAgentTool } from "./common.js";
|
||||
import { jsonResult, readNumberParam, readStringParam } from "./common.js";
|
||||
|
||||
const AllMessageActions = PROVIDER_MESSAGE_ACTION_NAMES;
|
||||
const AllMessageActions = CHANNEL_MESSAGE_ACTION_NAMES;
|
||||
|
||||
const MessageToolCommonSchema = {
|
||||
provider: Type.Optional(Type.String()),
|
||||
channel: Type.Optional(Type.String()),
|
||||
to: Type.Optional(Type.String()),
|
||||
message: Type.Optional(Type.String()),
|
||||
media: Type.Optional(Type.String()),
|
||||
@@ -131,8 +130,8 @@ type MessageToolOptions = {
|
||||
};
|
||||
|
||||
function buildMessageToolSchema(cfg: ClawdbotConfig) {
|
||||
const actions = listProviderMessageActions(cfg);
|
||||
const includeButtons = supportsProviderMessageButtons(cfg);
|
||||
const actions = listChannelMessageActions(cfg);
|
||||
const includeButtons = supportsChannelMessageButtons(cfg);
|
||||
return buildMessageToolSchemaFromActions(
|
||||
actions.length > 0 ? actions : ["send"],
|
||||
{ includeButtons },
|
||||
@@ -155,14 +154,14 @@ export function createMessageTool(options?: MessageToolOptions): AnyAgentTool {
|
||||
label: "Message",
|
||||
name: "message",
|
||||
description:
|
||||
"Send messages and provider actions (polls, reactions, pins, threads, etc.) via configured provider plugins.",
|
||||
"Send messages and channel actions (polls, reactions, pins, threads, etc.) via configured channel plugins.",
|
||||
parameters: schema,
|
||||
execute: async (_toolCallId, args) => {
|
||||
const params = args as Record<string, unknown>;
|
||||
const cfg = options?.config ?? loadConfig();
|
||||
const action = readStringParam(params, "action", {
|
||||
required: true,
|
||||
}) as ProviderMessageActionName;
|
||||
}) as ChannelMessageActionName;
|
||||
const accountId = readStringParam(params, "accountId") ?? agentAccountId;
|
||||
|
||||
const gateway = {
|
||||
|
||||
@@ -322,8 +322,8 @@ export function createSessionStatusTool(opts?: {
|
||||
|
||||
const queueSettings = resolveQueueSettings({
|
||||
cfg,
|
||||
provider:
|
||||
resolved.entry.provider ?? resolved.entry.lastProvider ?? "unknown",
|
||||
channel:
|
||||
resolved.entry.channel ?? resolved.entry.lastChannel ?? "unknown",
|
||||
sessionEntry: resolved.entry,
|
||||
});
|
||||
const queueKey = resolved.key ?? resolved.entry.sessionId;
|
||||
|
||||
@@ -17,7 +17,7 @@ describe("resolveAnnounceTarget", () => {
|
||||
sessionKey: "agent:main:discord:group:dev",
|
||||
displayKey: "agent:main:discord:group:dev",
|
||||
});
|
||||
expect(target).toEqual({ provider: "discord", to: "channel:dev" });
|
||||
expect(target).toEqual({ channel: "discord", to: "channel:dev" });
|
||||
expect(callGatewayMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ describe("resolveAnnounceTarget", () => {
|
||||
sessions: [
|
||||
{
|
||||
key: "agent:main:whatsapp:group:123@g.us",
|
||||
lastProvider: "whatsapp",
|
||||
lastChannel: "whatsapp",
|
||||
lastTo: "123@g.us",
|
||||
lastAccountId: "work",
|
||||
},
|
||||
@@ -38,7 +38,7 @@ describe("resolveAnnounceTarget", () => {
|
||||
displayKey: "agent:main:whatsapp:group:123@g.us",
|
||||
});
|
||||
expect(target).toEqual({
|
||||
provider: "whatsapp",
|
||||
channel: "whatsapp",
|
||||
to: "123@g.us",
|
||||
accountId: "work",
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { callGateway } from "../../gateway/call.js";
|
||||
import {
|
||||
getProviderPlugin,
|
||||
normalizeProviderId,
|
||||
} from "../../providers/plugins/index.js";
|
||||
getChannelPlugin,
|
||||
normalizeChannelId,
|
||||
} from "../../channels/plugins/index.js";
|
||||
import { callGateway } from "../../gateway/call.js";
|
||||
import type { AnnounceTarget } from "./sessions-send-helpers.js";
|
||||
import { resolveAnnounceTargetFromKey } from "./sessions-send-helpers.js";
|
||||
|
||||
@@ -15,8 +15,8 @@ export async function resolveAnnounceTarget(params: {
|
||||
const fallback = parsed ?? parsedDisplay ?? null;
|
||||
|
||||
if (fallback) {
|
||||
const normalized = normalizeProviderId(fallback.provider);
|
||||
const plugin = normalized ? getProviderPlugin(normalized) : null;
|
||||
const normalized = normalizeChannelId(fallback.channel);
|
||||
const plugin = normalized ? getChannelPlugin(normalized) : null;
|
||||
if (!plugin?.meta?.preferSessionLookupForAnnounceTarget) {
|
||||
return fallback;
|
||||
}
|
||||
@@ -35,14 +35,14 @@ export async function resolveAnnounceTarget(params: {
|
||||
const match =
|
||||
sessions.find((entry) => entry?.key === params.sessionKey) ??
|
||||
sessions.find((entry) => entry?.key === params.displayKey);
|
||||
const provider =
|
||||
typeof match?.lastProvider === "string" ? match.lastProvider : undefined;
|
||||
const channel =
|
||||
typeof match?.lastChannel === "string" ? match.lastChannel : undefined;
|
||||
const to = typeof match?.lastTo === "string" ? match.lastTo : undefined;
|
||||
const accountId =
|
||||
typeof match?.lastAccountId === "string"
|
||||
? match.lastAccountId
|
||||
: undefined;
|
||||
if (provider && to) return { provider, to, accountId };
|
||||
if (channel && to) return { channel, to, accountId };
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@@ -56,11 +56,11 @@ export function classifySessionKind(params: {
|
||||
return "other";
|
||||
}
|
||||
|
||||
export function deriveProvider(params: {
|
||||
export function deriveChannel(params: {
|
||||
key: string;
|
||||
kind: SessionKind;
|
||||
provider?: string | null;
|
||||
lastProvider?: string | null;
|
||||
channel?: string | null;
|
||||
lastChannel?: string | null;
|
||||
}): string {
|
||||
if (
|
||||
params.kind === "cron" ||
|
||||
@@ -68,10 +68,10 @@ export function deriveProvider(params: {
|
||||
params.kind === "node"
|
||||
)
|
||||
return "internal";
|
||||
const provider = normalizeKey(params.provider ?? undefined);
|
||||
if (provider) return provider;
|
||||
const lastProvider = normalizeKey(params.lastProvider ?? undefined);
|
||||
if (lastProvider) return lastProvider;
|
||||
const channel = normalizeKey(params.channel ?? undefined);
|
||||
if (channel) return channel;
|
||||
const lastChannel = normalizeKey(params.lastChannel ?? undefined);
|
||||
if (lastChannel) return lastChannel;
|
||||
const parts = params.key.split(":").filter(Boolean);
|
||||
if (parts.length >= 3 && (parts[1] === "group" || parts[1] === "channel")) {
|
||||
return parts[0];
|
||||
|
||||
@@ -13,7 +13,7 @@ import type { AnyAgentTool } from "./common.js";
|
||||
import { jsonResult, readStringArrayParam } from "./common.js";
|
||||
import {
|
||||
classifySessionKind,
|
||||
deriveProvider,
|
||||
deriveChannel,
|
||||
resolveDisplaySessionKey,
|
||||
resolveInternalSessionKey,
|
||||
resolveMainSessionAlias,
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
type SessionListRow = {
|
||||
key: string;
|
||||
kind: SessionKind;
|
||||
provider: string;
|
||||
channel: string;
|
||||
label?: string;
|
||||
displayName?: string;
|
||||
updatedAt?: number | null;
|
||||
@@ -37,7 +37,7 @@ type SessionListRow = {
|
||||
systemSent?: boolean;
|
||||
abortedLastRun?: boolean;
|
||||
sendPolicy?: string;
|
||||
lastProvider?: string;
|
||||
lastChannel?: string;
|
||||
lastTo?: string;
|
||||
lastAccountId?: string;
|
||||
transcriptPath?: string;
|
||||
@@ -178,21 +178,19 @@ export function createSessionsListTool(opts?: {
|
||||
mainKey,
|
||||
});
|
||||
|
||||
const entryProvider =
|
||||
typeof entry.provider === "string" ? entry.provider : undefined;
|
||||
const lastProvider =
|
||||
typeof entry.lastProvider === "string"
|
||||
? entry.lastProvider
|
||||
: undefined;
|
||||
const entryChannel =
|
||||
typeof entry.channel === "string" ? entry.channel : undefined;
|
||||
const lastChannel =
|
||||
typeof entry.lastChannel === "string" ? entry.lastChannel : undefined;
|
||||
const lastAccountId =
|
||||
typeof entry.lastAccountId === "string"
|
||||
? entry.lastAccountId
|
||||
: undefined;
|
||||
const derivedProvider = deriveProvider({
|
||||
const derivedChannel = deriveChannel({
|
||||
key,
|
||||
kind,
|
||||
provider: entryProvider,
|
||||
lastProvider,
|
||||
channel: entryChannel,
|
||||
lastChannel,
|
||||
});
|
||||
|
||||
const sessionId =
|
||||
@@ -205,7 +203,7 @@ export function createSessionsListTool(opts?: {
|
||||
const row: SessionListRow = {
|
||||
key: displayKey,
|
||||
kind,
|
||||
provider: derivedProvider,
|
||||
channel: derivedChannel,
|
||||
label: typeof entry.label === "string" ? entry.label : undefined,
|
||||
displayName:
|
||||
typeof entry.displayName === "string"
|
||||
@@ -241,7 +239,7 @@ export function createSessionsListTool(opts?: {
|
||||
: undefined,
|
||||
sendPolicy:
|
||||
typeof entry.sendPolicy === "string" ? entry.sendPolicy : undefined,
|
||||
lastProvider,
|
||||
lastChannel,
|
||||
lastTo: typeof entry.lastTo === "string" ? entry.lastTo : undefined,
|
||||
lastAccountId,
|
||||
transcriptPath,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import {
|
||||
getProviderPlugin,
|
||||
normalizeProviderId,
|
||||
} from "../../providers/plugins/index.js";
|
||||
getChannelPlugin,
|
||||
normalizeChannelId,
|
||||
} from "../../channels/plugins/index.js";
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
|
||||
const ANNOUNCE_SKIP_TOKEN = "ANNOUNCE_SKIP";
|
||||
const REPLY_SKIP_TOKEN = "REPLY_SKIP";
|
||||
@@ -10,7 +10,7 @@ const DEFAULT_PING_PONG_TURNS = 5;
|
||||
const MAX_PING_PONG_TURNS = 5;
|
||||
|
||||
export type AnnounceTarget = {
|
||||
provider: string;
|
||||
channel: string;
|
||||
to: string;
|
||||
accountId?: string;
|
||||
};
|
||||
@@ -24,29 +24,29 @@ export function resolveAnnounceTargetFromKey(
|
||||
? rawParts.slice(2)
|
||||
: rawParts;
|
||||
if (parts.length < 3) return null;
|
||||
const [providerRaw, kind, ...rest] = parts;
|
||||
const [channelRaw, kind, ...rest] = parts;
|
||||
if (kind !== "group" && kind !== "channel") return null;
|
||||
const id = rest.join(":").trim();
|
||||
if (!id) return null;
|
||||
if (!providerRaw) return null;
|
||||
const normalizedProvider = normalizeProviderId(providerRaw);
|
||||
const provider = normalizedProvider ?? providerRaw.toLowerCase();
|
||||
const kindTarget = normalizedProvider
|
||||
if (!channelRaw) return null;
|
||||
const normalizedChannel = normalizeChannelId(channelRaw);
|
||||
const channel = normalizedChannel ?? channelRaw.toLowerCase();
|
||||
const kindTarget = normalizedChannel
|
||||
? kind === "channel"
|
||||
? `channel:${id}`
|
||||
: `group:${id}`
|
||||
: id;
|
||||
const normalized = normalizedProvider
|
||||
? getProviderPlugin(normalizedProvider)?.messaging?.normalizeTarget?.(
|
||||
const normalized = normalizedChannel
|
||||
? getChannelPlugin(normalizedChannel)?.messaging?.normalizeTarget?.(
|
||||
kindTarget,
|
||||
)
|
||||
: undefined;
|
||||
return { provider, to: normalized ?? kindTarget };
|
||||
return { channel, to: normalized ?? kindTarget };
|
||||
}
|
||||
|
||||
export function buildAgentToAgentMessageContext(params: {
|
||||
requesterSessionKey?: string;
|
||||
requesterProvider?: string;
|
||||
requesterChannel?: string;
|
||||
targetSessionKey: string;
|
||||
}) {
|
||||
const lines = [
|
||||
@@ -54,8 +54,8 @@ export function buildAgentToAgentMessageContext(params: {
|
||||
params.requesterSessionKey
|
||||
? `Agent 1 (requester) session: ${params.requesterSessionKey}.`
|
||||
: undefined,
|
||||
params.requesterProvider
|
||||
? `Agent 1 (requester) provider: ${params.requesterProvider}.`
|
||||
params.requesterChannel
|
||||
? `Agent 1 (requester) channel: ${params.requesterChannel}.`
|
||||
: undefined,
|
||||
`Agent 2 (target) session: ${params.targetSessionKey}.`,
|
||||
].filter(Boolean);
|
||||
@@ -64,9 +64,9 @@ export function buildAgentToAgentMessageContext(params: {
|
||||
|
||||
export function buildAgentToAgentReplyContext(params: {
|
||||
requesterSessionKey?: string;
|
||||
requesterProvider?: string;
|
||||
requesterChannel?: string;
|
||||
targetSessionKey: string;
|
||||
targetProvider?: string;
|
||||
targetChannel?: string;
|
||||
currentRole: "requester" | "target";
|
||||
turn: number;
|
||||
maxTurns: number;
|
||||
@@ -82,12 +82,12 @@ export function buildAgentToAgentReplyContext(params: {
|
||||
params.requesterSessionKey
|
||||
? `Agent 1 (requester) session: ${params.requesterSessionKey}.`
|
||||
: undefined,
|
||||
params.requesterProvider
|
||||
? `Agent 1 (requester) provider: ${params.requesterProvider}.`
|
||||
params.requesterChannel
|
||||
? `Agent 1 (requester) channel: ${params.requesterChannel}.`
|
||||
: undefined,
|
||||
`Agent 2 (target) session: ${params.targetSessionKey}.`,
|
||||
params.targetProvider
|
||||
? `Agent 2 (target) provider: ${params.targetProvider}.`
|
||||
params.targetChannel
|
||||
? `Agent 2 (target) channel: ${params.targetChannel}.`
|
||||
: undefined,
|
||||
`If you want to stop the ping-pong, reply exactly "${REPLY_SKIP_TOKEN}".`,
|
||||
].filter(Boolean);
|
||||
@@ -96,9 +96,9 @@ export function buildAgentToAgentReplyContext(params: {
|
||||
|
||||
export function buildAgentToAgentAnnounceContext(params: {
|
||||
requesterSessionKey?: string;
|
||||
requesterProvider?: string;
|
||||
requesterChannel?: string;
|
||||
targetSessionKey: string;
|
||||
targetProvider?: string;
|
||||
targetChannel?: string;
|
||||
originalMessage: string;
|
||||
roundOneReply?: string;
|
||||
latestReply?: string;
|
||||
@@ -108,12 +108,12 @@ export function buildAgentToAgentAnnounceContext(params: {
|
||||
params.requesterSessionKey
|
||||
? `Agent 1 (requester) session: ${params.requesterSessionKey}.`
|
||||
: undefined,
|
||||
params.requesterProvider
|
||||
? `Agent 1 (requester) provider: ${params.requesterProvider}.`
|
||||
params.requesterChannel
|
||||
? `Agent 1 (requester) channel: ${params.requesterChannel}.`
|
||||
: undefined,
|
||||
`Agent 2 (target) session: ${params.targetSessionKey}.`,
|
||||
params.targetProvider
|
||||
? `Agent 2 (target) provider: ${params.targetProvider}.`
|
||||
params.targetChannel
|
||||
? `Agent 2 (target) channel: ${params.targetChannel}.`
|
||||
: undefined,
|
||||
`Original request: ${params.originalMessage}`,
|
||||
params.roundOneReply
|
||||
@@ -123,7 +123,7 @@ export function buildAgentToAgentAnnounceContext(params: {
|
||||
? `Latest reply: ${params.latestReply}`
|
||||
: "Latest reply: (not available).",
|
||||
`If you want to remain silent, reply exactly "${ANNOUNCE_SKIP_TOKEN}".`,
|
||||
"Any other reply will be posted to the target provider.",
|
||||
"Any other reply will be posted to the target channel.",
|
||||
"After this reply, the agent-to-agent conversation is over.",
|
||||
].filter(Boolean);
|
||||
return lines.join("\n");
|
||||
|
||||
@@ -28,7 +28,7 @@ describe("sessions_send gating", () => {
|
||||
it("blocks cross-agent sends when tools.agentToAgent.enabled is false", async () => {
|
||||
const tool = createSessionsSendTool({
|
||||
agentSessionKey: "agent:main:main",
|
||||
agentProvider: "whatsapp",
|
||||
agentChannel: "whatsapp",
|
||||
});
|
||||
|
||||
const result = await tool.execute("call1", {
|
||||
|
||||
@@ -13,9 +13,9 @@ import {
|
||||
} from "../../routing/session-key.js";
|
||||
import { SESSION_LABEL_MAX_LENGTH } from "../../sessions/session-label.js";
|
||||
import {
|
||||
type GatewayMessageProvider,
|
||||
INTERNAL_MESSAGE_PROVIDER,
|
||||
} from "../../utils/message-provider.js";
|
||||
type GatewayMessageChannel,
|
||||
INTERNAL_MESSAGE_CHANNEL,
|
||||
} from "../../utils/message-channel.js";
|
||||
import { AGENT_LANE_NESTED } from "../lanes.js";
|
||||
import { readLatestAssistantReply, runAgentStep } from "./agent-step.js";
|
||||
import type { AnyAgentTool } from "./common.js";
|
||||
@@ -51,7 +51,7 @@ const SessionsSendToolSchema = Type.Object({
|
||||
|
||||
export function createSessionsSendTool(opts?: {
|
||||
agentSessionKey?: string;
|
||||
agentProvider?: GatewayMessageProvider;
|
||||
agentChannel?: GatewayMessageChannel;
|
||||
sandboxed?: boolean;
|
||||
}): AnyAgentTool {
|
||||
return {
|
||||
@@ -297,7 +297,7 @@ export function createSessionsSendTool(opts?: {
|
||||
|
||||
const agentMessageContext = buildAgentToAgentMessageContext({
|
||||
requesterSessionKey: opts?.agentSessionKey,
|
||||
requesterProvider: opts?.agentProvider,
|
||||
requesterChannel: opts?.agentChannel,
|
||||
targetSessionKey: displayKey,
|
||||
});
|
||||
const sendParams = {
|
||||
@@ -305,12 +305,12 @@ export function createSessionsSendTool(opts?: {
|
||||
sessionKey: resolvedKey,
|
||||
idempotencyKey,
|
||||
deliver: false,
|
||||
provider: INTERNAL_MESSAGE_PROVIDER,
|
||||
channel: INTERNAL_MESSAGE_CHANNEL,
|
||||
lane: AGENT_LANE_NESTED,
|
||||
extraSystemPrompt: agentMessageContext,
|
||||
};
|
||||
const requesterSessionKey = opts?.agentSessionKey;
|
||||
const requesterProvider = opts?.agentProvider;
|
||||
const requesterChannel = opts?.agentChannel;
|
||||
const maxPingPongTurns = resolvePingPongTurns(cfg);
|
||||
const delivery = { status: "pending", mode: "announce" as const };
|
||||
|
||||
@@ -344,7 +344,7 @@ export function createSessionsSendTool(opts?: {
|
||||
sessionKey: resolvedKey,
|
||||
displayKey,
|
||||
});
|
||||
const targetProvider = announceTarget?.provider ?? "unknown";
|
||||
const targetChannel = announceTarget?.channel ?? "unknown";
|
||||
if (
|
||||
maxPingPongTurns > 0 &&
|
||||
requesterSessionKey &&
|
||||
@@ -360,9 +360,9 @@ export function createSessionsSendTool(opts?: {
|
||||
: "target";
|
||||
const replyPrompt = buildAgentToAgentReplyContext({
|
||||
requesterSessionKey,
|
||||
requesterProvider,
|
||||
requesterChannel,
|
||||
targetSessionKey: displayKey,
|
||||
targetProvider,
|
||||
targetChannel,
|
||||
currentRole,
|
||||
turn,
|
||||
maxTurns: maxPingPongTurns,
|
||||
@@ -386,9 +386,9 @@ export function createSessionsSendTool(opts?: {
|
||||
}
|
||||
const announcePrompt = buildAgentToAgentAnnounceContext({
|
||||
requesterSessionKey,
|
||||
requesterProvider,
|
||||
requesterChannel,
|
||||
targetSessionKey: displayKey,
|
||||
targetProvider,
|
||||
targetChannel,
|
||||
originalMessage: message,
|
||||
roundOneReply: primaryReply,
|
||||
latestReply,
|
||||
@@ -412,7 +412,7 @@ export function createSessionsSendTool(opts?: {
|
||||
params: {
|
||||
to: announceTarget.to,
|
||||
message: announceReply.trim(),
|
||||
provider: announceTarget.provider,
|
||||
channel: announceTarget.channel,
|
||||
accountId: announceTarget.accountId,
|
||||
idempotencyKey: crypto.randomUUID(),
|
||||
},
|
||||
@@ -421,7 +421,7 @@ export function createSessionsSendTool(opts?: {
|
||||
} catch (err) {
|
||||
log.warn("sessions_send announce delivery failed", {
|
||||
runId: runContextId,
|
||||
provider: announceTarget.provider,
|
||||
channel: announceTarget.channel,
|
||||
to: announceTarget.to,
|
||||
error: formatErrorMessage(err),
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
normalizeAgentId,
|
||||
parseAgentSessionKey,
|
||||
} from "../../routing/session-key.js";
|
||||
import type { GatewayMessageProvider } from "../../utils/message-provider.js";
|
||||
import type { GatewayMessageChannel } from "../../utils/message-channel.js";
|
||||
import { resolveAgentConfig } from "../agent-scope.js";
|
||||
import { AGENT_LANE_SUBAGENT } from "../lanes.js";
|
||||
import { optionalStringEnum } from "../schema/typebox.js";
|
||||
@@ -47,7 +47,7 @@ function normalizeModelSelection(value: unknown): string | undefined {
|
||||
|
||||
export function createSessionsSpawnTool(opts?: {
|
||||
agentSessionKey?: string;
|
||||
agentProvider?: GatewayMessageProvider;
|
||||
agentChannel?: GatewayMessageChannel;
|
||||
sandboxed?: boolean;
|
||||
}): AnyAgentTool {
|
||||
return {
|
||||
@@ -174,7 +174,7 @@ export function createSessionsSpawnTool(opts?: {
|
||||
}
|
||||
const childSystemPrompt = buildSubagentSystemPrompt({
|
||||
requesterSessionKey,
|
||||
requesterProvider: opts?.agentProvider,
|
||||
requesterChannel: opts?.agentChannel,
|
||||
childSessionKey,
|
||||
label: label || undefined,
|
||||
task,
|
||||
@@ -188,7 +188,7 @@ export function createSessionsSpawnTool(opts?: {
|
||||
params: {
|
||||
message: task,
|
||||
sessionKey: childSessionKey,
|
||||
provider: opts?.agentProvider,
|
||||
channel: opts?.agentChannel,
|
||||
idempotencyKey: childIdem,
|
||||
deliver: false,
|
||||
lane: AGENT_LANE_SUBAGENT,
|
||||
@@ -221,7 +221,7 @@ export function createSessionsSpawnTool(opts?: {
|
||||
runId: childRunId,
|
||||
childSessionKey,
|
||||
requesterSessionKey: requesterInternalKey,
|
||||
requesterProvider: opts?.agentProvider,
|
||||
requesterChannel: opts?.agentChannel,
|
||||
requesterDisplayKey,
|
||||
task,
|
||||
cleanup,
|
||||
|
||||
@@ -36,7 +36,7 @@ vi.mock("../../slack/actions.js", () => ({
|
||||
|
||||
describe("handleSlackAction", () => {
|
||||
it("adds reactions", async () => {
|
||||
const cfg = { slack: { botToken: "tok" } } as ClawdbotConfig;
|
||||
const cfg = { channels: { slack: { botToken: "tok" } } } as ClawdbotConfig;
|
||||
await handleSlackAction(
|
||||
{
|
||||
action: "react",
|
||||
@@ -50,7 +50,7 @@ describe("handleSlackAction", () => {
|
||||
});
|
||||
|
||||
it("removes reactions on empty emoji", async () => {
|
||||
const cfg = { slack: { botToken: "tok" } } as ClawdbotConfig;
|
||||
const cfg = { channels: { slack: { botToken: "tok" } } } as ClawdbotConfig;
|
||||
await handleSlackAction(
|
||||
{
|
||||
action: "react",
|
||||
@@ -64,7 +64,7 @@ describe("handleSlackAction", () => {
|
||||
});
|
||||
|
||||
it("removes reactions when remove flag set", async () => {
|
||||
const cfg = { slack: { botToken: "tok" } } as ClawdbotConfig;
|
||||
const cfg = { channels: { slack: { botToken: "tok" } } } as ClawdbotConfig;
|
||||
await handleSlackAction(
|
||||
{
|
||||
action: "react",
|
||||
@@ -79,7 +79,7 @@ describe("handleSlackAction", () => {
|
||||
});
|
||||
|
||||
it("rejects removes without emoji", async () => {
|
||||
const cfg = { slack: { botToken: "tok" } } as ClawdbotConfig;
|
||||
const cfg = { channels: { slack: { botToken: "tok" } } } as ClawdbotConfig;
|
||||
await expect(
|
||||
handleSlackAction(
|
||||
{
|
||||
@@ -96,7 +96,7 @@ describe("handleSlackAction", () => {
|
||||
|
||||
it("respects reaction gating", async () => {
|
||||
const cfg = {
|
||||
slack: { botToken: "tok", actions: { reactions: false } },
|
||||
channels: { slack: { botToken: "tok", actions: { reactions: false } } },
|
||||
} as ClawdbotConfig;
|
||||
await expect(
|
||||
handleSlackAction(
|
||||
@@ -112,7 +112,7 @@ describe("handleSlackAction", () => {
|
||||
});
|
||||
|
||||
it("passes threadTs to sendSlackMessage for thread replies", async () => {
|
||||
const cfg = { slack: { botToken: "tok" } } as ClawdbotConfig;
|
||||
const cfg = { channels: { slack: { botToken: "tok" } } } as ClawdbotConfig;
|
||||
await handleSlackAction(
|
||||
{
|
||||
action: "sendMessage",
|
||||
@@ -133,7 +133,7 @@ describe("handleSlackAction", () => {
|
||||
});
|
||||
|
||||
it("auto-injects threadTs from context when replyToMode=all", async () => {
|
||||
const cfg = { slack: { botToken: "tok" } } as ClawdbotConfig;
|
||||
const cfg = { channels: { slack: { botToken: "tok" } } } as ClawdbotConfig;
|
||||
sendSlackMessage.mockClear();
|
||||
await handleSlackAction(
|
||||
{
|
||||
@@ -159,7 +159,7 @@ describe("handleSlackAction", () => {
|
||||
});
|
||||
|
||||
it("replyToMode=first threads first message then stops", async () => {
|
||||
const cfg = { slack: { botToken: "tok" } } as ClawdbotConfig;
|
||||
const cfg = { channels: { slack: { botToken: "tok" } } } as ClawdbotConfig;
|
||||
sendSlackMessage.mockClear();
|
||||
const hasRepliedRef = { value: false };
|
||||
const context = {
|
||||
@@ -198,7 +198,7 @@ describe("handleSlackAction", () => {
|
||||
});
|
||||
|
||||
it("replyToMode=first marks hasRepliedRef even when threadTs is explicit", async () => {
|
||||
const cfg = { slack: { botToken: "tok" } } as ClawdbotConfig;
|
||||
const cfg = { channels: { slack: { botToken: "tok" } } } as ClawdbotConfig;
|
||||
sendSlackMessage.mockClear();
|
||||
const hasRepliedRef = { value: false };
|
||||
const context = {
|
||||
@@ -244,7 +244,7 @@ describe("handleSlackAction", () => {
|
||||
});
|
||||
|
||||
it("replyToMode=first without hasRepliedRef does not thread", async () => {
|
||||
const cfg = { slack: { botToken: "tok" } } as ClawdbotConfig;
|
||||
const cfg = { channels: { slack: { botToken: "tok" } } } as ClawdbotConfig;
|
||||
sendSlackMessage.mockClear();
|
||||
await handleSlackAction(
|
||||
{ action: "sendMessage", to: "channel:C123", content: "No ref" },
|
||||
@@ -263,7 +263,7 @@ describe("handleSlackAction", () => {
|
||||
});
|
||||
|
||||
it("does not auto-inject threadTs when replyToMode=off", async () => {
|
||||
const cfg = { slack: { botToken: "tok" } } as ClawdbotConfig;
|
||||
const cfg = { channels: { slack: { botToken: "tok" } } } as ClawdbotConfig;
|
||||
sendSlackMessage.mockClear();
|
||||
await handleSlackAction(
|
||||
{
|
||||
@@ -285,7 +285,7 @@ describe("handleSlackAction", () => {
|
||||
});
|
||||
|
||||
it("does not auto-inject threadTs when sending to different channel", async () => {
|
||||
const cfg = { slack: { botToken: "tok" } } as ClawdbotConfig;
|
||||
const cfg = { channels: { slack: { botToken: "tok" } } } as ClawdbotConfig;
|
||||
sendSlackMessage.mockClear();
|
||||
await handleSlackAction(
|
||||
{
|
||||
@@ -311,7 +311,7 @@ describe("handleSlackAction", () => {
|
||||
});
|
||||
|
||||
it("explicit threadTs overrides context threadTs", async () => {
|
||||
const cfg = { slack: { botToken: "tok" } } as ClawdbotConfig;
|
||||
const cfg = { channels: { slack: { botToken: "tok" } } } as ClawdbotConfig;
|
||||
sendSlackMessage.mockClear();
|
||||
await handleSlackAction(
|
||||
{
|
||||
@@ -338,7 +338,7 @@ describe("handleSlackAction", () => {
|
||||
});
|
||||
|
||||
it("handles channel target without prefix when replyToMode=all", async () => {
|
||||
const cfg = { slack: { botToken: "tok" } } as ClawdbotConfig;
|
||||
const cfg = { channels: { slack: { botToken: "tok" } } } as ClawdbotConfig;
|
||||
sendSlackMessage.mockClear();
|
||||
await handleSlackAction(
|
||||
{
|
||||
|
||||
@@ -93,7 +93,7 @@ export async function handleSlackAction(
|
||||
const accountId = readStringParam(params, "accountId");
|
||||
const accountOpts = accountId ? { accountId } : undefined;
|
||||
const account = resolveSlackAccount({ cfg, accountId });
|
||||
const actionConfig = account.actions ?? cfg.slack?.actions;
|
||||
const actionConfig = account.actions ?? cfg.channels?.slack?.actions;
|
||||
const isActionEnabled = createActionGate(actionConfig);
|
||||
|
||||
if (reactionsActions.has(action)) {
|
||||
|
||||
@@ -34,7 +34,9 @@ describe("handleTelegramAction", () => {
|
||||
});
|
||||
|
||||
it("adds reactions", async () => {
|
||||
const cfg = { telegram: { botToken: "tok" } } as ClawdbotConfig;
|
||||
const cfg = {
|
||||
channels: { telegram: { botToken: "tok" } },
|
||||
} as ClawdbotConfig;
|
||||
await handleTelegramAction(
|
||||
{
|
||||
action: "react",
|
||||
@@ -53,7 +55,9 @@ describe("handleTelegramAction", () => {
|
||||
});
|
||||
|
||||
it("removes reactions on empty emoji", async () => {
|
||||
const cfg = { telegram: { botToken: "tok" } } as ClawdbotConfig;
|
||||
const cfg = {
|
||||
channels: { telegram: { botToken: "tok" } },
|
||||
} as ClawdbotConfig;
|
||||
await handleTelegramAction(
|
||||
{
|
||||
action: "react",
|
||||
@@ -72,7 +76,9 @@ describe("handleTelegramAction", () => {
|
||||
});
|
||||
|
||||
it("removes reactions when remove flag set", async () => {
|
||||
const cfg = { telegram: { botToken: "tok" } } as ClawdbotConfig;
|
||||
const cfg = {
|
||||
channels: { telegram: { botToken: "tok" } },
|
||||
} as ClawdbotConfig;
|
||||
await handleTelegramAction(
|
||||
{
|
||||
action: "react",
|
||||
@@ -93,7 +99,9 @@ describe("handleTelegramAction", () => {
|
||||
|
||||
it("respects reaction gating", async () => {
|
||||
const cfg = {
|
||||
telegram: { botToken: "tok", actions: { reactions: false } },
|
||||
channels: {
|
||||
telegram: { botToken: "tok", actions: { reactions: false } },
|
||||
},
|
||||
} as ClawdbotConfig;
|
||||
await expect(
|
||||
handleTelegramAction(
|
||||
@@ -109,7 +117,9 @@ describe("handleTelegramAction", () => {
|
||||
});
|
||||
|
||||
it("sends a text message", async () => {
|
||||
const cfg = { telegram: { botToken: "tok" } } as ClawdbotConfig;
|
||||
const cfg = {
|
||||
channels: { telegram: { botToken: "tok" } },
|
||||
} as ClawdbotConfig;
|
||||
const result = await handleTelegramAction(
|
||||
{
|
||||
action: "sendMessage",
|
||||
@@ -130,7 +140,9 @@ describe("handleTelegramAction", () => {
|
||||
});
|
||||
|
||||
it("sends a message with media", async () => {
|
||||
const cfg = { telegram: { botToken: "tok" } } as ClawdbotConfig;
|
||||
const cfg = {
|
||||
channels: { telegram: { botToken: "tok" } },
|
||||
} as ClawdbotConfig;
|
||||
await handleTelegramAction(
|
||||
{
|
||||
action: "sendMessage",
|
||||
@@ -152,7 +164,9 @@ describe("handleTelegramAction", () => {
|
||||
|
||||
it("respects sendMessage gating", async () => {
|
||||
const cfg = {
|
||||
telegram: { botToken: "tok", actions: { sendMessage: false } },
|
||||
channels: {
|
||||
telegram: { botToken: "tok", actions: { sendMessage: false } },
|
||||
},
|
||||
} as ClawdbotConfig;
|
||||
await expect(
|
||||
handleTelegramAction(
|
||||
@@ -182,7 +196,9 @@ describe("handleTelegramAction", () => {
|
||||
});
|
||||
|
||||
it("requires inlineButtons capability when buttons are provided", async () => {
|
||||
const cfg = { telegram: { botToken: "tok" } } as ClawdbotConfig;
|
||||
const cfg = {
|
||||
channels: { telegram: { botToken: "tok" } },
|
||||
} as ClawdbotConfig;
|
||||
await expect(
|
||||
handleTelegramAction(
|
||||
{
|
||||
@@ -198,7 +214,9 @@ describe("handleTelegramAction", () => {
|
||||
|
||||
it("sends messages with inline keyboard buttons when enabled", async () => {
|
||||
const cfg = {
|
||||
telegram: { botToken: "tok", capabilities: ["inlineButtons"] },
|
||||
channels: {
|
||||
telegram: { botToken: "tok", capabilities: ["inlineButtons"] },
|
||||
},
|
||||
} as ClawdbotConfig;
|
||||
await handleTelegramAction(
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
|
||||
import { resolveChannelCapabilities } from "../../config/channel-capabilities.js";
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import { resolveProviderCapabilities } from "../../config/provider-capabilities.js";
|
||||
import {
|
||||
reactMessageTelegram,
|
||||
sendMessageTelegram,
|
||||
@@ -26,9 +25,9 @@ function hasInlineButtonsCapability(params: {
|
||||
accountId?: string | undefined;
|
||||
}): boolean {
|
||||
const caps =
|
||||
resolveProviderCapabilities({
|
||||
resolveChannelCapabilities({
|
||||
cfg: params.cfg,
|
||||
provider: "telegram",
|
||||
channel: "telegram",
|
||||
accountId: params.accountId,
|
||||
}) ?? [];
|
||||
return caps.some((cap) => cap.toLowerCase() === "inlinebuttons");
|
||||
@@ -84,7 +83,7 @@ export async function handleTelegramAction(
|
||||
): Promise<AgentToolResult<unknown>> {
|
||||
const action = readStringParam(params, "action", { required: true });
|
||||
const accountId = readStringParam(params, "accountId");
|
||||
const isActionEnabled = createActionGate(cfg.telegram?.actions);
|
||||
const isActionEnabled = createActionGate(cfg.channels?.telegram?.actions);
|
||||
|
||||
if (action === "react") {
|
||||
if (!isActionEnabled("reactions")) {
|
||||
@@ -103,7 +102,7 @@ export async function handleTelegramAction(
|
||||
const token = resolveTelegramToken(cfg, { accountId }).token;
|
||||
if (!token) {
|
||||
throw new Error(
|
||||
"Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or telegram.botToken.",
|
||||
"Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or channels.telegram.botToken.",
|
||||
);
|
||||
}
|
||||
await reactMessageTelegram(chatId ?? "", messageId ?? 0, emoji ?? "", {
|
||||
@@ -130,7 +129,7 @@ export async function handleTelegramAction(
|
||||
!hasInlineButtonsCapability({ cfg, accountId: accountId ?? undefined })
|
||||
) {
|
||||
throw new Error(
|
||||
'Telegram inline buttons requested but not enabled. Add "inlineButtons" to telegram.capabilities (or telegram.accounts.<id>.capabilities).',
|
||||
'Telegram inline buttons requested but not enabled. Add "inlineButtons" to channels.telegram.capabilities (or channels.telegram.accounts.<id>.capabilities).',
|
||||
);
|
||||
}
|
||||
// Optional threading parameters for forum topics and reply chains
|
||||
@@ -143,7 +142,7 @@ export async function handleTelegramAction(
|
||||
const token = resolveTelegramToken(cfg, { accountId }).token;
|
||||
if (!token) {
|
||||
throw new Error(
|
||||
"Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or telegram.botToken.",
|
||||
"Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or channels.telegram.botToken.",
|
||||
);
|
||||
}
|
||||
const result = await sendMessageTelegram(to, content, {
|
||||
|
||||
@@ -10,7 +10,7 @@ vi.mock("../../web/outbound.js", () => ({
|
||||
}));
|
||||
|
||||
const enabledConfig = {
|
||||
whatsapp: { actions: { reactions: true } },
|
||||
channels: { whatsapp: { actions: { reactions: true } } },
|
||||
} as ClawdbotConfig;
|
||||
|
||||
describe("handleWhatsAppAction", () => {
|
||||
@@ -112,7 +112,7 @@ describe("handleWhatsAppAction", () => {
|
||||
|
||||
it("respects reaction gating", async () => {
|
||||
const cfg = {
|
||||
whatsapp: { actions: { reactions: false } },
|
||||
channels: { whatsapp: { actions: { reactions: false } } },
|
||||
} as ClawdbotConfig;
|
||||
await expect(
|
||||
handleWhatsAppAction(
|
||||
|
||||
@@ -14,7 +14,7 @@ export async function handleWhatsAppAction(
|
||||
cfg: ClawdbotConfig,
|
||||
): Promise<AgentToolResult<unknown>> {
|
||||
const action = readStringParam(params, "action", { required: true });
|
||||
const isActionEnabled = createActionGate(cfg.whatsapp?.actions);
|
||||
const isActionEnabled = createActionGate(cfg.channels?.whatsapp?.actions);
|
||||
|
||||
if (action === "react") {
|
||||
if (!isActionEnabled("reactions")) {
|
||||
|
||||
Reference in New Issue
Block a user