




















@@ -1,7 +1,10 @@
11import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
22import type { ChannelOutboundAdapter } from "../../channels/plugins/types.js";
33import type { OpenClawConfig } from "../../config/config.js";
4-import { forumMessagingForTest } from "../../infra/outbound/targets.test-helpers.js";
4+import {
5+forumMessagingForTest,
6+telegramMessagingForTest,
7+} from "../../infra/outbound/targets.test-helpers.js";
58import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../../plugins/runtime.js";
69import { createOutboundTestPlugin, createTestRegistry } from "../../test-utils/channel-plugins.js";
710@@ -86,8 +89,20 @@ function createAllowlistAwareStubOutbound(label: string): ChannelOutboundAdapter
8689};
8790}
889192+const normalizeTelegramTargetForDeliveryTest = vi.fn((raw: string): string | undefined => {
93+const target = telegramMessagingForTest.parseExplicitTarget?.({ raw });
94+if (!target?.to) {
95+return undefined;
96+}
97+const normalizedTo = target.to.toLowerCase();
98+return target.threadId == null
99+ ? `telegram:${normalizedTo}`
100+ : `telegram:${normalizedTo}:topic:${target.threadId}`;
101+});
102+89103beforeEach(() => {
90104resetPluginRuntimeStateForTest();
105+normalizeTelegramTargetForDeliveryTest.mockClear();
91106vi.mocked(resolveOutboundTarget).mockReset();
92107setActivePluginRegistry(
93108createTestRegistry([
@@ -100,6 +115,18 @@ beforeEach(() => {
100115}),
101116source: "test",
102117},
118+{
119+pluginId: "telegram",
120+plugin: createOutboundTestPlugin({
121+id: "telegram",
122+outbound: createStubOutbound("Telegram"),
123+messaging: {
124+ ...telegramMessagingForTest,
125+normalizeTarget: normalizeTelegramTargetForDeliveryTest,
126+},
127+}),
128+source: "test",
129+},
103130{
104131pluginId: "alpha",
105132plugin: {
@@ -421,6 +448,38 @@ describe("resolveDeliveryTarget", () => {
421448);
422449});
423450451+it("returns an unresolved target when loaded target resolution throws", async () => {
452+setMainSessionEntry(undefined);
453+setActivePluginRegistry(
454+createTestRegistry([
455+{
456+pluginId: "alpha",
457+plugin: createOutboundTestPlugin({
458+id: "alpha",
459+outbound: {
460+deliveryMode: "gateway",
461+resolveTarget: () => {
462+throw new Error("target normalizer exploded");
463+},
464+},
465+}),
466+source: "test",
467+},
468+]),
469+);
470+471+const result = await resolveDeliveryTarget(makeCfg({ bindings: [] }), AGENT_ID, {
472+channel: "alpha",
473+to: "room:default",
474+});
475+476+expect(result.ok).toBe(false);
477+if (result.ok) {
478+throw new Error("expected invalid delivery target");
479+}
480+expect(result.error.message).toContain("Invalid delivery target: target normalizer exploded");
481+});
482+424483it("selects correct binding when multiple agents have bindings", async () => {
425484setMainSessionEntry(undefined);
426485@@ -483,6 +542,66 @@ describe("resolveDeliveryTarget", () => {
483542expect(result.threadId).toBe("thread-2");
484543});
485544545+it("keeps a session Telegram topic threadId when a bare explicit target matches the topic route", async () => {
546+setLastSessionEntry({
547+sessionId: "sess-telegram-topic",
548+lastChannel: "telegram",
549+lastTo: "-100200300:topic:77",
550+lastThreadId: "77",
551+});
552+normalizeTelegramTargetForDeliveryTest.mockClear();
553+554+const result = await resolveDeliveryTarget(makeCfg({ bindings: [] }), AGENT_ID, {
555+channel: "telegram",
556+to: "-100200300",
557+});
558+559+expect(result.ok).toBe(true);
560+expect(result.to).toBe("-100200300");
561+expect(result.threadId).toBe(77);
562+expect(normalizeTelegramTargetForDeliveryTest).toHaveBeenCalledWith("-100200300");
563+expect(normalizeTelegramTargetForDeliveryTest).toHaveBeenCalledWith("-100200300:topic:77");
564+});
565+566+it("drops carried threadId instead of throwing when target normalization fails", async () => {
567+setLastSessionEntry({
568+sessionId: "sess-telegram-topic-invalid",
569+lastChannel: "telegram",
570+lastTo: "-100200300:topic:77",
571+lastThreadId: "77",
572+});
573+normalizeTelegramTargetForDeliveryTest.mockImplementationOnce(() => {
574+throw new Error("target normalizer exploded");
575+});
576+577+const result = await resolveDeliveryTarget(makeCfg({ bindings: [] }), AGENT_ID, {
578+channel: "telegram",
579+to: "-100200300",
580+});
581+582+expect(result.ok).toBe(true);
583+expect(result.to).toBe("-100200300");
584+expect(result.threadId).toBeUndefined();
585+});
586+587+it("drops a session Telegram topic threadId when a bare explicit target names a different chat", async () => {
588+setLastSessionEntry({
589+sessionId: "sess-telegram-topic-stale",
590+lastChannel: "telegram",
591+lastTo: "-100200300:topic:77",
592+lastThreadId: "77",
593+});
594+595+const result = await resolveDeliveryTarget(makeCfg({ bindings: [] }), AGENT_ID, {
596+channel: "telegram",
597+to: "-100999999",
598+});
599+600+expect(result.ok).toBe(true);
601+expect(result.to).toBe("-100999999");
602+expect(result.threadId).toBeUndefined();
603+});
604+486605it("uses single configured channel when neither explicit nor session channel exists", async () => {
487606setMainSessionEntry(undefined);
488607此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。