mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-29 18:12:52 +00:00
refactor: move Telegram channel implementation to extensions/ (#45635)
* refactor: move Telegram channel implementation to extensions/telegram/src/ Move all Telegram channel code (123 files + 10 bot/ files + 8 channel plugin files) from src/telegram/ and src/channels/plugins/*/telegram.ts to extensions/telegram/src/. Leave thin re-export shims at original locations so cross-cutting src/ imports continue to resolve. - Fix all relative import paths in moved files (../X/ -> ../../../src/X/) - Fix vi.mock paths in 60 test files - Fix inline typeof import() expressions - Update tsconfig.plugin-sdk.dts.json rootDir to "." for cross-directory DTS - Update write-plugin-sdk-entry-dts.ts for new rootDir structure - Move channel plugin files with correct path remapping * fix: support keyed telegram send deps * fix: sync telegram extension copies with latest main * fix: correct import paths and remove misplaced files in telegram extension * fix: sync outbound-adapter with main (add sendTelegramPayloadMessages) and fix delivery.test import path
This commit is contained in:
214
extensions/telegram/src/thread-bindings.test.ts
Normal file
214
extensions/telegram/src/thread-bindings.test.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { resolveStateDir } from "../../../src/config/paths.js";
|
||||
import { getSessionBindingService } from "../../../src/infra/outbound/session-binding-service.js";
|
||||
import { importFreshModule } from "../../../test/helpers/import-fresh.js";
|
||||
import {
|
||||
__testing,
|
||||
createTelegramThreadBindingManager,
|
||||
setTelegramThreadBindingIdleTimeoutBySessionKey,
|
||||
setTelegramThreadBindingMaxAgeBySessionKey,
|
||||
} from "./thread-bindings.js";
|
||||
|
||||
describe("telegram thread bindings", () => {
|
||||
let stateDirOverride: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
__testing.resetTelegramThreadBindingsForTests();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
if (stateDirOverride) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
fs.rmSync(stateDirOverride, { recursive: true, force: true });
|
||||
stateDirOverride = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
it("registers a telegram binding adapter and binds current conversations", async () => {
|
||||
const manager = createTelegramThreadBindingManager({
|
||||
accountId: "work",
|
||||
persist: false,
|
||||
enableSweeper: false,
|
||||
idleTimeoutMs: 30_000,
|
||||
maxAgeMs: 0,
|
||||
});
|
||||
const bound = await getSessionBindingService().bind({
|
||||
targetSessionKey: "agent:main:subagent:child-1",
|
||||
targetKind: "subagent",
|
||||
conversation: {
|
||||
channel: "telegram",
|
||||
accountId: "work",
|
||||
conversationId: "-100200300:topic:77",
|
||||
},
|
||||
placement: "current",
|
||||
metadata: {
|
||||
boundBy: "user-1",
|
||||
},
|
||||
});
|
||||
|
||||
expect(bound.conversation.channel).toBe("telegram");
|
||||
expect(bound.conversation.accountId).toBe("work");
|
||||
expect(bound.conversation.conversationId).toBe("-100200300:topic:77");
|
||||
expect(bound.targetSessionKey).toBe("agent:main:subagent:child-1");
|
||||
expect(manager.getByConversationId("-100200300:topic:77")?.boundBy).toBe("user-1");
|
||||
});
|
||||
|
||||
it("does not support child placement", async () => {
|
||||
createTelegramThreadBindingManager({
|
||||
accountId: "default",
|
||||
persist: false,
|
||||
enableSweeper: false,
|
||||
});
|
||||
|
||||
await expect(
|
||||
getSessionBindingService().bind({
|
||||
targetSessionKey: "agent:main:subagent:child-1",
|
||||
targetKind: "subagent",
|
||||
conversation: {
|
||||
channel: "telegram",
|
||||
accountId: "default",
|
||||
conversationId: "-100200300:topic:77",
|
||||
},
|
||||
placement: "child",
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "BINDING_CAPABILITY_UNSUPPORTED",
|
||||
});
|
||||
});
|
||||
|
||||
it("shares binding state across distinct module instances", async () => {
|
||||
const bindingsA = await importFreshModule<typeof import("./thread-bindings.js")>(
|
||||
import.meta.url,
|
||||
"./thread-bindings.js?scope=shared-a",
|
||||
);
|
||||
const bindingsB = await importFreshModule<typeof import("./thread-bindings.js")>(
|
||||
import.meta.url,
|
||||
"./thread-bindings.js?scope=shared-b",
|
||||
);
|
||||
|
||||
bindingsA.__testing.resetTelegramThreadBindingsForTests();
|
||||
|
||||
try {
|
||||
const managerA = bindingsA.createTelegramThreadBindingManager({
|
||||
accountId: "shared-runtime",
|
||||
persist: false,
|
||||
enableSweeper: false,
|
||||
});
|
||||
const managerB = bindingsB.createTelegramThreadBindingManager({
|
||||
accountId: "shared-runtime",
|
||||
persist: false,
|
||||
enableSweeper: false,
|
||||
});
|
||||
|
||||
expect(managerB).toBe(managerA);
|
||||
|
||||
await getSessionBindingService().bind({
|
||||
targetSessionKey: "agent:main:subagent:child-shared",
|
||||
targetKind: "subagent",
|
||||
conversation: {
|
||||
channel: "telegram",
|
||||
accountId: "shared-runtime",
|
||||
conversationId: "-100200300:topic:44",
|
||||
},
|
||||
placement: "current",
|
||||
});
|
||||
|
||||
expect(
|
||||
bindingsB
|
||||
.getTelegramThreadBindingManager("shared-runtime")
|
||||
?.getByConversationId("-100200300:topic:44")?.targetSessionKey,
|
||||
).toBe("agent:main:subagent:child-shared");
|
||||
} finally {
|
||||
bindingsA.__testing.resetTelegramThreadBindingsForTests();
|
||||
}
|
||||
});
|
||||
|
||||
it("updates lifecycle windows by session key", async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2026-03-06T10:00:00.000Z"));
|
||||
const manager = createTelegramThreadBindingManager({
|
||||
accountId: "work",
|
||||
persist: false,
|
||||
enableSweeper: false,
|
||||
});
|
||||
|
||||
await getSessionBindingService().bind({
|
||||
targetSessionKey: "agent:main:subagent:child-1",
|
||||
targetKind: "subagent",
|
||||
conversation: {
|
||||
channel: "telegram",
|
||||
accountId: "work",
|
||||
conversationId: "1234",
|
||||
},
|
||||
});
|
||||
const original = manager.listBySessionKey("agent:main:subagent:child-1")[0];
|
||||
expect(original).toBeDefined();
|
||||
|
||||
const idleUpdated = setTelegramThreadBindingIdleTimeoutBySessionKey({
|
||||
accountId: "work",
|
||||
targetSessionKey: "agent:main:subagent:child-1",
|
||||
idleTimeoutMs: 2 * 60 * 60 * 1000,
|
||||
});
|
||||
vi.setSystemTime(new Date("2026-03-06T12:00:00.000Z"));
|
||||
const maxAgeUpdated = setTelegramThreadBindingMaxAgeBySessionKey({
|
||||
accountId: "work",
|
||||
targetSessionKey: "agent:main:subagent:child-1",
|
||||
maxAgeMs: 6 * 60 * 60 * 1000,
|
||||
});
|
||||
|
||||
expect(idleUpdated).toHaveLength(1);
|
||||
expect(idleUpdated[0]?.idleTimeoutMs).toBe(2 * 60 * 60 * 1000);
|
||||
expect(maxAgeUpdated).toHaveLength(1);
|
||||
expect(maxAgeUpdated[0]?.maxAgeMs).toBe(6 * 60 * 60 * 1000);
|
||||
expect(maxAgeUpdated[0]?.boundAt).toBe(original?.boundAt);
|
||||
expect(maxAgeUpdated[0]?.lastActivityAt).toBe(Date.parse("2026-03-06T12:00:00.000Z"));
|
||||
expect(manager.listBySessionKey("agent:main:subagent:child-1")[0]?.maxAgeMs).toBe(
|
||||
6 * 60 * 60 * 1000,
|
||||
);
|
||||
});
|
||||
|
||||
it("does not persist lifecycle updates when manager persistence is disabled", async () => {
|
||||
stateDirOverride = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-telegram-bindings-"));
|
||||
process.env.OPENCLAW_STATE_DIR = stateDirOverride;
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2026-03-06T10:00:00.000Z"));
|
||||
|
||||
createTelegramThreadBindingManager({
|
||||
accountId: "no-persist",
|
||||
persist: false,
|
||||
enableSweeper: false,
|
||||
});
|
||||
|
||||
await getSessionBindingService().bind({
|
||||
targetSessionKey: "agent:main:subagent:child-2",
|
||||
targetKind: "subagent",
|
||||
conversation: {
|
||||
channel: "telegram",
|
||||
accountId: "no-persist",
|
||||
conversationId: "-100200300:topic:88",
|
||||
},
|
||||
});
|
||||
|
||||
setTelegramThreadBindingIdleTimeoutBySessionKey({
|
||||
accountId: "no-persist",
|
||||
targetSessionKey: "agent:main:subagent:child-2",
|
||||
idleTimeoutMs: 60 * 60 * 1000,
|
||||
});
|
||||
setTelegramThreadBindingMaxAgeBySessionKey({
|
||||
accountId: "no-persist",
|
||||
targetSessionKey: "agent:main:subagent:child-2",
|
||||
maxAgeMs: 2 * 60 * 60 * 1000,
|
||||
});
|
||||
|
||||
const statePath = path.join(
|
||||
resolveStateDir(process.env, os.homedir),
|
||||
"telegram",
|
||||
"thread-bindings-no-persist.json",
|
||||
);
|
||||
expect(fs.existsSync(statePath)).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user