mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:20:43 +00:00
test: decouple outbound target tests from bundled plugins
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
5e63409c91d1021a24496f498af2761cca644e59e26742b473eb53615d13154f plugin-sdk-api-baseline.json
|
||||
61943ab581937f84635e9b46e0f05591bb1fabe606cb57c36e9aed7a1242c685 plugin-sdk-api-baseline.jsonl
|
||||
4ec700ac180b7eca81ca48885bc7f645dbf5016e2438e44678f4c206eed4b643 plugin-sdk-api-baseline.json
|
||||
ff0d1541e7220c67d97444304568285303e423770bd6af6227afdf470bf233cc plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -707,6 +707,7 @@ export const telegramPlugin = createChatChannelPlugin({
|
||||
resolveTelegramSessionConversation({ kind, rawId }),
|
||||
parseExplicitTarget: ({ raw }) => parseTelegramExplicitTarget(raw),
|
||||
inferTargetChatType: ({ to }) => parseTelegramExplicitTarget(to).chatType,
|
||||
preserveHeartbeatThreadIdForGroupRoute: true,
|
||||
formatTargetDisplay: ({ target, display, kind }) => {
|
||||
const formatted = display?.trim();
|
||||
if (formatted) {
|
||||
|
||||
@@ -10,15 +10,15 @@ import {
|
||||
resolveComparableTargetForLoadedChannel,
|
||||
} from "./target-parsing.js";
|
||||
|
||||
function parseTelegramTargetForTest(raw: string): {
|
||||
function parseThreadedTargetForTest(raw: string): {
|
||||
to: string;
|
||||
threadId?: number;
|
||||
chatType?: "direct" | "group";
|
||||
} {
|
||||
const trimmed = raw
|
||||
.trim()
|
||||
.replace(/^telegram:/i, "")
|
||||
.replace(/^tg:/i, "");
|
||||
.replace(/^threaded:/i, "")
|
||||
.replace(/^mock:/i, "");
|
||||
const prefixedTopic = /^group:([^:]+):topic:(\d+)$/i.exec(trimmed);
|
||||
if (prefixedTopic) {
|
||||
return {
|
||||
@@ -45,14 +45,14 @@ function setMinimalTargetParsingRegistry(): void {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "telegram",
|
||||
pluginId: "mock-threaded",
|
||||
plugin: {
|
||||
id: "telegram",
|
||||
id: "mock-threaded",
|
||||
meta: {
|
||||
id: "telegram",
|
||||
label: "Telegram",
|
||||
selectionLabel: "Telegram",
|
||||
docsPath: "/channels/telegram",
|
||||
id: "mock-threaded",
|
||||
label: "Mock Threaded",
|
||||
selectionLabel: "Mock Threaded",
|
||||
docsPath: "/channels/mock-threaded",
|
||||
blurb: "test stub",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct", "group"] },
|
||||
@@ -61,7 +61,7 @@ function setMinimalTargetParsingRegistry(): void {
|
||||
resolveAccount: () => ({}),
|
||||
},
|
||||
messaging: {
|
||||
parseExplicitTarget: ({ raw }: { raw: string }) => parseTelegramTargetForTest(raw),
|
||||
parseExplicitTarget: ({ raw }: { raw: string }) => parseThreadedTargetForTest(raw),
|
||||
},
|
||||
},
|
||||
source: "test",
|
||||
@@ -100,15 +100,17 @@ describe("parseExplicitTargetForChannel", () => {
|
||||
setMinimalTargetParsingRegistry();
|
||||
});
|
||||
|
||||
it("parses Telegram targets via the registered channel plugin contract", () => {
|
||||
expect(parseExplicitTargetForChannel("telegram", "telegram:group:-100123:topic:77")).toEqual({
|
||||
to: "-100123",
|
||||
it("parses threaded targets via the registered channel plugin contract", () => {
|
||||
expect(
|
||||
parseExplicitTargetForChannel("mock-threaded", "threaded:group:room-a:topic:77"),
|
||||
).toEqual({
|
||||
to: "room-a",
|
||||
threadId: 77,
|
||||
chatType: "group",
|
||||
});
|
||||
expect(parseExplicitTargetForChannel("telegram", "-100123")).toEqual({
|
||||
to: "-100123",
|
||||
chatType: "group",
|
||||
expect(parseExplicitTargetForChannel("mock-threaded", "room-a")).toEqual({
|
||||
to: "room-a",
|
||||
chatType: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -126,23 +128,23 @@ describe("parseExplicitTargetForChannel", () => {
|
||||
it("builds comparable targets from plugin-owned grammar", () => {
|
||||
expect(
|
||||
resolveComparableTargetForChannel({
|
||||
channel: "telegram",
|
||||
rawTarget: "telegram:group:-100123:topic:77",
|
||||
channel: "mock-threaded",
|
||||
rawTarget: "threaded:group:room-a:topic:77",
|
||||
}),
|
||||
).toEqual({
|
||||
rawTo: "telegram:group:-100123:topic:77",
|
||||
to: "-100123",
|
||||
rawTo: "threaded:group:room-a:topic:77",
|
||||
to: "room-a",
|
||||
threadId: 77,
|
||||
chatType: "group",
|
||||
});
|
||||
expect(
|
||||
resolveComparableTargetForLoadedChannel({
|
||||
channel: "telegram",
|
||||
rawTarget: "telegram:group:-100123:topic:77",
|
||||
channel: "mock-threaded",
|
||||
rawTarget: "threaded:group:room-a:topic:77",
|
||||
}),
|
||||
).toEqual({
|
||||
rawTo: "telegram:group:-100123:topic:77",
|
||||
to: "-100123",
|
||||
rawTo: "threaded:group:room-a:topic:77",
|
||||
to: "room-a",
|
||||
threadId: 77,
|
||||
chatType: "group",
|
||||
});
|
||||
@@ -150,12 +152,12 @@ describe("parseExplicitTargetForChannel", () => {
|
||||
|
||||
it("matches comparable targets when only the plugin grammar differs", () => {
|
||||
const topicTarget = resolveComparableTargetForChannel({
|
||||
channel: "telegram",
|
||||
rawTarget: "telegram:-100123:topic:77",
|
||||
channel: "mock-threaded",
|
||||
rawTarget: "threaded:room-a:topic:77",
|
||||
});
|
||||
const bareTarget = resolveComparableTargetForChannel({
|
||||
channel: "telegram",
|
||||
rawTarget: "-100123",
|
||||
channel: "mock-threaded",
|
||||
rawTarget: "room-a",
|
||||
});
|
||||
|
||||
expect(
|
||||
|
||||
@@ -515,6 +515,11 @@ export type ChannelMessagingAdapter = {
|
||||
* steer peer-vs-group resolution without reimplementing host search flow.
|
||||
*/
|
||||
inferTargetChatType?: (params: { to: string }) => ChatType | undefined;
|
||||
/**
|
||||
* Preserve the session thread/topic id for heartbeat replies when that thread
|
||||
* is part of the destination identity, not a transient reply thread.
|
||||
*/
|
||||
preserveHeartbeatThreadIdForGroupRoute?: boolean;
|
||||
buildCrossContextComponents?: ChannelCrossContextComponentsFactory;
|
||||
transformReplyPayload?: (params: {
|
||||
payload: ReplyPayload;
|
||||
|
||||
@@ -4,7 +4,6 @@ import { runSubagentAnnounceFlow } from "../agents/subagent-announce.js";
|
||||
import type { ChannelOutboundAdapter, ChannelOutboundContext } from "../channels/plugins/types.js";
|
||||
import type { CliDeps } from "../cli/deps.js";
|
||||
import { resolveOutboundSendDep } from "../infra/outbound/send-deps.js";
|
||||
import { createWhatsAppTestPlugin } from "../infra/outbound/targets.test-helpers.js";
|
||||
import { setActivePluginRegistry } from "../plugins/runtime.js";
|
||||
import { createOutboundTestPlugin, createTestRegistry } from "../test-utils/channel-plugins.js";
|
||||
import { createCliDeps, mockAgentPayloads } from "./isolated-agent.delivery.test-helpers.js";
|
||||
@@ -169,7 +168,12 @@ function createCliDelegatingOutbound(params: {
|
||||
};
|
||||
}
|
||||
|
||||
const whatsappResolveTarget = createWhatsAppTestPlugin().outbound?.resolveTarget;
|
||||
const identityResolveTarget: ChannelOutboundAdapter["resolveTarget"] = ({ to }) => {
|
||||
const trimmed = to?.trim();
|
||||
return trimmed
|
||||
? { ok: true, to: trimmed }
|
||||
: { ok: false, error: new Error("target is required") };
|
||||
};
|
||||
|
||||
describe("runCronIsolatedAgentTurn core-channel direct delivery", () => {
|
||||
beforeEach(() => {
|
||||
@@ -199,7 +203,7 @@ describe("runCronIsolatedAgentTurn core-channel direct delivery", () => {
|
||||
outbound: createCliDelegatingOutbound({
|
||||
channel: "whatsapp",
|
||||
deliveryMode: "gateway",
|
||||
resolveTarget: whatsappResolveTarget,
|
||||
resolveTarget: identityResolveTarget,
|
||||
}),
|
||||
}),
|
||||
source: "test",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ChannelOutboundAdapter } from "../../channels/plugins/types.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { telegramMessagingForTest } from "../../infra/outbound/targets.test-helpers.js";
|
||||
import { forumMessagingForTest } from "../../infra/outbound/targets.test-helpers.js";
|
||||
import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../../plugins/runtime.js";
|
||||
import { createOutboundTestPlugin, createTestRegistry } from "../../test-utils/channel-plugins.js";
|
||||
|
||||
@@ -20,7 +20,7 @@ vi.mock("../../config/sessions/store-load.js", () => ({
|
||||
vi.mock("../../infra/outbound/channel-selection.runtime.js", () => ({
|
||||
resolveMessageChannelSelection: vi
|
||||
.fn()
|
||||
.mockResolvedValue({ channel: "telegram", configured: ["telegram"] }),
|
||||
.mockResolvedValue({ channel: "alpha", configured: ["alpha"] }),
|
||||
}));
|
||||
|
||||
vi.mock("../../infra/outbound/target-id-resolution.js", () => ({
|
||||
@@ -92,26 +92,26 @@ beforeEach(() => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "telegram",
|
||||
pluginId: "forum",
|
||||
plugin: createOutboundTestPlugin({
|
||||
id: "telegram",
|
||||
outbound: createStubOutbound("Telegram"),
|
||||
messaging: telegramMessagingForTest,
|
||||
id: "forum",
|
||||
outbound: createStubOutbound("Forum"),
|
||||
messaging: forumMessagingForTest,
|
||||
}),
|
||||
source: "test",
|
||||
},
|
||||
{
|
||||
pluginId: "whatsapp",
|
||||
pluginId: "alpha",
|
||||
plugin: {
|
||||
...createOutboundTestPlugin({
|
||||
id: "whatsapp",
|
||||
outbound: createAllowlistAwareStubOutbound("WhatsApp"),
|
||||
id: "alpha",
|
||||
outbound: createAllowlistAwareStubOutbound("Alpha"),
|
||||
}),
|
||||
config: {
|
||||
listAccountIds: () => [],
|
||||
resolveAccount: () => ({}),
|
||||
resolveAllowFrom: ({ cfg }: { cfg: OpenClawConfig }) =>
|
||||
(cfg.channels?.whatsapp as { allowFrom?: string[] } | undefined)?.allowFrom,
|
||||
(cfg.channels?.alpha as { allowFrom?: string[] } | undefined)?.allowFrom,
|
||||
},
|
||||
},
|
||||
source: "test",
|
||||
@@ -132,12 +132,12 @@ function makeCfg(overrides?: Partial<OpenClawConfig>): OpenClawConfig {
|
||||
} as OpenClawConfig;
|
||||
}
|
||||
|
||||
function makeTelegramBoundCfg(accountId = "account-b"): OpenClawConfig {
|
||||
function makeForumBoundCfg(accountId = "account-b"): OpenClawConfig {
|
||||
return makeCfg({
|
||||
bindings: [
|
||||
{
|
||||
agentId: AGENT_ID,
|
||||
match: { channel: "telegram", accountId },
|
||||
match: { channel: "forum", accountId },
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -145,8 +145,8 @@ function makeTelegramBoundCfg(accountId = "account-b"): OpenClawConfig {
|
||||
|
||||
const AGENT_ID = "agent-b";
|
||||
const DEFAULT_TARGET = {
|
||||
channel: "telegram" as const,
|
||||
to: "123456",
|
||||
channel: "forum" as const,
|
||||
to: "room:default",
|
||||
};
|
||||
|
||||
type SessionStore = ReturnType<typeof loadSessionStore>;
|
||||
@@ -177,13 +177,13 @@ function setLastSessionEntry(params: {
|
||||
});
|
||||
}
|
||||
|
||||
function setStoredWhatsAppAllowFrom(allowFrom: string[]) {
|
||||
function setStoredAlphaAllowFrom(allowFrom: string[]) {
|
||||
vi.mocked(readChannelAllowFromStoreEntriesSync).mockReturnValue(allowFrom);
|
||||
}
|
||||
|
||||
async function resolveForAgent(params: {
|
||||
cfg: OpenClawConfig;
|
||||
target?: { channel?: "last" | "telegram"; to?: string };
|
||||
target?: { channel?: "last" | "forum" | "alpha"; to?: string };
|
||||
}) {
|
||||
const channel = params.target ? params.target.channel : DEFAULT_TARGET.channel;
|
||||
const to = params.target && "to" in params.target ? params.target.to : DEFAULT_TARGET.to;
|
||||
@@ -201,41 +201,41 @@ async function resolveLastTarget(cfg: OpenClawConfig) {
|
||||
}
|
||||
|
||||
describe("resolveDeliveryTarget", () => {
|
||||
it("reroutes implicit whatsapp delivery to authorized allowFrom recipient", async () => {
|
||||
it("reroutes implicit delivery to an authorized allowFrom recipient", async () => {
|
||||
setLastSessionEntry({
|
||||
sessionId: "sess-w1",
|
||||
lastChannel: "whatsapp",
|
||||
lastTo: "+15550000099",
|
||||
lastChannel: "alpha",
|
||||
lastTo: "room-denied",
|
||||
});
|
||||
setStoredWhatsAppAllowFrom(["+15550000001"]);
|
||||
setStoredAlphaAllowFrom(["room-allowed"]);
|
||||
|
||||
const cfg = makeCfg({ bindings: [], channels: { whatsapp: { allowFrom: [] } } });
|
||||
const cfg = makeCfg({ bindings: [], channels: { alpha: { allowFrom: [] } } });
|
||||
const result = await resolveLastTarget(cfg);
|
||||
|
||||
expect(result.channel).toBe("whatsapp");
|
||||
expect(result.to).toBe("+15550000001");
|
||||
expect(result.channel).toBe("alpha");
|
||||
expect(result.to).toBe("room-allowed");
|
||||
});
|
||||
|
||||
it("keeps explicit whatsapp target unchanged", async () => {
|
||||
it("keeps explicit delivery target unchanged", async () => {
|
||||
setLastSessionEntry({
|
||||
sessionId: "sess-w2",
|
||||
lastChannel: "whatsapp",
|
||||
lastTo: "+15550000099",
|
||||
lastChannel: "alpha",
|
||||
lastTo: "room-denied",
|
||||
});
|
||||
setStoredWhatsAppAllowFrom(["+15550000001"]);
|
||||
setStoredAlphaAllowFrom(["room-allowed"]);
|
||||
|
||||
const cfg = makeCfg({ bindings: [], channels: { whatsapp: { allowFrom: [] } } });
|
||||
const cfg = makeCfg({ bindings: [], channels: { alpha: { allowFrom: [] } } });
|
||||
const result = await resolveDeliveryTarget(cfg, AGENT_ID, {
|
||||
channel: "whatsapp",
|
||||
to: "+15550000099",
|
||||
channel: "alpha",
|
||||
to: "room-denied",
|
||||
});
|
||||
|
||||
expect(result.to).toBe("+15550000099");
|
||||
expect(result.to).toBe("room-denied");
|
||||
});
|
||||
|
||||
it("falls back to bound accountId when session has no lastAccountId", async () => {
|
||||
setMainSessionEntry(undefined);
|
||||
const cfg = makeTelegramBoundCfg();
|
||||
const cfg = makeForumBoundCfg();
|
||||
const result = await resolveForAgent({ cfg });
|
||||
|
||||
expect(result.accountId).toBe("account-b");
|
||||
@@ -248,14 +248,14 @@ describe("resolveDeliveryTarget", () => {
|
||||
{
|
||||
agentId: AGENT_ID,
|
||||
match: {
|
||||
channel: "telegram",
|
||||
peer: { kind: "channel", id: "123456" },
|
||||
channel: "forum",
|
||||
peer: { kind: "channel", id: "room:default" },
|
||||
accountId: "peer-first",
|
||||
},
|
||||
},
|
||||
{
|
||||
agentId: AGENT_ID,
|
||||
match: { channel: "telegram", accountId: "channel-second" },
|
||||
match: { channel: "forum", accountId: "channel-second" },
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -272,7 +272,7 @@ describe("resolveDeliveryTarget", () => {
|
||||
{
|
||||
agentId: AGENT_ID,
|
||||
match: {
|
||||
channel: "telegram",
|
||||
channel: "forum",
|
||||
guildId: "guild-1",
|
||||
accountId: "tenant-account",
|
||||
},
|
||||
@@ -289,12 +289,12 @@ describe("resolveDeliveryTarget", () => {
|
||||
setMainSessionEntry({
|
||||
sessionId: "sess-1",
|
||||
updatedAt: 1000,
|
||||
lastChannel: "telegram",
|
||||
lastTo: "123456",
|
||||
lastChannel: "forum",
|
||||
lastTo: "room:default",
|
||||
lastAccountId: "session-account",
|
||||
});
|
||||
|
||||
const cfg = makeTelegramBoundCfg();
|
||||
const cfg = makeForumBoundCfg();
|
||||
const result = await resolveForAgent({ cfg });
|
||||
|
||||
// Session-derived accountId should take precedence over binding
|
||||
@@ -321,7 +321,7 @@ describe("resolveDeliveryTarget", () => {
|
||||
});
|
||||
|
||||
const result = await resolveDeliveryTarget(makeCfg({ bindings: [] }), AGENT_ID, {
|
||||
channel: "telegram",
|
||||
channel: "forum",
|
||||
to: "123456789",
|
||||
});
|
||||
|
||||
@@ -329,7 +329,7 @@ describe("resolveDeliveryTarget", () => {
|
||||
expect(result.to).toBe("user:123456789");
|
||||
expect(maybeResolveIdLikeTarget).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
channel: "telegram",
|
||||
channel: "forum",
|
||||
input: "123456789",
|
||||
}),
|
||||
);
|
||||
@@ -340,33 +340,33 @@ describe("resolveDeliveryTarget", () => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "whatsapp",
|
||||
pluginId: "alpha",
|
||||
plugin: createOutboundTestPlugin({
|
||||
id: "whatsapp",
|
||||
outbound: createStubOutbound("WhatsApp"),
|
||||
id: "alpha",
|
||||
outbound: createStubOutbound("Alpha"),
|
||||
}),
|
||||
source: "test",
|
||||
},
|
||||
]),
|
||||
);
|
||||
vi.mocked(resolveOutboundTarget).mockReturnValueOnce({ ok: true, to: "123456" });
|
||||
vi.mocked(resolveOutboundTarget).mockReturnValueOnce({ ok: true, to: "room:default" });
|
||||
|
||||
const result = await resolveDeliveryTarget(makeCfg({ bindings: [] }), AGENT_ID, {
|
||||
channel: "telegram",
|
||||
to: "123456",
|
||||
channel: "forum",
|
||||
to: "room:default",
|
||||
});
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
ok: true,
|
||||
channel: "telegram",
|
||||
to: "123456",
|
||||
channel: "forum",
|
||||
to: "room:default",
|
||||
}),
|
||||
);
|
||||
expect(resolveOutboundTarget).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
channel: "telegram",
|
||||
to: "123456",
|
||||
channel: "forum",
|
||||
to: "room:default",
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -378,11 +378,11 @@ describe("resolveDeliveryTarget", () => {
|
||||
bindings: [
|
||||
{
|
||||
agentId: "agent-a",
|
||||
match: { channel: "telegram", accountId: "account-a" },
|
||||
match: { channel: "forum", accountId: "account-a" },
|
||||
},
|
||||
{
|
||||
agentId: "agent-b",
|
||||
match: { channel: "telegram", accountId: "account-b" },
|
||||
match: { channel: "forum", accountId: "account-b" },
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -399,7 +399,7 @@ describe("resolveDeliveryTarget", () => {
|
||||
bindings: [
|
||||
{
|
||||
agentId: "agent-b",
|
||||
match: { channel: "discord", accountId: "discord-account" },
|
||||
match: { channel: "alpha", accountId: "alpha-account" },
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -412,8 +412,8 @@ describe("resolveDeliveryTarget", () => {
|
||||
it("drops session threadId when destination does not match the previous recipient", async () => {
|
||||
setLastSessionEntry({
|
||||
sessionId: "sess-2",
|
||||
lastChannel: "telegram",
|
||||
lastTo: "999999",
|
||||
lastChannel: "forum",
|
||||
lastTo: "room:other",
|
||||
lastThreadId: "thread-1",
|
||||
});
|
||||
|
||||
@@ -424,8 +424,8 @@ describe("resolveDeliveryTarget", () => {
|
||||
it("keeps session threadId when destination matches the previous recipient", async () => {
|
||||
setLastSessionEntry({
|
||||
sessionId: "sess-3",
|
||||
lastChannel: "telegram",
|
||||
lastTo: "123456",
|
||||
lastChannel: "forum",
|
||||
lastTo: "room:default",
|
||||
lastThreadId: "thread-2",
|
||||
});
|
||||
|
||||
@@ -437,7 +437,7 @@ describe("resolveDeliveryTarget", () => {
|
||||
setMainSessionEntry(undefined);
|
||||
|
||||
const result = await resolveLastTarget(makeCfg({ bindings: [] }));
|
||||
expect(result.channel).toBe("telegram");
|
||||
expect(result.channel).toBe("alpha");
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) {
|
||||
throw new Error("expected unresolved delivery target");
|
||||
@@ -450,7 +450,7 @@ describe("resolveDeliveryTarget", () => {
|
||||
it("returns an error when channel selection is ambiguous", async () => {
|
||||
setMainSessionEntry(undefined);
|
||||
vi.mocked(resolveMessageChannelSelection).mockRejectedValueOnce(
|
||||
new Error("Channel is required when multiple channels are configured: telegram, slack"),
|
||||
new Error("Channel is required when multiple channels are configured: alpha, forum"),
|
||||
);
|
||||
|
||||
const result = await resolveLastTarget(makeCfg({ bindings: [] }));
|
||||
@@ -468,13 +468,13 @@ describe("resolveDeliveryTarget", () => {
|
||||
"agent:test:main": {
|
||||
sessionId: "main-session",
|
||||
updatedAt: 1000,
|
||||
lastChannel: "telegram",
|
||||
lastChannel: "forum",
|
||||
lastTo: "main-chat",
|
||||
},
|
||||
"agent:test:thread:42": {
|
||||
sessionId: "thread-session",
|
||||
updatedAt: 2000,
|
||||
lastChannel: "telegram",
|
||||
lastChannel: "forum",
|
||||
lastTo: "thread-chat",
|
||||
lastThreadId: 42,
|
||||
},
|
||||
@@ -486,7 +486,7 @@ describe("resolveDeliveryTarget", () => {
|
||||
to: undefined,
|
||||
});
|
||||
|
||||
expect(result.channel).toBe("telegram");
|
||||
expect(result.channel).toBe("forum");
|
||||
expect(result.to).toBe("thread-chat");
|
||||
expect(result.threadId).toBe(42);
|
||||
});
|
||||
@@ -496,7 +496,7 @@ describe("resolveDeliveryTarget", () => {
|
||||
"agent:test:main": {
|
||||
sessionId: "main-session",
|
||||
updatedAt: 1000,
|
||||
lastChannel: "telegram",
|
||||
lastChannel: "forum",
|
||||
lastTo: "main-chat",
|
||||
},
|
||||
} as SessionStore);
|
||||
@@ -507,34 +507,34 @@ describe("resolveDeliveryTarget", () => {
|
||||
to: undefined,
|
||||
});
|
||||
|
||||
expect(result.channel).toBe("telegram");
|
||||
expect(result.channel).toBe("forum");
|
||||
expect(result.to).toBe("main-chat");
|
||||
});
|
||||
|
||||
it("uses main session channel when channel=last and session route exists", async () => {
|
||||
setLastSessionEntry({
|
||||
sessionId: "sess-4",
|
||||
lastChannel: "telegram",
|
||||
lastTo: "987654",
|
||||
lastChannel: "forum",
|
||||
lastTo: "room:default",
|
||||
});
|
||||
|
||||
const result = await resolveLastTarget(makeCfg({ bindings: [] }));
|
||||
|
||||
expect(result.channel).toBe("telegram");
|
||||
expect(result.to).toBe("987654");
|
||||
expect(result.channel).toBe("forum");
|
||||
expect(result.to).toBe("room:default");
|
||||
expect(result.ok).toBe(true);
|
||||
});
|
||||
|
||||
it("parses explicit telegram topic targets into delivery threadId", async () => {
|
||||
it("parses explicit plugin topic targets into delivery threadId", async () => {
|
||||
setMainSessionEntry(undefined);
|
||||
|
||||
const result = await resolveDeliveryTarget(makeCfg({ bindings: [] }), AGENT_ID, {
|
||||
channel: "telegram",
|
||||
to: "63448508:topic:1008013",
|
||||
channel: "forum",
|
||||
to: "room:ops:topic:1008013",
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
expect(result.to).toBe("63448508");
|
||||
expect(result.to).toBe("room:ops");
|
||||
expect(result.threadId).toBe(1008013);
|
||||
});
|
||||
|
||||
@@ -542,27 +542,27 @@ describe("resolveDeliveryTarget", () => {
|
||||
setMainSessionEntry(undefined);
|
||||
|
||||
const result = await resolveDeliveryTarget(makeCfg({ bindings: [] }), AGENT_ID, {
|
||||
channel: "telegram",
|
||||
to: "63448508",
|
||||
channel: "forum",
|
||||
to: "room:ops",
|
||||
threadId: "1008013",
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
expect(result.to).toBe("63448508");
|
||||
expect(result.to).toBe("room:ops");
|
||||
expect(result.threadId).toBe("1008013");
|
||||
});
|
||||
|
||||
it("explicit delivery.accountId overrides session-derived accountId", async () => {
|
||||
setLastSessionEntry({
|
||||
sessionId: "sess-5",
|
||||
lastChannel: "telegram",
|
||||
lastTo: "chat-999",
|
||||
lastChannel: "forum",
|
||||
lastTo: "room:ops",
|
||||
lastAccountId: "default",
|
||||
});
|
||||
|
||||
const result = await resolveDeliveryTarget(makeCfg({ bindings: [] }), AGENT_ID, {
|
||||
channel: "telegram",
|
||||
to: "chat-999",
|
||||
channel: "forum",
|
||||
to: "room:ops",
|
||||
accountId: "bot-b",
|
||||
});
|
||||
|
||||
@@ -573,12 +573,12 @@ describe("resolveDeliveryTarget", () => {
|
||||
it("explicit delivery.accountId overrides bindings-derived accountId", async () => {
|
||||
setMainSessionEntry(undefined);
|
||||
const cfg = makeCfg({
|
||||
bindings: [{ agentId: AGENT_ID, match: { channel: "telegram", accountId: "bound" } }],
|
||||
bindings: [{ agentId: AGENT_ID, match: { channel: "forum", accountId: "bound" } }],
|
||||
});
|
||||
|
||||
const result = await resolveDeliveryTarget(cfg, AGENT_ID, {
|
||||
channel: "telegram",
|
||||
to: "chat-777",
|
||||
channel: "forum",
|
||||
to: "room:ops",
|
||||
accountId: "explicit",
|
||||
});
|
||||
|
||||
|
||||
@@ -18,19 +18,20 @@ describe("tryResolveLoadedOutboundTarget", () => {
|
||||
it("returns undefined when no loaded plugin exists", () => {
|
||||
mocks.getLoadedChannelPlugin.mockReturnValue(undefined);
|
||||
|
||||
expect(tryResolveLoadedOutboundTarget({ channel: "telegram", to: "123" })).toBeUndefined();
|
||||
expect(tryResolveLoadedOutboundTarget({ channel: "alpha", to: "room-one" })).toBeUndefined();
|
||||
});
|
||||
|
||||
it("uses loaded plugin config defaultTo fallback", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: { telegram: { defaultTo: "123456789" } },
|
||||
channels: { alpha: { defaultTo: "room-one" } },
|
||||
};
|
||||
mocks.getLoadedChannelPlugin.mockReturnValue({
|
||||
id: "telegram",
|
||||
meta: { label: "Telegram" },
|
||||
id: "alpha",
|
||||
meta: { label: "Alpha" },
|
||||
capabilities: {},
|
||||
config: {
|
||||
resolveDefaultTo: ({ cfg }: { cfg: OpenClawConfig }) => cfg.channels?.telegram?.defaultTo,
|
||||
resolveDefaultTo: ({ cfg }: { cfg: OpenClawConfig }) =>
|
||||
(cfg.channels?.alpha as { defaultTo?: string } | undefined)?.defaultTo,
|
||||
},
|
||||
outbound: {},
|
||||
messaging: {},
|
||||
@@ -38,17 +39,17 @@ describe("tryResolveLoadedOutboundTarget", () => {
|
||||
|
||||
expect(
|
||||
tryResolveLoadedOutboundTarget({
|
||||
channel: "telegram",
|
||||
channel: "alpha",
|
||||
to: "",
|
||||
cfg,
|
||||
mode: "implicit",
|
||||
}),
|
||||
).toEqual({ ok: true, to: "123456789" });
|
||||
).toEqual({ ok: true, to: "room-one" });
|
||||
});
|
||||
|
||||
it("trims channel ids before reading the loaded registry", () => {
|
||||
tryResolveLoadedOutboundTarget({ channel: " telegram " as never, to: "123" });
|
||||
tryResolveLoadedOutboundTarget({ channel: " alpha " as never, to: "room-one" });
|
||||
|
||||
expect(mocks.getLoadedChannelPlugin).toHaveBeenCalledWith("telegram");
|
||||
expect(mocks.getLoadedChannelPlugin).toHaveBeenCalledWith("alpha");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,15 +2,20 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { setActivePluginRegistry } from "../../plugins/runtime.js";
|
||||
import { resolveOutboundTarget } from "./targets.js";
|
||||
import {
|
||||
createForumTargetTestPlugin,
|
||||
createGenericTargetTestPlugin,
|
||||
createTargetsTestRegistry,
|
||||
createTelegramTestPlugin,
|
||||
createWhatsAppTestPlugin,
|
||||
createTestChannelPlugin,
|
||||
} from "./targets.test-helpers.js";
|
||||
|
||||
export function installResolveOutboundTargetPluginRegistryHooks(): void {
|
||||
beforeEach(() => {
|
||||
setActivePluginRegistry(
|
||||
createTargetsTestRegistry([createWhatsAppTestPlugin(), createTelegramTestPlugin()]),
|
||||
createTargetsTestRegistry([
|
||||
createGenericTargetTestPlugin("alpha", "Alpha"),
|
||||
createGenericTargetTestPlugin("beta", "Beta"),
|
||||
createForumTargetTestPlugin(),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -23,69 +28,50 @@ export function runResolveOutboundTargetCoreTests(): void {
|
||||
describe("resolveOutboundTarget", () => {
|
||||
installResolveOutboundTargetPluginRegistryHooks();
|
||||
|
||||
it("rejects whatsapp with empty target even when allowFrom configured", () => {
|
||||
it("rejects empty targets through the loaded channel plugin", () => {
|
||||
const cfg = {
|
||||
channels: { whatsapp: { allowFrom: ["+1555"] } },
|
||||
channels: { alpha: { allowFrom: ["room-one"] } },
|
||||
};
|
||||
const res = resolveOutboundTarget({
|
||||
channel: "whatsapp",
|
||||
channel: "alpha",
|
||||
to: "",
|
||||
cfg,
|
||||
mode: "explicit",
|
||||
});
|
||||
expect(res.ok).toBe(false);
|
||||
if (!res.ok) {
|
||||
expect(res.error.message).toContain("WhatsApp");
|
||||
expect(res.error.message).toContain("Alpha");
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "normalizes whatsapp target when provided",
|
||||
input: { channel: "whatsapp" as const, to: " (555) 123-4567 " },
|
||||
expected: { ok: true as const, to: "+5551234567" },
|
||||
name: "normalizes target through the loaded plugin",
|
||||
input: { channel: "alpha" as const, to: " Alpha:Room One " },
|
||||
expected: { ok: true as const, to: "room-one" },
|
||||
},
|
||||
{
|
||||
name: "keeps whatsapp group targets",
|
||||
input: { channel: "whatsapp" as const, to: "120363401234567890@g.us" },
|
||||
expected: { ok: true as const, to: "120363401234567890@g.us" },
|
||||
},
|
||||
{
|
||||
name: "normalizes prefixed/uppercase whatsapp group targets",
|
||||
name: "uses channel defaultTo when no target was provided",
|
||||
input: {
|
||||
channel: "whatsapp" as const,
|
||||
to: " WhatsApp:120363401234567890@G.US ",
|
||||
},
|
||||
expected: { ok: true as const, to: "120363401234567890@g.us" },
|
||||
},
|
||||
{
|
||||
name: "rejects whatsapp with empty target and allowFrom (no silent fallback)",
|
||||
input: { channel: "whatsapp" as const, to: "", allowFrom: ["+1555"] },
|
||||
expectedErrorIncludes: "WhatsApp",
|
||||
},
|
||||
{
|
||||
name: "rejects whatsapp with empty target and prefixed allowFrom (no silent fallback)",
|
||||
input: {
|
||||
channel: "whatsapp" as const,
|
||||
channel: "beta" as const,
|
||||
to: "",
|
||||
allowFrom: ["whatsapp:(555) 123-4567"],
|
||||
cfg: { channels: { beta: { defaultTo: "Beta:Default Room" } } },
|
||||
},
|
||||
expectedErrorIncludes: "WhatsApp",
|
||||
expected: { ok: true as const, to: "default-room" },
|
||||
},
|
||||
{
|
||||
name: "rejects invalid whatsapp target",
|
||||
input: { channel: "whatsapp" as const, to: "wat" },
|
||||
expectedErrorIncludes: "WhatsApp",
|
||||
name: "passes explicit allowFrom without using it as an implicit target",
|
||||
input: {
|
||||
channel: "alpha" as const,
|
||||
to: "",
|
||||
allowFrom: ["alpha:room-one"],
|
||||
},
|
||||
expectedErrorIncludes: "Alpha",
|
||||
},
|
||||
{
|
||||
name: "rejects whatsapp without to when allowFrom missing",
|
||||
input: { channel: "whatsapp" as const, to: " " },
|
||||
expectedErrorIncludes: "WhatsApp",
|
||||
},
|
||||
{
|
||||
name: "rejects whatsapp allowFrom fallback when invalid",
|
||||
input: { channel: "whatsapp" as const, to: "", allowFrom: ["wat"] },
|
||||
expectedErrorIncludes: "WhatsApp",
|
||||
name: "rejects plugin-specific invalid targets",
|
||||
input: { channel: "alpha" as const, to: "invalid" },
|
||||
expectedErrorIncludes: "Alpha",
|
||||
},
|
||||
])("$name", ({ input, expected, expectedErrorIncludes }) => {
|
||||
const res = resolveOutboundTarget(input);
|
||||
@@ -99,11 +85,28 @@ export function runResolveOutboundTargetCoreTests(): void {
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects telegram with missing target", () => {
|
||||
const res = resolveOutboundTarget({ channel: "telegram", to: " " });
|
||||
it("uses the plugin hint when a channel has outbound support but no target resolver", () => {
|
||||
setActivePluginRegistry(
|
||||
createTargetsTestRegistry([
|
||||
createForumTargetTestPlugin(),
|
||||
createTestChannelPlugin({
|
||||
id: "noresolver",
|
||||
label: "NoResolver",
|
||||
outbound: {
|
||||
deliveryMode: "direct",
|
||||
sendText: async () => ({ channel: "noresolver", messageId: "noresolver-msg" }),
|
||||
},
|
||||
messaging: {
|
||||
targetResolver: { hint: "<test-target>" },
|
||||
},
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const res = resolveOutboundTarget({ channel: "noresolver", to: " " });
|
||||
expect(res.ok).toBe(false);
|
||||
if (!res.ok) {
|
||||
expect(res.error.message).toContain("Telegram");
|
||||
expect(res.error.message).toContain("NoResolver");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -4,9 +4,56 @@ import type {
|
||||
ChannelPlugin,
|
||||
} from "../../channels/plugins/types.public.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
|
||||
import { createTestRegistry } from "../../test-utils/channel-plugins.js";
|
||||
|
||||
function readTestDefaultTo(cfg: OpenClawConfig, channelId: string): string | undefined {
|
||||
const channels = cfg.channels as Record<string, { defaultTo?: unknown }> | undefined;
|
||||
const value = channels?.[channelId]?.defaultTo;
|
||||
return typeof value === "string" ? value : undefined;
|
||||
}
|
||||
|
||||
function stripTestPrefix(raw: string, channelId: string): string {
|
||||
return raw.replace(new RegExp(`^${channelId}:`, "i"), "").trim();
|
||||
}
|
||||
|
||||
function parseForumTargetForTest(raw: string): {
|
||||
roomId: string;
|
||||
threadId?: number;
|
||||
chatType: "direct" | "group" | "unknown";
|
||||
} {
|
||||
const trimmed = stripTestPrefix(raw.trim(), "forum");
|
||||
const topicMatch = /^(.*):topic:(\d+)$/i.exec(trimmed);
|
||||
const roomId = topicMatch?.[1]?.trim() || trimmed;
|
||||
const threadId = topicMatch?.[2] ? Number.parseInt(topicMatch[2], 10) : undefined;
|
||||
const chatType = roomId.startsWith("dm:")
|
||||
? "direct"
|
||||
: roomId.startsWith("room:")
|
||||
? "group"
|
||||
: "unknown";
|
||||
return { roomId, threadId, chatType };
|
||||
}
|
||||
|
||||
function normalizeGenericTargetForTest(raw: string, channelId: string): string | null {
|
||||
const normalized = stripTestPrefix(raw, channelId).toLowerCase().replace(/\s+/gu, "-");
|
||||
if (!normalized || normalized === "invalid") {
|
||||
return null;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function createGenericResolveTarget(
|
||||
channelId: string,
|
||||
label: string,
|
||||
): ChannelOutboundAdapter["resolveTarget"] {
|
||||
return ({ to }) => {
|
||||
const normalized = to ? normalizeGenericTargetForTest(to, channelId) : null;
|
||||
if (!normalized) {
|
||||
return { ok: false, error: new Error(`${label} target is required`) };
|
||||
}
|
||||
return { ok: true, to: normalized };
|
||||
};
|
||||
}
|
||||
|
||||
function parseTelegramTargetForTest(raw: string): {
|
||||
chatId: string;
|
||||
messageThreadId?: number;
|
||||
@@ -27,44 +74,6 @@ function parseTelegramTargetForTest(raw: string): {
|
||||
return { chatId, messageThreadId, chatType };
|
||||
}
|
||||
|
||||
function normalizeWhatsAppTargetForTest(raw: string): string | null {
|
||||
const trimmed = raw
|
||||
.trim()
|
||||
.replace(/^whatsapp:/i, "")
|
||||
.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
const lowered = normalizeLowercaseStringOrEmpty(trimmed);
|
||||
if (lowered.endsWith("@g.us")) {
|
||||
const normalized = lowered.replace(/\s+/gu, "");
|
||||
return /^\d+@g\.us$/u.test(normalized) ? normalized : null;
|
||||
}
|
||||
const digits = trimmed.replace(/\D/gu, "");
|
||||
const normalized = digits ? `+${digits}` : "";
|
||||
return /^\+\d{7,15}$/u.test(normalized) ? normalized : null;
|
||||
}
|
||||
|
||||
function createWhatsAppResolveTarget(label = "WhatsApp"): ChannelOutboundAdapter["resolveTarget"] {
|
||||
return ({ to }) => {
|
||||
const normalized = to ? normalizeWhatsAppTargetForTest(to) : null;
|
||||
if (!normalized) {
|
||||
return { ok: false, error: new Error(`${label} target is required`) };
|
||||
}
|
||||
return { ok: true, to: normalized };
|
||||
};
|
||||
}
|
||||
|
||||
function createTelegramResolveTarget(label = "Telegram"): ChannelOutboundAdapter["resolveTarget"] {
|
||||
return ({ to }) => {
|
||||
const trimmed = to?.trim();
|
||||
if (!trimmed) {
|
||||
return { ok: false, error: new Error(`${label} target is required`) };
|
||||
}
|
||||
return { ok: true, to: parseTelegramTargetForTest(trimmed).chatId };
|
||||
};
|
||||
}
|
||||
|
||||
export const telegramMessagingForTest: ChannelMessagingAdapter = {
|
||||
parseExplicitTarget: ({ raw }) => {
|
||||
const target = parseTelegramTargetForTest(raw);
|
||||
@@ -80,17 +89,23 @@ export const telegramMessagingForTest: ChannelMessagingAdapter = {
|
||||
},
|
||||
};
|
||||
|
||||
export const whatsappMessagingForTest: ChannelMessagingAdapter = {
|
||||
export const forumMessagingForTest: ChannelMessagingAdapter = {
|
||||
parseExplicitTarget: ({ raw }) => {
|
||||
const target = parseForumTargetForTest(raw);
|
||||
return {
|
||||
to: target.roomId,
|
||||
threadId: target.threadId,
|
||||
chatType: target.chatType === "unknown" ? undefined : target.chatType,
|
||||
};
|
||||
},
|
||||
inferTargetChatType: ({ to }) => {
|
||||
const normalized = normalizeWhatsAppTargetForTest(to);
|
||||
if (!normalized) {
|
||||
return undefined;
|
||||
}
|
||||
return normalized.endsWith("@g.us") ? "group" : "direct";
|
||||
const target = parseForumTargetForTest(to);
|
||||
return target.chatType === "unknown" ? undefined : target.chatType;
|
||||
},
|
||||
targetResolver: {
|
||||
hint: "<E.164|group JID>",
|
||||
hint: "<room|dm target>",
|
||||
},
|
||||
preserveHeartbeatThreadIdForGroupRoute: true,
|
||||
};
|
||||
|
||||
export function createTestChannelPlugin(params: {
|
||||
@@ -124,54 +139,42 @@ export function createTestChannelPlugin(params: {
|
||||
};
|
||||
}
|
||||
|
||||
export function createTelegramTestPlugin(): ChannelPlugin {
|
||||
return createTestChannelPlugin({
|
||||
id: "telegram",
|
||||
label: "Telegram",
|
||||
outbound: {
|
||||
deliveryMode: "direct",
|
||||
sendText: async () => ({ channel: "telegram", messageId: "telegram-msg" }),
|
||||
resolveTarget: createTelegramResolveTarget(),
|
||||
},
|
||||
messaging: telegramMessagingForTest,
|
||||
resolveDefaultTo: ({ cfg }) =>
|
||||
typeof cfg.channels?.telegram?.defaultTo === "string"
|
||||
? cfg.channels.telegram.defaultTo
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
export function createWhatsAppTestPlugin(): ChannelPlugin {
|
||||
return createTestChannelPlugin({
|
||||
id: "whatsapp",
|
||||
label: "WhatsApp",
|
||||
outbound: {
|
||||
deliveryMode: "direct",
|
||||
sendText: async () => ({ channel: "whatsapp", messageId: "whatsapp-msg" }),
|
||||
resolveTarget: createWhatsAppResolveTarget(),
|
||||
},
|
||||
messaging: whatsappMessagingForTest,
|
||||
resolveDefaultTo: ({ cfg }) =>
|
||||
typeof cfg.channels?.whatsapp?.defaultTo === "string"
|
||||
? cfg.channels.whatsapp.defaultTo
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
export function createNoopOutboundChannelPlugin(
|
||||
id: "discord" | "imessage" | "slack",
|
||||
export function createGenericTargetTestPlugin(
|
||||
id: ChannelPlugin["id"],
|
||||
label = String(id),
|
||||
): ChannelPlugin {
|
||||
return createTestChannelPlugin({
|
||||
id,
|
||||
label,
|
||||
outbound: {
|
||||
deliveryMode: "direct",
|
||||
sendText: async () => ({ channel: id, messageId: `${id}-msg` }),
|
||||
resolveTarget: createGenericResolveTarget(String(id), label),
|
||||
},
|
||||
resolveDefaultTo: ({ cfg }) => readTestDefaultTo(cfg, String(id)),
|
||||
});
|
||||
}
|
||||
|
||||
export function createForumTargetTestPlugin(): ChannelPlugin {
|
||||
return createTestChannelPlugin({
|
||||
id: "forum",
|
||||
label: "Forum",
|
||||
outbound: {
|
||||
deliveryMode: "direct",
|
||||
sendText: async () => ({ channel: "forum", messageId: "forum-msg" }),
|
||||
resolveTarget: createGenericResolveTarget("forum", "Forum"),
|
||||
},
|
||||
messaging: forumMessagingForTest,
|
||||
resolveDefaultTo: ({ cfg }) => readTestDefaultTo(cfg, "forum"),
|
||||
});
|
||||
}
|
||||
|
||||
export function createTargetsTestRegistry(
|
||||
plugins: ChannelPlugin[] = [createWhatsAppTestPlugin(), createTelegramTestPlugin()],
|
||||
plugins: ChannelPlugin[] = [
|
||||
createGenericTargetTestPlugin("alpha", "Alpha"),
|
||||
createGenericTargetTestPlugin("beta", "Beta"),
|
||||
createForumTargetTestPlugin(),
|
||||
],
|
||||
) {
|
||||
return createTestRegistry(
|
||||
plugins.map((plugin) => ({
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -220,7 +220,8 @@ export function resolveHeartbeatDeliveryTarget(params: {
|
||||
}
|
||||
}
|
||||
|
||||
const inheritedHeartbeatThreadId = shouldReuseHeartbeatTelegramTopicThread({
|
||||
const inheritedHeartbeatThreadId = shouldReuseHeartbeatRouteThreadId({
|
||||
cfg,
|
||||
target,
|
||||
heartbeat,
|
||||
turnSource: params.turnSource,
|
||||
@@ -235,10 +236,8 @@ export function resolveHeartbeatDeliveryTarget(params: {
|
||||
to: resolved.to,
|
||||
reason,
|
||||
accountId: effectiveAccountId,
|
||||
// Heartbeats normally avoid inheriting session reply-thread IDs, but
|
||||
// Telegram forum-topic sessions encode the topic as part of the
|
||||
// destination identity. Preserve that topic routing when the heartbeat is
|
||||
// still targeting the same group session.
|
||||
// Heartbeats normally avoid inheriting session reply-thread IDs, but some
|
||||
// plugins encode thread/topic ids as part of the destination identity.
|
||||
threadId: resolvedTarget.threadId ?? inheritedHeartbeatThreadId,
|
||||
lastChannel: resolvedTarget.lastChannel,
|
||||
lastAccountId: resolvedTarget.lastAccountId,
|
||||
@@ -299,20 +298,24 @@ function resolveHeartbeatDeliveryChatType(params: {
|
||||
});
|
||||
}
|
||||
|
||||
function shouldReuseHeartbeatTelegramTopicThread(params: {
|
||||
function shouldReuseHeartbeatRouteThreadId(params: {
|
||||
cfg: OpenClawConfig;
|
||||
target: HeartbeatTarget;
|
||||
heartbeat?: AgentDefaultsConfig["heartbeat"];
|
||||
turnSource?: DeliveryContext;
|
||||
entry?: SessionEntry;
|
||||
resolvedTarget: SessionDeliveryTarget;
|
||||
}): boolean {
|
||||
const channel = params.resolvedTarget.channel;
|
||||
const messaging =
|
||||
channel && resolveOutboundChannelPlugin({ channel, cfg: params.cfg })?.messaging;
|
||||
return (
|
||||
messaging?.preserveHeartbeatThreadIdForGroupRoute === true &&
|
||||
params.resolvedTarget.threadId == null &&
|
||||
params.target === "last" &&
|
||||
!params.heartbeat?.to &&
|
||||
params.turnSource?.threadId == null &&
|
||||
params.resolvedTarget.channel === "telegram" &&
|
||||
params.resolvedTarget.lastChannel === "telegram" &&
|
||||
params.resolvedTarget.channel === params.resolvedTarget.lastChannel &&
|
||||
Boolean(params.resolvedTarget.to) &&
|
||||
Boolean(params.resolvedTarget.lastTo) &&
|
||||
params.resolvedTarget.to === params.resolvedTarget.lastTo &&
|
||||
|
||||
Reference in New Issue
Block a user