refactor!: rename chat providers to channels
This commit is contained in:
17
src/agents/channel-tools.ts
Normal file
17
src/agents/channel-tools.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { listChannelPlugins } from "../channels/plugins/index.js";
|
||||
import type { ChannelAgentTool } from "../channels/plugins/types.js";
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
|
||||
export function listChannelAgentTools(params: {
|
||||
cfg?: ClawdbotConfig;
|
||||
}): ChannelAgentTool[] {
|
||||
// Channel docking: aggregate channel-owned tools (login, etc.).
|
||||
const tools: ChannelAgentTool[] = [];
|
||||
for (const plugin of listChannelPlugins()) {
|
||||
const entry = plugin.agentTools;
|
||||
if (!entry) continue;
|
||||
const resolved = typeof entry === "function" ? entry(params) : entry;
|
||||
if (Array.isArray(resolved)) tools.push(...resolved);
|
||||
}
|
||||
return tools;
|
||||
}
|
||||
@@ -73,14 +73,14 @@ describe("sessions tools", () => {
|
||||
kind: "direct",
|
||||
sessionId: "s-main",
|
||||
updatedAt: 10,
|
||||
lastProvider: "whatsapp",
|
||||
lastChannel: "whatsapp",
|
||||
},
|
||||
{
|
||||
key: "discord:group:dev",
|
||||
kind: "group",
|
||||
sessionId: "s-group",
|
||||
updatedAt: 11,
|
||||
provider: "discord",
|
||||
channel: "discord",
|
||||
displayName: "discord:g-dev",
|
||||
},
|
||||
{
|
||||
@@ -120,7 +120,7 @@ describe("sessions tools", () => {
|
||||
};
|
||||
expect(details.sessions).toHaveLength(3);
|
||||
const main = details.sessions?.find((s) => s.key === "main");
|
||||
expect(main?.provider).toBe("whatsapp");
|
||||
expect(main?.channel).toBe("whatsapp");
|
||||
expect(main?.messages?.length).toBe(1);
|
||||
expect(main?.messages?.[0]?.role).toBe("assistant");
|
||||
|
||||
@@ -233,7 +233,7 @@ describe("sessions tools", () => {
|
||||
|
||||
const tool = createClawdbotTools({
|
||||
agentSessionKey: requesterKey,
|
||||
agentProvider: "discord",
|
||||
agentChannel: "discord",
|
||||
}).find((candidate) => candidate.name === "sessions_send");
|
||||
expect(tool).toBeDefined();
|
||||
if (!tool) throw new Error("missing sessions_send tool");
|
||||
@@ -275,7 +275,7 @@ describe("sessions tools", () => {
|
||||
for (const call of agentCalls) {
|
||||
expect(call.params).toMatchObject({
|
||||
lane: "nested",
|
||||
provider: "webchat",
|
||||
channel: "webchat",
|
||||
});
|
||||
}
|
||||
expect(
|
||||
@@ -321,7 +321,7 @@ describe("sessions tools", () => {
|
||||
const replyByRunId = new Map<string, string>();
|
||||
const requesterKey = "discord:group:req";
|
||||
const targetKey = "discord:group:target";
|
||||
let sendParams: { to?: string; provider?: string; message?: string } = {};
|
||||
let sendParams: { to?: string; channel?: string; message?: string } = {};
|
||||
callGatewayMock.mockImplementation(async (opts: unknown) => {
|
||||
const request = opts as { method?: string; params?: unknown };
|
||||
calls.push(request);
|
||||
@@ -371,11 +371,11 @@ describe("sessions tools", () => {
|
||||
}
|
||||
if (request.method === "send") {
|
||||
const params = request.params as
|
||||
| { to?: string; provider?: string; message?: string }
|
||||
| { to?: string; channel?: string; message?: string }
|
||||
| undefined;
|
||||
sendParams = {
|
||||
to: params?.to,
|
||||
provider: params?.provider,
|
||||
channel: params?.channel,
|
||||
message: params?.message,
|
||||
};
|
||||
return { messageId: "m-announce" };
|
||||
@@ -385,7 +385,7 @@ describe("sessions tools", () => {
|
||||
|
||||
const tool = createClawdbotTools({
|
||||
agentSessionKey: requesterKey,
|
||||
agentProvider: "discord",
|
||||
agentChannel: "discord",
|
||||
}).find((candidate) => candidate.name === "sessions_send");
|
||||
expect(tool).toBeDefined();
|
||||
if (!tool) throw new Error("missing sessions_send tool");
|
||||
@@ -407,7 +407,7 @@ describe("sessions tools", () => {
|
||||
for (const call of agentCalls) {
|
||||
expect(call.params).toMatchObject({
|
||||
lane: "nested",
|
||||
provider: "webchat",
|
||||
channel: "webchat",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -423,7 +423,7 @@ describe("sessions tools", () => {
|
||||
expect(replySteps).toHaveLength(2);
|
||||
expect(sendParams).toMatchObject({
|
||||
to: "channel:target",
|
||||
provider: "discord",
|
||||
channel: "discord",
|
||||
message: "announce now",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,12 +37,12 @@ describe("subagents", () => {
|
||||
};
|
||||
});
|
||||
|
||||
it("sessions_spawn announces back to the requester group provider", async () => {
|
||||
it("sessions_spawn announces back to the requester group channel", async () => {
|
||||
resetSubagentRegistryForTests();
|
||||
callGatewayMock.mockReset();
|
||||
const calls: Array<{ method?: string; params?: unknown }> = [];
|
||||
let agentCallCount = 0;
|
||||
let sendParams: { to?: string; provider?: string; message?: string } = {};
|
||||
let sendParams: { to?: string; channel?: string; message?: string } = {};
|
||||
let deletedKey: string | undefined;
|
||||
let childRunId: string | undefined;
|
||||
let childSessionKey: string | undefined;
|
||||
@@ -58,7 +58,7 @@ describe("subagents", () => {
|
||||
const params = request.params as {
|
||||
message?: string;
|
||||
sessionKey?: string;
|
||||
provider?: string;
|
||||
channel?: string;
|
||||
timeout?: number;
|
||||
};
|
||||
const message = params?.message ?? "";
|
||||
@@ -69,7 +69,7 @@ describe("subagents", () => {
|
||||
childRunId = runId;
|
||||
childSessionKey = sessionKey;
|
||||
sessionLastAssistantText.set(sessionKey, "result");
|
||||
expect(params?.provider).toBe("discord");
|
||||
expect(params?.channel).toBe("discord");
|
||||
expect(params?.timeout).toBe(1);
|
||||
}
|
||||
return {
|
||||
@@ -96,11 +96,11 @@ describe("subagents", () => {
|
||||
}
|
||||
if (request.method === "send") {
|
||||
const params = request.params as
|
||||
| { to?: string; provider?: string; message?: string }
|
||||
| { to?: string; channel?: string; message?: string }
|
||||
| undefined;
|
||||
sendParams = {
|
||||
to: params?.to,
|
||||
provider: params?.provider,
|
||||
channel: params?.channel,
|
||||
message: params?.message,
|
||||
};
|
||||
return { messageId: "m-announce" };
|
||||
@@ -115,7 +115,7 @@ describe("subagents", () => {
|
||||
|
||||
const tool = createClawdbotTools({
|
||||
agentSessionKey: "discord:group:req",
|
||||
agentProvider: "discord",
|
||||
agentChannel: "discord",
|
||||
}).find((candidate) => candidate.name === "sessions_spawn");
|
||||
if (!tool) throw new Error("missing sessions_spawn tool");
|
||||
|
||||
@@ -153,22 +153,22 @@ describe("subagents", () => {
|
||||
lane?: string;
|
||||
deliver?: boolean;
|
||||
sessionKey?: string;
|
||||
provider?: string;
|
||||
channel?: string;
|
||||
}
|
||||
| undefined;
|
||||
expect(first?.lane).toBe("subagent");
|
||||
expect(first?.deliver).toBe(false);
|
||||
expect(first?.provider).toBe("discord");
|
||||
expect(first?.channel).toBe("discord");
|
||||
expect(first?.sessionKey?.startsWith("agent:main:subagent:")).toBe(true);
|
||||
expect(childSessionKey?.startsWith("agent:main:subagent:")).toBe(true);
|
||||
const second = agentCalls[1]?.params as
|
||||
| { provider?: string; deliver?: boolean; lane?: string }
|
||||
| { channel?: string; deliver?: boolean; lane?: string }
|
||||
| undefined;
|
||||
expect(second?.lane).toBe("nested");
|
||||
expect(second?.deliver).toBe(false);
|
||||
expect(second?.provider).toBe("webchat");
|
||||
expect(second?.channel).toBe("webchat");
|
||||
|
||||
expect(sendParams.provider).toBe("discord");
|
||||
expect(sendParams.channel).toBe("discord");
|
||||
expect(sendParams.to).toBe("channel:req");
|
||||
expect(sendParams.message ?? "").toContain("announce now");
|
||||
expect(sendParams.message ?? "").toContain("Stats:");
|
||||
@@ -180,7 +180,7 @@ describe("subagents", () => {
|
||||
callGatewayMock.mockReset();
|
||||
const calls: Array<{ method?: string; params?: unknown }> = [];
|
||||
let agentCallCount = 0;
|
||||
let sendParams: { to?: string; provider?: string; message?: string } = {};
|
||||
let sendParams: { to?: string; channel?: string; message?: string } = {};
|
||||
let deletedKey: string | undefined;
|
||||
let childRunId: string | undefined;
|
||||
let childSessionKey: string | undefined;
|
||||
@@ -196,7 +196,7 @@ describe("subagents", () => {
|
||||
const params = request.params as {
|
||||
message?: string;
|
||||
sessionKey?: string;
|
||||
provider?: string;
|
||||
channel?: string;
|
||||
timeout?: number;
|
||||
};
|
||||
const message = params?.message ?? "";
|
||||
@@ -207,7 +207,7 @@ describe("subagents", () => {
|
||||
childRunId = runId;
|
||||
childSessionKey = sessionKey;
|
||||
sessionLastAssistantText.set(sessionKey, "result");
|
||||
expect(params?.provider).toBe("discord");
|
||||
expect(params?.channel).toBe("discord");
|
||||
expect(params?.timeout).toBe(1);
|
||||
}
|
||||
return {
|
||||
@@ -238,11 +238,11 @@ describe("subagents", () => {
|
||||
}
|
||||
if (request.method === "send") {
|
||||
const params = request.params as
|
||||
| { to?: string; provider?: string; message?: string }
|
||||
| { to?: string; channel?: string; message?: string }
|
||||
| undefined;
|
||||
sendParams = {
|
||||
to: params?.to,
|
||||
provider: params?.provider,
|
||||
channel: params?.channel,
|
||||
message: params?.message,
|
||||
};
|
||||
return { messageId: "m-announce" };
|
||||
@@ -257,7 +257,7 @@ describe("subagents", () => {
|
||||
|
||||
const tool = createClawdbotTools({
|
||||
agentSessionKey: "discord:group:req",
|
||||
agentProvider: "discord",
|
||||
agentChannel: "discord",
|
||||
}).find((candidate) => candidate.name === "sessions_spawn");
|
||||
if (!tool) throw new Error("missing sessions_spawn tool");
|
||||
|
||||
@@ -282,13 +282,13 @@ describe("subagents", () => {
|
||||
const agentCalls = calls.filter((call) => call.method === "agent");
|
||||
expect(agentCalls).toHaveLength(2);
|
||||
const second = agentCalls[1]?.params as
|
||||
| { provider?: string; deliver?: boolean; lane?: string }
|
||||
| { channel?: string; deliver?: boolean; lane?: string }
|
||||
| undefined;
|
||||
expect(second?.lane).toBe("nested");
|
||||
expect(second?.deliver).toBe(false);
|
||||
expect(second?.provider).toBe("webchat");
|
||||
expect(second?.channel).toBe("webchat");
|
||||
|
||||
expect(sendParams.provider).toBe("discord");
|
||||
expect(sendParams.channel).toBe("discord");
|
||||
expect(sendParams.to).toBe("channel:req");
|
||||
expect(sendParams.message ?? "").toContain("announce now");
|
||||
expect(sendParams.message ?? "").toContain("Stats:");
|
||||
@@ -300,7 +300,7 @@ describe("subagents", () => {
|
||||
callGatewayMock.mockReset();
|
||||
const calls: Array<{ method?: string; params?: unknown }> = [];
|
||||
let agentCallCount = 0;
|
||||
let sendParams: { to?: string; provider?: string; message?: string } = {};
|
||||
let sendParams: { to?: string; channel?: string; message?: string } = {};
|
||||
let childRunId: string | undefined;
|
||||
let childSessionKey: string | undefined;
|
||||
const waitCalls: Array<{ runId?: string; timeoutMs?: number }> = [];
|
||||
@@ -314,7 +314,7 @@ describe("subagents", () => {
|
||||
sessions: [
|
||||
{
|
||||
key: "main",
|
||||
lastProvider: "whatsapp",
|
||||
lastChannel: "whatsapp",
|
||||
lastTo: "+123",
|
||||
},
|
||||
],
|
||||
@@ -360,11 +360,11 @@ describe("subagents", () => {
|
||||
}
|
||||
if (request.method === "send") {
|
||||
const params = request.params as
|
||||
| { to?: string; provider?: string; message?: string }
|
||||
| { to?: string; channel?: string; message?: string }
|
||||
| undefined;
|
||||
sendParams = {
|
||||
to: params?.to,
|
||||
provider: params?.provider,
|
||||
channel: params?.channel,
|
||||
message: params?.message,
|
||||
};
|
||||
return { messageId: "m1" };
|
||||
@@ -377,7 +377,7 @@ describe("subagents", () => {
|
||||
|
||||
const tool = createClawdbotTools({
|
||||
agentSessionKey: "main",
|
||||
agentProvider: "whatsapp",
|
||||
agentChannel: "whatsapp",
|
||||
}).find((candidate) => candidate.name === "sessions_spawn");
|
||||
if (!tool) throw new Error("missing sessions_spawn tool");
|
||||
|
||||
@@ -407,7 +407,7 @@ describe("subagents", () => {
|
||||
|
||||
const childWait = waitCalls.find((call) => call.runId === childRunId);
|
||||
expect(childWait?.timeoutMs).toBe(1000);
|
||||
expect(sendParams.provider).toBe("whatsapp");
|
||||
expect(sendParams.channel).toBe("whatsapp");
|
||||
expect(sendParams.to).toBe("+123");
|
||||
expect(sendParams.message ?? "").toContain("hello from sub");
|
||||
expect(sendParams.message ?? "").toContain("Stats:");
|
||||
@@ -420,7 +420,7 @@ describe("subagents", () => {
|
||||
|
||||
const tool = createClawdbotTools({
|
||||
agentSessionKey: "main",
|
||||
agentProvider: "whatsapp",
|
||||
agentChannel: "whatsapp",
|
||||
}).find((candidate) => candidate.name === "sessions_spawn");
|
||||
if (!tool) throw new Error("missing sessions_spawn tool");
|
||||
|
||||
@@ -470,7 +470,7 @@ describe("subagents", () => {
|
||||
|
||||
const tool = createClawdbotTools({
|
||||
agentSessionKey: "main",
|
||||
agentProvider: "whatsapp",
|
||||
agentChannel: "whatsapp",
|
||||
}).find((candidate) => candidate.name === "sessions_spawn");
|
||||
if (!tool) throw new Error("missing sessions_spawn tool");
|
||||
|
||||
@@ -522,7 +522,7 @@ describe("subagents", () => {
|
||||
|
||||
const tool = createClawdbotTools({
|
||||
agentSessionKey: "main",
|
||||
agentProvider: "whatsapp",
|
||||
agentChannel: "whatsapp",
|
||||
}).find((candidate) => candidate.name === "sessions_spawn");
|
||||
if (!tool) throw new Error("missing sessions_spawn tool");
|
||||
|
||||
@@ -574,7 +574,7 @@ describe("subagents", () => {
|
||||
|
||||
const tool = createClawdbotTools({
|
||||
agentSessionKey: "main",
|
||||
agentProvider: "whatsapp",
|
||||
agentChannel: "whatsapp",
|
||||
}).find((candidate) => candidate.name === "sessions_spawn");
|
||||
if (!tool) throw new Error("missing sessions_spawn tool");
|
||||
|
||||
@@ -612,7 +612,7 @@ describe("subagents", () => {
|
||||
|
||||
const tool = createClawdbotTools({
|
||||
agentSessionKey: "main",
|
||||
agentProvider: "whatsapp",
|
||||
agentChannel: "whatsapp",
|
||||
}).find((candidate) => candidate.name === "sessions_spawn");
|
||||
if (!tool) throw new Error("missing sessions_spawn tool");
|
||||
|
||||
@@ -710,7 +710,7 @@ describe("subagents", () => {
|
||||
|
||||
const tool = createClawdbotTools({
|
||||
agentSessionKey: "agent:main:main",
|
||||
agentProvider: "discord",
|
||||
agentChannel: "discord",
|
||||
}).find((candidate) => candidate.name === "sessions_spawn");
|
||||
if (!tool) throw new Error("missing sessions_spawn tool");
|
||||
|
||||
@@ -754,7 +754,7 @@ describe("subagents", () => {
|
||||
|
||||
const tool = createClawdbotTools({
|
||||
agentSessionKey: "agent:research:main",
|
||||
agentProvider: "discord",
|
||||
agentChannel: "discord",
|
||||
}).find((candidate) => candidate.name === "sessions_spawn");
|
||||
if (!tool) throw new Error("missing sessions_spawn tool");
|
||||
|
||||
@@ -804,7 +804,7 @@ describe("subagents", () => {
|
||||
|
||||
const tool = createClawdbotTools({
|
||||
agentSessionKey: "main",
|
||||
agentProvider: "whatsapp",
|
||||
agentChannel: "whatsapp",
|
||||
}).find((candidate) => candidate.name === "sessions_spawn");
|
||||
if (!tool) throw new Error("missing sessions_spawn tool");
|
||||
|
||||
@@ -840,7 +840,7 @@ describe("subagents", () => {
|
||||
|
||||
const tool = createClawdbotTools({
|
||||
agentSessionKey: "main",
|
||||
agentProvider: "whatsapp",
|
||||
agentChannel: "whatsapp",
|
||||
}).find((candidate) => candidate.name === "sessions_spawn");
|
||||
if (!tool) throw new Error("missing sessions_spawn tool");
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import { resolvePluginTools } from "../plugins/tools.js";
|
||||
import type { GatewayMessageProvider } from "../utils/message-provider.js";
|
||||
import type { GatewayMessageChannel } from "../utils/message-channel.js";
|
||||
import { resolveSessionAgentId } from "./agent-scope.js";
|
||||
import { createAgentsListTool } from "./tools/agents-list-tool.js";
|
||||
import { createBrowserTool } from "./tools/browser-tool.js";
|
||||
@@ -28,7 +28,7 @@ export function createClawdbotTools(options?: {
|
||||
allowedControlHosts?: string[];
|
||||
allowedControlPorts?: number[];
|
||||
agentSessionKey?: string;
|
||||
agentProvider?: GatewayMessageProvider;
|
||||
agentChannel?: GatewayMessageChannel;
|
||||
agentAccountId?: string;
|
||||
agentDir?: string;
|
||||
sandboxRoot?: string;
|
||||
@@ -93,12 +93,12 @@ export function createClawdbotTools(options?: {
|
||||
}),
|
||||
createSessionsSendTool({
|
||||
agentSessionKey: options?.agentSessionKey,
|
||||
agentProvider: options?.agentProvider,
|
||||
agentChannel: options?.agentChannel,
|
||||
sandboxed: options?.sandboxed,
|
||||
}),
|
||||
createSessionsSpawnTool({
|
||||
agentSessionKey: options?.agentSessionKey,
|
||||
agentProvider: options?.agentProvider,
|
||||
agentChannel: options?.agentChannel,
|
||||
sandboxed: options?.sandboxed,
|
||||
}),
|
||||
createSessionStatusTool({
|
||||
@@ -121,7 +121,7 @@ export function createClawdbotTools(options?: {
|
||||
config: options?.config,
|
||||
}),
|
||||
sessionKey: options?.agentSessionKey,
|
||||
messageProvider: options?.agentProvider,
|
||||
messageChannel: options?.agentChannel,
|
||||
agentAccountId: options?.agentAccountId,
|
||||
sandboxed: options?.sandboxed,
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
getProviderPlugin,
|
||||
normalizeProviderId,
|
||||
} from "../providers/plugins/index.js";
|
||||
getChannelPlugin,
|
||||
normalizeChannelId,
|
||||
} from "../channels/plugins/index.js";
|
||||
|
||||
export type MessagingToolSend = {
|
||||
tool: string;
|
||||
@@ -15,8 +15,8 @@ const CORE_MESSAGING_TOOLS = new Set(["sessions_send", "message"]);
|
||||
// Provider docking: any plugin with `actions` opts into messaging tool handling.
|
||||
export function isMessagingTool(toolName: string): boolean {
|
||||
if (CORE_MESSAGING_TOOLS.has(toolName)) return true;
|
||||
const providerId = normalizeProviderId(toolName);
|
||||
return Boolean(providerId && getProviderPlugin(providerId)?.actions);
|
||||
const providerId = normalizeChannelId(toolName);
|
||||
return Boolean(providerId && getChannelPlugin(providerId)?.actions);
|
||||
}
|
||||
|
||||
export function isMessagingToolSendAction(
|
||||
@@ -28,9 +28,9 @@ export function isMessagingToolSendAction(
|
||||
if (toolName === "message") {
|
||||
return action === "send" || action === "thread-reply";
|
||||
}
|
||||
const providerId = normalizeProviderId(toolName);
|
||||
const providerId = normalizeChannelId(toolName);
|
||||
if (!providerId) return false;
|
||||
const plugin = getProviderPlugin(providerId);
|
||||
const plugin = getChannelPlugin(providerId);
|
||||
if (!plugin?.actions?.extractToolSend) return false;
|
||||
return Boolean(plugin.actions.extractToolSend({ args })?.to);
|
||||
}
|
||||
@@ -40,8 +40,8 @@ export function normalizeTargetForProvider(
|
||||
raw?: string,
|
||||
): string | undefined {
|
||||
if (!raw) return undefined;
|
||||
const providerId = normalizeProviderId(provider);
|
||||
const plugin = providerId ? getProviderPlugin(providerId) : undefined;
|
||||
const providerId = normalizeChannelId(provider);
|
||||
const plugin = providerId ? getChannelPlugin(providerId) : undefined;
|
||||
const normalized =
|
||||
plugin?.messaging?.normalizeTarget?.(raw) ??
|
||||
(raw.trim().toLowerCase() || undefined);
|
||||
|
||||
@@ -478,17 +478,23 @@ describe("getDmHistoryLimitFromSessionKey", () => {
|
||||
});
|
||||
|
||||
it("returns dmHistoryLimit for telegram provider", () => {
|
||||
const config = { telegram: { dmHistoryLimit: 15 } } as ClawdbotConfig;
|
||||
const config = {
|
||||
channels: { telegram: { dmHistoryLimit: 15 } },
|
||||
} as ClawdbotConfig;
|
||||
expect(getDmHistoryLimitFromSessionKey("telegram:dm:123", config)).toBe(15);
|
||||
});
|
||||
|
||||
it("returns dmHistoryLimit for whatsapp provider", () => {
|
||||
const config = { whatsapp: { dmHistoryLimit: 20 } } as ClawdbotConfig;
|
||||
const config = {
|
||||
channels: { whatsapp: { dmHistoryLimit: 20 } },
|
||||
} as ClawdbotConfig;
|
||||
expect(getDmHistoryLimitFromSessionKey("whatsapp:dm:123", config)).toBe(20);
|
||||
});
|
||||
|
||||
it("returns dmHistoryLimit for agent-prefixed session keys", () => {
|
||||
const config = { telegram: { dmHistoryLimit: 10 } } as ClawdbotConfig;
|
||||
const config = {
|
||||
channels: { telegram: { dmHistoryLimit: 10 } },
|
||||
} as ClawdbotConfig;
|
||||
expect(
|
||||
getDmHistoryLimitFromSessionKey("agent:main:telegram:dm:123", config),
|
||||
).toBe(10);
|
||||
@@ -496,8 +502,10 @@ describe("getDmHistoryLimitFromSessionKey", () => {
|
||||
|
||||
it("returns undefined for non-dm session kinds", () => {
|
||||
const config = {
|
||||
slack: { dmHistoryLimit: 10 },
|
||||
telegram: { dmHistoryLimit: 15 },
|
||||
channels: {
|
||||
telegram: { dmHistoryLimit: 15 },
|
||||
slack: { dmHistoryLimit: 10 },
|
||||
},
|
||||
} as ClawdbotConfig;
|
||||
expect(
|
||||
getDmHistoryLimitFromSessionKey("agent:beta:slack:channel:C1", config),
|
||||
@@ -508,14 +516,16 @@ describe("getDmHistoryLimitFromSessionKey", () => {
|
||||
});
|
||||
|
||||
it("returns undefined for unknown provider", () => {
|
||||
const config = { telegram: { dmHistoryLimit: 15 } } as ClawdbotConfig;
|
||||
const config = {
|
||||
channels: { telegram: { dmHistoryLimit: 15 } },
|
||||
} as ClawdbotConfig;
|
||||
expect(
|
||||
getDmHistoryLimitFromSessionKey("unknown:dm:123", config),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined when provider config has no dmHistoryLimit", () => {
|
||||
const config = { telegram: {} } as ClawdbotConfig;
|
||||
const config = { channels: { telegram: {} } } as ClawdbotConfig;
|
||||
expect(
|
||||
getDmHistoryLimitFromSessionKey("telegram:dm:123", config),
|
||||
).toBeUndefined();
|
||||
@@ -533,7 +543,9 @@ describe("getDmHistoryLimitFromSessionKey", () => {
|
||||
] as const;
|
||||
|
||||
for (const provider of providers) {
|
||||
const config = { [provider]: { dmHistoryLimit: 5 } } as ClawdbotConfig;
|
||||
const config = {
|
||||
channels: { [provider]: { dmHistoryLimit: 5 } },
|
||||
} as ClawdbotConfig;
|
||||
expect(
|
||||
getDmHistoryLimitFromSessionKey(`${provider}:dm:123`, config),
|
||||
).toBe(5);
|
||||
@@ -554,9 +566,11 @@ describe("getDmHistoryLimitFromSessionKey", () => {
|
||||
for (const provider of providers) {
|
||||
// Test per-DM override takes precedence
|
||||
const configWithOverride = {
|
||||
[provider]: {
|
||||
dmHistoryLimit: 20,
|
||||
dms: { user123: { historyLimit: 7 } },
|
||||
channels: {
|
||||
[provider]: {
|
||||
dmHistoryLimit: 20,
|
||||
dms: { user123: { historyLimit: 7 } },
|
||||
},
|
||||
},
|
||||
} as ClawdbotConfig;
|
||||
expect(
|
||||
@@ -586,9 +600,11 @@ describe("getDmHistoryLimitFromSessionKey", () => {
|
||||
|
||||
it("returns per-DM override when set", () => {
|
||||
const config = {
|
||||
telegram: {
|
||||
dmHistoryLimit: 15,
|
||||
dms: { "123": { historyLimit: 5 } },
|
||||
channels: {
|
||||
telegram: {
|
||||
dmHistoryLimit: 15,
|
||||
dms: { "123": { historyLimit: 5 } },
|
||||
},
|
||||
},
|
||||
} as ClawdbotConfig;
|
||||
expect(getDmHistoryLimitFromSessionKey("telegram:dm:123", config)).toBe(5);
|
||||
@@ -596,9 +612,11 @@ describe("getDmHistoryLimitFromSessionKey", () => {
|
||||
|
||||
it("falls back to provider default when per-DM not set", () => {
|
||||
const config = {
|
||||
telegram: {
|
||||
dmHistoryLimit: 15,
|
||||
dms: { "456": { historyLimit: 5 } },
|
||||
channels: {
|
||||
telegram: {
|
||||
dmHistoryLimit: 15,
|
||||
dms: { "456": { historyLimit: 5 } },
|
||||
},
|
||||
},
|
||||
} as ClawdbotConfig;
|
||||
expect(getDmHistoryLimitFromSessionKey("telegram:dm:123", config)).toBe(15);
|
||||
@@ -606,9 +624,11 @@ describe("getDmHistoryLimitFromSessionKey", () => {
|
||||
|
||||
it("returns per-DM override for agent-prefixed keys", () => {
|
||||
const config = {
|
||||
telegram: {
|
||||
dmHistoryLimit: 20,
|
||||
dms: { "789": { historyLimit: 3 } },
|
||||
channels: {
|
||||
telegram: {
|
||||
dmHistoryLimit: 20,
|
||||
dms: { "789": { historyLimit: 3 } },
|
||||
},
|
||||
},
|
||||
} as ClawdbotConfig;
|
||||
expect(
|
||||
@@ -618,9 +638,11 @@ describe("getDmHistoryLimitFromSessionKey", () => {
|
||||
|
||||
it("handles userId with colons (e.g., email)", () => {
|
||||
const config = {
|
||||
msteams: {
|
||||
dmHistoryLimit: 10,
|
||||
dms: { "user@example.com": { historyLimit: 7 } },
|
||||
channels: {
|
||||
msteams: {
|
||||
dmHistoryLimit: 10,
|
||||
dms: { "user@example.com": { historyLimit: 7 } },
|
||||
},
|
||||
},
|
||||
} as ClawdbotConfig;
|
||||
expect(
|
||||
@@ -630,8 +652,10 @@ describe("getDmHistoryLimitFromSessionKey", () => {
|
||||
|
||||
it("returns undefined when per-DM historyLimit is not set", () => {
|
||||
const config = {
|
||||
telegram: {
|
||||
dms: { "123": {} },
|
||||
channels: {
|
||||
telegram: {
|
||||
dms: { "123": {} },
|
||||
},
|
||||
},
|
||||
} as ClawdbotConfig;
|
||||
expect(
|
||||
@@ -641,9 +665,11 @@ describe("getDmHistoryLimitFromSessionKey", () => {
|
||||
|
||||
it("returns 0 when per-DM historyLimit is explicitly 0 (unlimited)", () => {
|
||||
const config = {
|
||||
telegram: {
|
||||
dmHistoryLimit: 15,
|
||||
dms: { "123": { historyLimit: 0 } },
|
||||
channels: {
|
||||
telegram: {
|
||||
dmHistoryLimit: 15,
|
||||
dms: { "123": { historyLimit: 0 } },
|
||||
},
|
||||
},
|
||||
} as ClawdbotConfig;
|
||||
expect(getDmHistoryLimitFromSessionKey("telegram:dm:123", config)).toBe(0);
|
||||
|
||||
@@ -33,8 +33,8 @@ import type {
|
||||
} from "../auto-reply/thinking.js";
|
||||
import { formatToolAggregate } from "../auto-reply/tool-meta.js";
|
||||
import { isCacheEnabled, resolveCacheTtlMs } from "../config/cache-utils.js";
|
||||
import { resolveChannelCapabilities } from "../config/channel-capabilities.js";
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import { resolveProviderCapabilities } from "../config/provider-capabilities.js";
|
||||
import { getMachineDisplayName } from "../infra/machine-name.js";
|
||||
import { registerUnhandledRejectionHandler } from "../infra/unhandled-rejections.js";
|
||||
import { createSubsystemLogger } from "../logging.js";
|
||||
@@ -42,7 +42,7 @@ import {
|
||||
type enqueueCommand,
|
||||
enqueueCommandInLane,
|
||||
} from "../process/command-queue.js";
|
||||
import { normalizeMessageProvider } from "../utils/message-provider.js";
|
||||
import { normalizeMessageChannel } from "../utils/message-channel.js";
|
||||
import { isReasoningTagProvider } from "../utils/provider-utils.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
import { resolveClawdbotAgentDir } from "./agent-paths.js";
|
||||
@@ -690,19 +690,19 @@ export function getDmHistoryLimitFromSessionKey(
|
||||
// Map provider to config key
|
||||
switch (provider) {
|
||||
case "telegram":
|
||||
return getLimit(config.telegram);
|
||||
return getLimit(config.channels?.telegram);
|
||||
case "whatsapp":
|
||||
return getLimit(config.whatsapp);
|
||||
return getLimit(config.channels?.whatsapp);
|
||||
case "discord":
|
||||
return getLimit(config.discord);
|
||||
return getLimit(config.channels?.discord);
|
||||
case "slack":
|
||||
return getLimit(config.slack);
|
||||
return getLimit(config.channels?.slack);
|
||||
case "signal":
|
||||
return getLimit(config.signal);
|
||||
return getLimit(config.channels?.signal);
|
||||
case "imessage":
|
||||
return getLimit(config.imessage);
|
||||
return getLimit(config.channels?.imessage);
|
||||
case "msteams":
|
||||
return getLimit(config.msteams);
|
||||
return getLimit(config.channels?.msteams);
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
@@ -1125,6 +1125,7 @@ function resolveModel(
|
||||
export async function compactEmbeddedPiSession(params: {
|
||||
sessionId: string;
|
||||
sessionKey?: string;
|
||||
messageChannel?: string;
|
||||
messageProvider?: string;
|
||||
agentAccountId?: string;
|
||||
sessionFile: string;
|
||||
@@ -1258,7 +1259,7 @@ export async function compactEmbeddedPiSession(params: {
|
||||
elevated: params.bashElevated,
|
||||
},
|
||||
sandbox,
|
||||
messageProvider: params.messageProvider,
|
||||
messageProvider: params.messageChannel ?? params.messageProvider,
|
||||
agentAccountId: params.agentAccountId,
|
||||
sessionKey: params.sessionKey ?? params.sessionId,
|
||||
agentDir,
|
||||
@@ -1272,13 +1273,13 @@ export async function compactEmbeddedPiSession(params: {
|
||||
});
|
||||
logToolSchemasForGoogle({ tools, provider });
|
||||
const machineName = await getMachineDisplayName();
|
||||
const runtimeProvider = normalizeMessageProvider(
|
||||
params.messageProvider,
|
||||
const runtimeChannel = normalizeMessageChannel(
|
||||
params.messageChannel ?? params.messageProvider,
|
||||
);
|
||||
const runtimeCapabilities = runtimeProvider
|
||||
? (resolveProviderCapabilities({
|
||||
const runtimeCapabilities = runtimeChannel
|
||||
? (resolveChannelCapabilities({
|
||||
cfg: params.config,
|
||||
provider: runtimeProvider,
|
||||
channel: runtimeChannel,
|
||||
accountId: params.agentAccountId,
|
||||
}) ?? [])
|
||||
: undefined;
|
||||
@@ -1288,7 +1289,7 @@ export async function compactEmbeddedPiSession(params: {
|
||||
arch: os.arch(),
|
||||
node: process.version,
|
||||
model: `${provider}/${modelId}`,
|
||||
provider: runtimeProvider,
|
||||
channel: runtimeChannel,
|
||||
capabilities: runtimeCapabilities,
|
||||
};
|
||||
const sandboxInfo = buildEmbeddedSandboxInfo(
|
||||
@@ -1443,6 +1444,7 @@ export async function compactEmbeddedPiSession(params: {
|
||||
export async function runEmbeddedPiAgent(params: {
|
||||
sessionId: string;
|
||||
sessionKey?: string;
|
||||
messageChannel?: string;
|
||||
messageProvider?: string;
|
||||
agentAccountId?: string;
|
||||
/** Current channel ID for auto-threading (Slack). */
|
||||
@@ -1639,7 +1641,7 @@ export async function runEmbeddedPiAgent(params: {
|
||||
attemptedThinking.add(thinkLevel);
|
||||
|
||||
log.debug(
|
||||
`embedded run start: runId=${params.runId} sessionId=${params.sessionId} provider=${provider} model=${modelId} thinking=${thinkLevel} messageProvider=${params.messageProvider ?? "unknown"}`,
|
||||
`embedded run start: runId=${params.runId} sessionId=${params.sessionId} provider=${provider} model=${modelId} thinking=${thinkLevel} messageChannel=${params.messageChannel ?? params.messageProvider ?? "unknown"}`,
|
||||
);
|
||||
|
||||
await fs.mkdir(resolvedWorkspace, { recursive: true });
|
||||
@@ -1698,7 +1700,7 @@ export async function runEmbeddedPiAgent(params: {
|
||||
elevated: params.bashElevated,
|
||||
},
|
||||
sandbox,
|
||||
messageProvider: params.messageProvider,
|
||||
messageProvider: params.messageChannel ?? params.messageProvider,
|
||||
agentAccountId: params.agentAccountId,
|
||||
sessionKey: params.sessionKey ?? params.sessionId,
|
||||
agentDir,
|
||||
|
||||
@@ -6,13 +6,13 @@ import type { AgentSession } from "@mariozechner/pi-coding-agent";
|
||||
import { parseReplyDirectives } from "../auto-reply/reply/reply-directives.js";
|
||||
import type { ReasoningLevel } from "../auto-reply/thinking.js";
|
||||
import { formatToolAggregate } from "../auto-reply/tool-meta.js";
|
||||
import {
|
||||
getChannelPlugin,
|
||||
normalizeChannelId,
|
||||
} from "../channels/plugins/index.js";
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
import { emitAgentEvent } from "../infra/agent-events.js";
|
||||
import { createSubsystemLogger } from "../logging.js";
|
||||
import {
|
||||
getProviderPlugin,
|
||||
normalizeProviderId,
|
||||
} from "../providers/plugins/index.js";
|
||||
import { truncateUtf16Safe } from "../utils.js";
|
||||
import type { BlockReplyChunking } from "./pi-embedded-block-chunker.js";
|
||||
import { EmbeddedBlockChunker } from "./pi-embedded-block-chunker.js";
|
||||
@@ -124,15 +124,15 @@ function extractMessagingToolSend(
|
||||
if (!toRaw) return undefined;
|
||||
const providerRaw =
|
||||
typeof args.provider === "string" ? args.provider.trim() : "";
|
||||
const providerId = providerRaw ? normalizeProviderId(providerRaw) : null;
|
||||
const providerId = providerRaw ? normalizeChannelId(providerRaw) : null;
|
||||
const provider =
|
||||
providerId ?? (providerRaw ? providerRaw.toLowerCase() : "message");
|
||||
const to = normalizeTargetForProvider(provider, toRaw);
|
||||
return to ? { tool: toolName, provider, accountId, to } : undefined;
|
||||
}
|
||||
const providerId = normalizeProviderId(toolName);
|
||||
const providerId = normalizeChannelId(toolName);
|
||||
if (!providerId) return undefined;
|
||||
const plugin = getProviderPlugin(providerId);
|
||||
const plugin = getChannelPlugin(providerId);
|
||||
const extracted = plugin?.actions?.extractToolSend?.({ args });
|
||||
if (!extracted?.to) return undefined;
|
||||
const to = normalizeTargetForProvider(providerId, extracted.to);
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import { detectMime } from "../media/mime.js";
|
||||
import { isSubagentSessionKey } from "../routing/session-key.js";
|
||||
import { resolveGatewayMessageProvider } from "../utils/message-provider.js";
|
||||
import { resolveGatewayMessageChannel } from "../utils/message-channel.js";
|
||||
import {
|
||||
resolveAgentConfig,
|
||||
resolveAgentIdFromSessionKey,
|
||||
@@ -21,9 +21,9 @@ import {
|
||||
type ExecToolDefaults,
|
||||
type ProcessToolDefaults,
|
||||
} from "./bash-tools.js";
|
||||
import { listChannelAgentTools } from "./channel-tools.js";
|
||||
import { createClawdbotTools } from "./clawdbot-tools.js";
|
||||
import type { ModelAuthMode } from "./model-auth.js";
|
||||
import { listProviderAgentTools } from "./provider-tools.js";
|
||||
import type { SandboxContext, SandboxToolPolicy } from "./sandbox.js";
|
||||
import { assertSandboxPath } from "./sandbox-paths.js";
|
||||
import { cleanSchemaForGemini } from "./schema/clean-for-gemini.js";
|
||||
@@ -807,8 +807,8 @@ export function createClawdbotCodingTools(options?: {
|
||||
execTool as unknown as AnyAgentTool,
|
||||
bashTool,
|
||||
processTool as unknown as AnyAgentTool,
|
||||
// Provider docking: include provider-defined agent tools (login, etc.).
|
||||
...listProviderAgentTools({ cfg: options?.config }),
|
||||
// Channel docking: include channel-defined agent tools (login, etc.).
|
||||
...listChannelAgentTools({ cfg: options?.config }),
|
||||
...createClawdbotTools({
|
||||
browserControlUrl: sandbox?.browser?.controlUrl,
|
||||
allowHostBrowserControl: sandbox ? sandbox.browserAllowHostControl : true,
|
||||
@@ -816,7 +816,7 @@ export function createClawdbotCodingTools(options?: {
|
||||
allowedControlHosts: sandbox?.browserAllowedControlHosts,
|
||||
allowedControlPorts: sandbox?.browserAllowedControlPorts,
|
||||
agentSessionKey: options?.sessionKey,
|
||||
agentProvider: resolveGatewayMessageProvider(options?.messageProvider),
|
||||
agentChannel: resolveGatewayMessageChannel(options?.messageProvider),
|
||||
agentAccountId: options?.agentAccountId,
|
||||
agentDir: options?.agentDir,
|
||||
sandboxRoot,
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import { listProviderPlugins } from "../providers/plugins/index.js";
|
||||
import type { ProviderAgentTool } from "../providers/plugins/types.js";
|
||||
|
||||
export function listProviderAgentTools(params: {
|
||||
cfg?: ClawdbotConfig;
|
||||
}): ProviderAgentTool[] {
|
||||
// Provider docking: aggregate provider-owned tools (login, etc.).
|
||||
const tools: ProviderAgentTool[] = [];
|
||||
for (const plugin of listProviderPlugins()) {
|
||||
const entry = plugin.agentTools;
|
||||
if (!entry) continue;
|
||||
const resolved = typeof entry === "function" ? entry(params) : entry;
|
||||
if (Array.isArray(resolved)) tools.push(...resolved);
|
||||
}
|
||||
return tools;
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
resolveProfile,
|
||||
} from "../browser/config.js";
|
||||
import { DEFAULT_CLAWD_BROWSER_COLOR } from "../browser/constants.js";
|
||||
import { CHANNEL_IDS } from "../channels/registry.js";
|
||||
import {
|
||||
type ClawdbotConfig,
|
||||
loadConfig,
|
||||
@@ -23,7 +24,6 @@ import {
|
||||
canonicalizeMainSessionAlias,
|
||||
resolveAgentMainSessionKey,
|
||||
} from "../config/sessions.js";
|
||||
import { PROVIDER_IDS } from "../providers/registry.js";
|
||||
import { normalizeAgentId } from "../routing/session-key.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
@@ -188,7 +188,7 @@ const DEFAULT_TOOL_DENY = [
|
||||
"nodes",
|
||||
"cron",
|
||||
"gateway",
|
||||
...PROVIDER_IDS,
|
||||
...CHANNEL_IDS,
|
||||
];
|
||||
export const DEFAULT_SANDBOX_BROWSER_IMAGE =
|
||||
"clawdbot-sandbox-browser:bookworm-slim";
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
resolveStorePath,
|
||||
} from "../config/sessions.js";
|
||||
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 { readLatestAssistantReply, runAgentStep } from "./tools/agent-step.js";
|
||||
import { resolveAnnounceTarget } from "./tools/sessions-announce-target.js";
|
||||
@@ -139,7 +139,7 @@ async function buildSubagentStatsLine(params: {
|
||||
|
||||
export function buildSubagentSystemPrompt(params: {
|
||||
requesterSessionKey?: string;
|
||||
requesterProvider?: string;
|
||||
requesterChannel?: string;
|
||||
childSessionKey: string;
|
||||
label?: string;
|
||||
task?: string;
|
||||
@@ -182,8 +182,8 @@ export function buildSubagentSystemPrompt(params: {
|
||||
params.requesterSessionKey
|
||||
? `- Requester session: ${params.requesterSessionKey}.`
|
||||
: undefined,
|
||||
params.requesterProvider
|
||||
? `- Requester provider: ${params.requesterProvider}.`
|
||||
params.requesterChannel
|
||||
? `- Requester channel: ${params.requesterChannel}.`
|
||||
: undefined,
|
||||
`- Your session: ${params.childSessionKey}.`,
|
||||
"",
|
||||
@@ -195,7 +195,7 @@ export function buildSubagentSystemPrompt(params: {
|
||||
|
||||
function buildSubagentAnnouncePrompt(params: {
|
||||
requesterSessionKey?: string;
|
||||
requesterProvider?: string;
|
||||
requesterChannel?: string;
|
||||
announceChannel: string;
|
||||
task: string;
|
||||
subagentReply?: string;
|
||||
@@ -205,10 +205,10 @@ function buildSubagentAnnouncePrompt(params: {
|
||||
params.requesterSessionKey
|
||||
? `Requester session: ${params.requesterSessionKey}.`
|
||||
: undefined,
|
||||
params.requesterProvider
|
||||
? `Requester provider: ${params.requesterProvider}.`
|
||||
params.requesterChannel
|
||||
? `Requester channel: ${params.requesterChannel}.`
|
||||
: undefined,
|
||||
`Post target provider: ${params.announceChannel}.`,
|
||||
`Post target channel: ${params.announceChannel}.`,
|
||||
`Original task: ${params.task}`,
|
||||
params.subagentReply
|
||||
? `Sub-agent result: ${params.subagentReply}`
|
||||
@@ -226,7 +226,7 @@ export async function runSubagentAnnounceFlow(params: {
|
||||
childSessionKey: string;
|
||||
childRunId: string;
|
||||
requesterSessionKey: string;
|
||||
requesterProvider?: string;
|
||||
requesterChannel?: string;
|
||||
requesterDisplayKey: string;
|
||||
task: string;
|
||||
timeoutMs: number;
|
||||
@@ -269,8 +269,8 @@ export async function runSubagentAnnounceFlow(params: {
|
||||
|
||||
const announcePrompt = buildSubagentAnnouncePrompt({
|
||||
requesterSessionKey: params.requesterSessionKey,
|
||||
requesterProvider: params.requesterProvider,
|
||||
announceChannel: announceTarget.provider,
|
||||
requesterChannel: params.requesterChannel,
|
||||
announceChannel: announceTarget.channel,
|
||||
task: params.task,
|
||||
subagentReply: reply,
|
||||
});
|
||||
@@ -280,7 +280,7 @@ export async function runSubagentAnnounceFlow(params: {
|
||||
message: "Sub-agent announce step.",
|
||||
extraSystemPrompt: announcePrompt,
|
||||
timeoutMs: params.timeoutMs,
|
||||
provider: INTERNAL_MESSAGE_PROVIDER,
|
||||
channel: INTERNAL_MESSAGE_CHANNEL,
|
||||
lane: AGENT_LANE_NESTED,
|
||||
});
|
||||
|
||||
@@ -305,7 +305,7 @@ export async function runSubagentAnnounceFlow(params: {
|
||||
params: {
|
||||
to: announceTarget.to,
|
||||
message,
|
||||
provider: announceTarget.provider,
|
||||
channel: announceTarget.channel,
|
||||
accountId: announceTarget.accountId,
|
||||
idempotencyKey: crypto.randomUUID(),
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@ export type SubagentRunRecord = {
|
||||
runId: string;
|
||||
childSessionKey: string;
|
||||
requesterSessionKey: string;
|
||||
requesterProvider?: string;
|
||||
requesterChannel?: string;
|
||||
requesterDisplayKey: string;
|
||||
task: string;
|
||||
cleanup: "delete" | "keep";
|
||||
@@ -105,7 +105,7 @@ function ensureListener() {
|
||||
childSessionKey: entry.childSessionKey,
|
||||
childRunId: entry.runId,
|
||||
requesterSessionKey: entry.requesterSessionKey,
|
||||
requesterProvider: entry.requesterProvider,
|
||||
requesterChannel: entry.requesterChannel,
|
||||
requesterDisplayKey: entry.requesterDisplayKey,
|
||||
task: entry.task,
|
||||
timeoutMs: 30_000,
|
||||
@@ -133,7 +133,7 @@ export function registerSubagentRun(params: {
|
||||
runId: string;
|
||||
childSessionKey: string;
|
||||
requesterSessionKey: string;
|
||||
requesterProvider?: string;
|
||||
requesterChannel?: string;
|
||||
requesterDisplayKey: string;
|
||||
task: string;
|
||||
cleanup: "delete" | "keep";
|
||||
@@ -152,7 +152,7 @@ export function registerSubagentRun(params: {
|
||||
runId: params.runId,
|
||||
childSessionKey: params.childSessionKey,
|
||||
requesterSessionKey: params.requesterSessionKey,
|
||||
requesterProvider: params.requesterProvider,
|
||||
requesterChannel: params.requesterChannel,
|
||||
requesterDisplayKey: params.requesterDisplayKey,
|
||||
task: params.task,
|
||||
cleanup: params.cleanup,
|
||||
@@ -191,7 +191,7 @@ async function waitForSubagentCompletion(runId: string, waitTimeoutMs: number) {
|
||||
childSessionKey: entry.childSessionKey,
|
||||
childRunId: entry.runId,
|
||||
requesterSessionKey: entry.requesterSessionKey,
|
||||
requesterProvider: entry.requesterProvider,
|
||||
requesterChannel: entry.requesterChannel,
|
||||
requesterDisplayKey: entry.requesterDisplayKey,
|
||||
task: entry.task,
|
||||
timeoutMs: 30_000,
|
||||
|
||||
@@ -153,7 +153,7 @@ describe("buildAgentSystemPrompt", () => {
|
||||
toolNames: ["message"],
|
||||
});
|
||||
|
||||
expect(prompt).toContain("message: Send messages and provider actions");
|
||||
expect(prompt).toContain("message: Send messages and channel actions");
|
||||
expect(prompt).toContain("### message tool");
|
||||
});
|
||||
|
||||
@@ -161,12 +161,12 @@ describe("buildAgentSystemPrompt", () => {
|
||||
const prompt = buildAgentSystemPrompt({
|
||||
workspaceDir: "/tmp/clawd",
|
||||
runtimeInfo: {
|
||||
provider: "telegram",
|
||||
channel: "telegram",
|
||||
capabilities: ["inlineButtons"],
|
||||
},
|
||||
});
|
||||
|
||||
expect(prompt).toContain("provider=telegram");
|
||||
expect(prompt).toContain("channel=telegram");
|
||||
expect(prompt).toContain("capabilities=inlineButtons");
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { ReasoningLevel, ThinkLevel } from "../auto-reply/thinking.js";
|
||||
import { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
|
||||
import { PROVIDER_IDS } from "../providers/registry.js";
|
||||
import { CHANNEL_IDS } from "../channels/registry.js";
|
||||
import type { EmbeddedContextFile } from "./pi-embedded-helpers.js";
|
||||
|
||||
const MESSAGE_PROVIDER_OPTIONS = PROVIDER_IDS.join("|");
|
||||
const MESSAGE_CHANNEL_OPTIONS = CHANNEL_IDS.join("|");
|
||||
|
||||
export function buildAgentSystemPrompt(params: {
|
||||
workspaceDir: string;
|
||||
@@ -26,7 +26,7 @@ export function buildAgentSystemPrompt(params: {
|
||||
arch?: string;
|
||||
node?: string;
|
||||
model?: string;
|
||||
provider?: string;
|
||||
channel?: string;
|
||||
capabilities?: string[];
|
||||
};
|
||||
sandboxInfo?: {
|
||||
@@ -56,12 +56,12 @@ export function buildAgentSystemPrompt(params: {
|
||||
ls: "List directory contents",
|
||||
exec: "Run shell commands",
|
||||
process: "Manage background exec sessions",
|
||||
// Provider docking: add provider login tools here when a provider needs interactive linking.
|
||||
// Channel docking: add login tools here when a channel needs interactive linking.
|
||||
browser: "Control web browser",
|
||||
canvas: "Present/eval/snapshot the Canvas",
|
||||
nodes: "List/describe/notify/camera/screen on paired nodes",
|
||||
cron: "Manage cron jobs and wake events (use for reminders)",
|
||||
message: "Send messages and provider actions",
|
||||
message: "Send messages and channel actions",
|
||||
gateway:
|
||||
"Restart, apply config, or run updates on the running Clawdbot process",
|
||||
agents_list: "List agent ids allowed for sessions_spawn",
|
||||
@@ -166,7 +166,7 @@ export function buildAgentSystemPrompt(params: {
|
||||
? `Heartbeat prompt: ${heartbeatPrompt}`
|
||||
: "Heartbeat prompt: (configured)";
|
||||
const runtimeInfo = params.runtimeInfo;
|
||||
const runtimeProvider = runtimeInfo?.provider?.trim().toLowerCase();
|
||||
const runtimeChannel = runtimeInfo?.channel?.trim().toLowerCase();
|
||||
const runtimeCapabilities = (runtimeInfo?.capabilities ?? [])
|
||||
.map((cap) => String(cap).trim())
|
||||
.filter(Boolean);
|
||||
@@ -322,23 +322,23 @@ export function buildAgentSystemPrompt(params: {
|
||||
"- [[reply_to_current]] replies to the triggering message.",
|
||||
"- [[reply_to:<id>]] replies to a specific message id when you have it.",
|
||||
"Whitespace inside the tag is allowed (e.g. [[ reply_to_current ]] / [[ reply_to: 123 ]]).",
|
||||
"Tags are stripped before sending; support depends on the current provider config.",
|
||||
"Tags are stripped before sending; support depends on the current channel config.",
|
||||
"",
|
||||
"## Messaging",
|
||||
"- Reply in current session → automatically routes to the source provider (Signal, Telegram, etc.)",
|
||||
"- Reply in current session → automatically routes to the source channel (Signal, Telegram, etc.)",
|
||||
"- Cross-session messaging → use sessions_send(sessionKey, message)",
|
||||
"- Never use exec/curl for provider messaging; Clawdbot handles all routing internally.",
|
||||
availableTools.has("message")
|
||||
? [
|
||||
"",
|
||||
"### message tool",
|
||||
"- Use `message` for proactive sends + provider actions (polls, reactions, etc.).",
|
||||
"- Use `message` for proactive sends + channel actions (polls, reactions, etc.).",
|
||||
"- For `action=send`, include `to` and `message`.",
|
||||
`- If multiple providers are configured, pass \`provider\` (${MESSAGE_PROVIDER_OPTIONS}).`,
|
||||
`- If multiple channels are configured, pass \`channel\` (${MESSAGE_CHANNEL_OPTIONS}).`,
|
||||
inlineButtonsEnabled
|
||||
? "- Inline buttons supported. Use `action=send` with `buttons=[[{text,callback_data}]]` (callback_data routes back as a user message)."
|
||||
: runtimeProvider
|
||||
? `- Inline buttons not enabled for ${runtimeProvider}. If you need them, ask to add "inlineButtons" to ${runtimeProvider}.capabilities or ${runtimeProvider}.accounts.<id>.capabilities.`
|
||||
: runtimeChannel
|
||||
? `- Inline buttons not enabled for ${runtimeChannel}. If you need them, ask to add "inlineButtons" to ${runtimeChannel}.capabilities or ${runtimeChannel}.accounts.<id>.capabilities.`
|
||||
: "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
@@ -397,8 +397,8 @@ export function buildAgentSystemPrompt(params: {
|
||||
: "",
|
||||
runtimeInfo?.node ? `node=${runtimeInfo.node}` : "",
|
||||
runtimeInfo?.model ? `model=${runtimeInfo.model}` : "",
|
||||
runtimeProvider ? `provider=${runtimeProvider}` : "",
|
||||
runtimeProvider
|
||||
runtimeChannel ? `channel=${runtimeChannel}` : "",
|
||||
runtimeChannel
|
||||
? `capabilities=${
|
||||
runtimeCapabilities.length > 0
|
||||
? runtimeCapabilities.join(",")
|
||||
|
||||
@@ -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