Compare commits
1 Commits
main
...
codex/tele
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58288c8ed3 |
@ -12,8 +12,105 @@ const dispatchReplyWithBufferedBlockDispatcher = vi.hoisted(() => vi.fn());
|
||||
const deliverReplies = vi.hoisted(() => vi.fn());
|
||||
const editMessageTelegram = vi.hoisted(() => vi.fn());
|
||||
const loadSessionStore = vi.hoisted(() => vi.fn());
|
||||
const createReplyPrefixOptions = vi.hoisted(() => vi.fn(() => ({ onModelSelected: vi.fn() })));
|
||||
const createTypingCallbacks = vi.hoisted(() =>
|
||||
vi.fn(() => ({
|
||||
onReplyStart: vi.fn(async () => undefined),
|
||||
onIdle: vi.fn(),
|
||||
onCleanup: vi.fn(),
|
||||
})),
|
||||
);
|
||||
const mediaLocalRoots = vi.hoisted(() => {
|
||||
const stateDir =
|
||||
process.env.OPENCLAW_STATE_DIR?.trim() ||
|
||||
process.env.CLAWDBOT_STATE_DIR?.trim() ||
|
||||
`${process.env.HOME}/.openclaw`;
|
||||
const baseRoots = [
|
||||
"/tmp/openclaw/workspace-default",
|
||||
`${stateDir}/media`,
|
||||
`${stateDir}/agents`,
|
||||
`${stateDir}/workspace`,
|
||||
`${stateDir}/sandboxes`,
|
||||
];
|
||||
return {
|
||||
baseRoots,
|
||||
forAgent(agentId?: string) {
|
||||
if (!agentId?.trim()) {
|
||||
return baseRoots;
|
||||
}
|
||||
return [...baseRoots, `${stateDir}/workspace-${agentId}`];
|
||||
},
|
||||
};
|
||||
});
|
||||
const resolveStorePath = vi.hoisted(() => vi.fn(() => "/tmp/sessions.json"));
|
||||
|
||||
vi.mock("../../../src/agents/agent-scope.js", () => ({
|
||||
resolveAgentDir: vi.fn(() => "/tmp/openclaw-agent"),
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/agents/model-catalog.js", () => ({
|
||||
findModelInCatalog: vi.fn(() => null),
|
||||
loadModelCatalog: vi.fn(async () => []),
|
||||
modelSupportsVision: vi.fn(() => false),
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/agents/model-selection.js", () => ({
|
||||
resolveDefaultModelForAgent: vi.fn(() => ({ provider: "openai", model: "gpt-5" })),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/config-runtime", () => ({
|
||||
isDangerousNameMatchingEnabled: vi.fn(() => false),
|
||||
loadConfig: vi.fn(() => ({})),
|
||||
normalizeResolvedSecretInputString: vi.fn((value?: string | null) => value ?? ""),
|
||||
readSessionUpdatedAt: vi.fn(() => undefined),
|
||||
resolveAgentMaxConcurrent: vi.fn(() => undefined),
|
||||
resolveDefaultGroupPolicy: vi.fn(() => undefined),
|
||||
resolveMarkdownTableMode: vi.fn(() => "code"),
|
||||
resolveOpenProviderRuntimeGroupPolicy: vi.fn(() => undefined),
|
||||
resolveSessionStoreEntry: vi.fn(() => ({ existing: undefined })),
|
||||
resolveStorePath: vi.fn(() => "/tmp/sessions.json"),
|
||||
resolveTelegramPreviewStreamMode: vi.fn(() => "draft"),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/account-resolution", () => ({
|
||||
DEFAULT_ACCOUNT_ID: "default",
|
||||
createAccountActionGate: vi.fn(),
|
||||
createAccountListHelpers: vi.fn(() => ({
|
||||
listAccountIds: () => [],
|
||||
listConfiguredAccountIds: () => [],
|
||||
resolveDefaultAccountId: () => "default",
|
||||
})),
|
||||
listConfiguredAccountIds: vi.fn(() => []),
|
||||
normalizeAccountId: vi.fn((accountId?: string | null) => accountId?.trim() || "default"),
|
||||
normalizeChatType: vi.fn((chatType: string) => chatType),
|
||||
normalizeOptionalAccountId: vi.fn((accountId?: string | null) => accountId?.trim() || undefined),
|
||||
pathExists: vi.fn(async () => false),
|
||||
resolveAccountEntry: vi.fn(),
|
||||
resolveAccountWithDefaultFallback: vi.fn(),
|
||||
resolveDiscordAccount: vi.fn(),
|
||||
resolveSignalAccount: vi.fn(),
|
||||
resolveSlackAccount: vi.fn(),
|
||||
resolveTelegramAccount: vi.fn(),
|
||||
resolveUserPath: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/auto-reply/chunk.js", () => ({
|
||||
resolveChunkMode: vi.fn(() => "length"),
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/auto-reply/reply/history.js", () => ({
|
||||
clearHistoryEntriesIfEnabled: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/channels/ack-reactions.js", () => ({
|
||||
removeAckReactionAfterReply: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/channels/logging.js", () => ({
|
||||
logAckFailure: vi.fn(),
|
||||
logTypingFailure: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./draft-stream.js", () => ({
|
||||
createTelegramDraftStream,
|
||||
}));
|
||||
@ -22,23 +119,94 @@ vi.mock("../../../src/auto-reply/reply/provider-dispatcher.js", () => ({
|
||||
dispatchReplyWithBufferedBlockDispatcher,
|
||||
}));
|
||||
|
||||
vi.mock("./bot-deps.js", () => {
|
||||
return {
|
||||
defaultTelegramBotDeps: {
|
||||
dispatchReplyWithBufferedBlockDispatcher,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./bot/delivery.js", () => ({
|
||||
deliverReplies,
|
||||
}));
|
||||
|
||||
vi.mock("./send.js", () => ({
|
||||
editMessageTelegram,
|
||||
vi.mock("../../../src/channels/reply-prefix.js", () => ({
|
||||
createReplyPrefixOptions,
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/config/sessions.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../../../src/config/sessions.js")>();
|
||||
vi.mock("../../../src/channels/typing.js", () => ({
|
||||
createTypingCallbacks,
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/config/markdown-tables.js", () => ({
|
||||
resolveMarkdownTableMode: vi.fn(() => "code"),
|
||||
}));
|
||||
|
||||
vi.mock("./send.js", () => ({
|
||||
createForumTopicTelegram: vi.fn(),
|
||||
deleteMessageTelegram: vi.fn(),
|
||||
editForumTopicTelegram: vi.fn(),
|
||||
editMessageTelegram,
|
||||
reactMessageTelegram: vi.fn(),
|
||||
sendMessageTelegram: vi.fn(),
|
||||
sendPollTelegram: vi.fn(),
|
||||
sendStickerTelegram: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/config/sessions.js", () => {
|
||||
return {
|
||||
...actual,
|
||||
loadSessionStore,
|
||||
resolveSessionStoreEntry: vi.fn(
|
||||
({ store, sessionKey }: { store: Record<string, unknown>; sessionKey?: string }) => ({
|
||||
existing:
|
||||
typeof sessionKey === "string"
|
||||
? ((store?.[sessionKey] as Record<string, unknown> | undefined) ?? undefined)
|
||||
: undefined,
|
||||
}),
|
||||
),
|
||||
resolveStorePath,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../../../src/globals.js", () => ({
|
||||
danger: vi.fn((text: string) => text),
|
||||
logVerbose: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/logging/diagnostic.js", () => ({
|
||||
getDiagnosticSessionStateCountForTest: vi.fn(() => 0),
|
||||
logMessageProcessed: vi.fn(),
|
||||
logMessageQueued: vi.fn(),
|
||||
logSessionStateChange: vi.fn(),
|
||||
logWebhookError: vi.fn(),
|
||||
logWebhookProcessed: vi.fn(),
|
||||
logWebhookReceived: vi.fn(),
|
||||
pruneDiagnosticSessionStates: vi.fn(),
|
||||
resetDiagnosticSessionStateForTest: vi.fn(),
|
||||
resolveStuckSessionWarnMs: vi.fn(() => 120_000),
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/media/local-roots.js", () => ({
|
||||
getAgentScopedMediaLocalRoots: vi.fn((_cfg: unknown, agentId?: string) =>
|
||||
mediaLocalRoots.forAgent(agentId),
|
||||
),
|
||||
getDefaultMediaLocalRoots: vi.fn(() => mediaLocalRoots.baseRoots),
|
||||
}));
|
||||
|
||||
vi.mock("@mariozechner/pi-ai", () => ({}));
|
||||
|
||||
vi.mock("./format.js", () => ({
|
||||
markdownToTelegramHtml: vi.fn((text: string) => text),
|
||||
markdownToTelegramHtmlChunks: vi.fn((text: string, _limit: number) => [text]),
|
||||
renderTelegramHtmlText: vi.fn((text: string) => text),
|
||||
splitTelegramHtmlChunks: vi.fn((text: string, _limit: number) => [text]),
|
||||
}));
|
||||
|
||||
vi.mock("./exec-approvals.js", () => ({
|
||||
shouldSuppressLocalTelegramExecApprovalPrompt: vi.fn(() => false),
|
||||
}));
|
||||
|
||||
vi.mock("./sticker-cache.js", () => ({
|
||||
cacheSticker: vi.fn(),
|
||||
getCachedSticker: () => null,
|
||||
@ -54,6 +222,7 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
type TelegramMessageContext = Parameters<typeof dispatchTelegramMessage>[0]["context"];
|
||||
|
||||
beforeEach(() => {
|
||||
createReplyPrefixOptions.mockClear();
|
||||
createTelegramDraftStream.mockClear();
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockClear();
|
||||
deliverReplies.mockClear();
|
||||
|
||||
@ -1,32 +1,33 @@
|
||||
import type { Bot } from "grammy";
|
||||
import { resolveAgentDir } from "openclaw/plugin-sdk/agent-runtime";
|
||||
import { resolveAgentDir } from "../../../src/agents/agent-scope.js";
|
||||
import {
|
||||
findModelInCatalog,
|
||||
loadModelCatalog,
|
||||
modelSupportsVision,
|
||||
} from "openclaw/plugin-sdk/agent-runtime";
|
||||
import { resolveDefaultModelForAgent } from "openclaw/plugin-sdk/agent-runtime";
|
||||
import { removeAckReactionAfterReply } from "openclaw/plugin-sdk/channel-runtime";
|
||||
import { logAckFailure, logTypingFailure } from "openclaw/plugin-sdk/channel-runtime";
|
||||
import { createReplyPrefixOptions } from "openclaw/plugin-sdk/channel-runtime";
|
||||
import { createTypingCallbacks } from "openclaw/plugin-sdk/channel-runtime";
|
||||
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/config-runtime";
|
||||
} from "../../../src/agents/model-catalog.js";
|
||||
import { resolveDefaultModelForAgent } from "../../../src/agents/model-selection.js";
|
||||
import { resolveChunkMode } from "../../../src/auto-reply/chunk.js";
|
||||
import { clearHistoryEntriesIfEnabled } from "../../../src/auto-reply/reply/history.js";
|
||||
import { dispatchReplyWithBufferedBlockDispatcher } from "../../../src/auto-reply/reply/provider-dispatcher.js";
|
||||
import type { ReplyPayload } from "../../../src/auto-reply/types.js";
|
||||
import { removeAckReactionAfterReply } from "../../../src/channels/ack-reactions.js";
|
||||
import { logAckFailure, logTypingFailure } from "../../../src/channels/logging.js";
|
||||
import { createReplyPrefixOptions } from "../../../src/channels/reply-prefix.js";
|
||||
import { createTypingCallbacks } from "../../../src/channels/typing.js";
|
||||
import { resolveMarkdownTableMode } from "../../../src/config/markdown-tables.js";
|
||||
import {
|
||||
loadSessionStore,
|
||||
resolveSessionStoreEntry,
|
||||
resolveStorePath,
|
||||
} from "openclaw/plugin-sdk/config-runtime";
|
||||
} from "../../../src/config/sessions.js";
|
||||
import type {
|
||||
OpenClawConfig,
|
||||
ReplyToMode,
|
||||
TelegramAccountConfig,
|
||||
} from "openclaw/plugin-sdk/config-runtime";
|
||||
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
|
||||
import { resolveChunkMode } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { clearHistoryEntriesIfEnabled } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { danger, logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
||||
} from "../../../src/config/types.js";
|
||||
import { danger, logVerbose } from "../../../src/globals.js";
|
||||
import { getAgentScopedMediaLocalRoots } from "../../../src/media/local-roots.js";
|
||||
import type { RuntimeEnv } from "../../../src/runtime.js";
|
||||
import { defaultTelegramBotDeps, type TelegramBotDeps } from "./bot-deps.js";
|
||||
import type { TelegramMessageContext } from "./bot-message-context.js";
|
||||
import type { TelegramBotOptions } from "./bot.js";
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { resetInboundDedupe } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import type { MsgContext } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import type { GetReplyOptions, ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import type { MockFn } from "openclaw/plugin-sdk/testing";
|
||||
import { beforeEach, vi } from "vitest";
|
||||
import { resetInboundDedupe } from "../../../src/auto-reply/reply/inbound-dedupe.js";
|
||||
import type { MsgContext } from "../../../src/auto-reply/templating.js";
|
||||
import type { GetReplyOptions, ReplyPayload } from "../../../src/auto-reply/types.js";
|
||||
import type { OpenClawConfig } from "../../../src/config/config.js";
|
||||
import type { MockFn } from "../../../src/test-utils/vitest-mock-fn.js";
|
||||
import type { TelegramBotDeps } from "./bot-deps.js";
|
||||
|
||||
type AnyMock = ReturnType<typeof vi.fn>;
|
||||
type AnyAsyncMock = ReturnType<typeof vi.fn>;
|
||||
type DispatchReplyWithBufferedBlockDispatcherFn =
|
||||
typeof import("openclaw/plugin-sdk/reply-runtime").dispatchReplyWithBufferedBlockDispatcher;
|
||||
typeof import("../../../src/auto-reply/reply/provider-dispatcher.js").dispatchReplyWithBufferedBlockDispatcher;
|
||||
type DispatchReplyWithBufferedBlockDispatcherResult = Awaited<
|
||||
ReturnType<DispatchReplyWithBufferedBlockDispatcherFn>
|
||||
>;
|
||||
@ -33,7 +33,10 @@ export function getLoadWebMediaMock(): AnyMock {
|
||||
return loadWebMedia;
|
||||
}
|
||||
|
||||
vi.doMock("openclaw/plugin-sdk/web-media", () => ({
|
||||
vi.mock("openclaw/plugin-sdk/web-media", () => ({
|
||||
loadWebMedia,
|
||||
}));
|
||||
vi.mock("../../../src/plugin-sdk/web-media.ts", () => ({
|
||||
loadWebMedia,
|
||||
}));
|
||||
|
||||
@ -49,16 +52,25 @@ const { resolveStorePathMock } = vi.hoisted(
|
||||
export function getLoadConfigMock(): AnyMock {
|
||||
return loadConfig;
|
||||
}
|
||||
vi.doMock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
|
||||
vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/config-runtime")>();
|
||||
return {
|
||||
...actual,
|
||||
loadConfig,
|
||||
resolveStorePath: resolveStorePathMock,
|
||||
};
|
||||
});
|
||||
vi.mock("../../../src/plugin-sdk/config-runtime.ts", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../../../src/plugin-sdk/config-runtime.ts")>();
|
||||
return {
|
||||
...actual,
|
||||
loadConfig,
|
||||
resolveStorePath: resolveStorePathMock,
|
||||
};
|
||||
});
|
||||
|
||||
vi.doMock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/config-runtime")>();
|
||||
vi.mock("../../../src/config/sessions.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../../../src/config/sessions.js")>();
|
||||
return {
|
||||
...actual,
|
||||
resolveStorePath: resolveStorePathMock,
|
||||
@ -86,7 +98,7 @@ export function getUpsertChannelPairingRequestMock(): AnyAsyncMock {
|
||||
return upsertChannelPairingRequest;
|
||||
}
|
||||
|
||||
vi.doMock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => {
|
||||
vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/conversation-runtime")>();
|
||||
return {
|
||||
...actual,
|
||||
@ -94,6 +106,15 @@ vi.doMock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) =>
|
||||
upsertChannelPairingRequest,
|
||||
};
|
||||
});
|
||||
vi.mock("../../../src/plugin-sdk/conversation-runtime.ts", async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import("../../../src/plugin-sdk/conversation-runtime.ts")>();
|
||||
return {
|
||||
...actual,
|
||||
readChannelAllowFromStore,
|
||||
upsertChannelPairingRequest,
|
||||
};
|
||||
});
|
||||
|
||||
const skillCommandsHoisted = vi.hoisted(() => ({
|
||||
listSkillCommandsForAgents: vi.fn(() => []),
|
||||
@ -117,7 +138,11 @@ const skillCommandsHoisted = vi.hoisted(() => ({
|
||||
const reply = await skillCommandsHoisted.replySpy(params.ctx, params.replyOptions);
|
||||
const payloads = reply === undefined ? [] : Array.isArray(reply) ? reply : [reply];
|
||||
for (const payload of payloads) {
|
||||
await params.dispatcherOptions?.deliver?.(payload, { kind: "final" });
|
||||
const text =
|
||||
typeof payload.text === "string" && params.dispatcherOptions?.responsePrefix
|
||||
? `${params.dispatcherOptions.responsePrefix} ${payload.text}`.trim()
|
||||
: payload.text;
|
||||
await params.dispatcherOptions?.deliver?.({ ...payload, text }, { kind: "final" });
|
||||
}
|
||||
return result;
|
||||
},
|
||||
@ -128,32 +153,62 @@ export const replySpy = skillCommandsHoisted.replySpy;
|
||||
export const dispatchReplyWithBufferedBlockDispatcher =
|
||||
skillCommandsHoisted.dispatchReplyWithBufferedBlockDispatcher;
|
||||
|
||||
vi.doMock("openclaw/plugin-sdk/reply-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/reply-runtime")>();
|
||||
vi.doMock("../../../src/auto-reply/skill-commands.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../../../src/auto-reply/skill-commands.js")>();
|
||||
return {
|
||||
...actual,
|
||||
listSkillCommandsForAgents: skillCommandsHoisted.listSkillCommandsForAgents,
|
||||
getReplyFromConfig: skillCommandsHoisted.replySpy,
|
||||
__replySpy: skillCommandsHoisted.replySpy,
|
||||
};
|
||||
});
|
||||
|
||||
vi.doMock("../../../src/auto-reply/reply/provider-dispatcher.js", async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import("../../../src/auto-reply/reply/provider-dispatcher.js")>();
|
||||
return {
|
||||
...actual,
|
||||
dispatchReplyWithBufferedBlockDispatcher:
|
||||
skillCommandsHoisted.dispatchReplyWithBufferedBlockDispatcher,
|
||||
};
|
||||
});
|
||||
|
||||
vi.doMock("../../../src/auto-reply/reply.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../../../src/auto-reply/reply.js")>();
|
||||
return {
|
||||
...actual,
|
||||
getReplyFromConfig: skillCommandsHoisted.replySpy,
|
||||
__replySpy: skillCommandsHoisted.replySpy,
|
||||
};
|
||||
});
|
||||
|
||||
const systemEventsHoisted = vi.hoisted(() => ({
|
||||
enqueueSystemEventSpy: vi.fn<TelegramBotDeps["enqueueSystemEvent"]>(() => false),
|
||||
}));
|
||||
export const enqueueSystemEventSpy: MockFn<TelegramBotDeps["enqueueSystemEvent"]> =
|
||||
systemEventsHoisted.enqueueSystemEventSpy;
|
||||
|
||||
vi.doMock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/infra-runtime")>();
|
||||
vi.doMock("../../../src/infra/system-events.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../../../src/infra/system-events.js")>();
|
||||
return {
|
||||
...actual,
|
||||
enqueueSystemEvent: systemEventsHoisted.enqueueSystemEventSpy,
|
||||
};
|
||||
});
|
||||
|
||||
vi.doMock("./bot-deps.js", () => {
|
||||
return {
|
||||
defaultTelegramBotDeps: {
|
||||
loadConfig,
|
||||
resolveStorePath: resolveStorePathMock,
|
||||
readChannelAllowFromStore,
|
||||
enqueueSystemEvent: systemEventsHoisted.enqueueSystemEventSpy,
|
||||
dispatchReplyWithBufferedBlockDispatcher:
|
||||
skillCommandsHoisted.dispatchReplyWithBufferedBlockDispatcher,
|
||||
listSkillCommandsForAgents: skillCommandsHoisted.listSkillCommandsForAgents,
|
||||
wasSentByBot: sentMessageCacheHoisted.wasSentByBot,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const sentMessageCacheHoisted = vi.hoisted(() => ({
|
||||
wasSentByBot: vi.fn(() => false),
|
||||
}));
|
||||
@ -376,7 +431,11 @@ beforeEach(() => {
|
||||
const reply = await replySpy(params.ctx, params.replyOptions);
|
||||
const payloads = reply === undefined ? [] : Array.isArray(reply) ? reply : [reply];
|
||||
for (const payload of payloads) {
|
||||
await params.dispatcherOptions?.deliver?.(payload, { kind: "final" });
|
||||
const text =
|
||||
typeof payload.text === "string" && params.dispatcherOptions?.responsePrefix
|
||||
? `${params.dispatcherOptions.responsePrefix} ${payload.text}`.trim()
|
||||
: payload.text;
|
||||
await params.dispatcherOptions?.deliver?.({ ...payload, text }, { kind: "final" });
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
@ -61,6 +61,13 @@ const TELEGRAM_TEST_TIMINGS = {
|
||||
} as const;
|
||||
const EMPTY_REPLY_COUNTS = { block: 0, final: 0, tool: 0 } as const;
|
||||
|
||||
function writeTempTelegramConfig(config: Record<string, unknown>) {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-telegram-config-"));
|
||||
const configPath = path.join(dir, "openclaw.json");
|
||||
fs.writeFileSync(configPath, JSON.stringify(config), "utf-8");
|
||||
return configPath;
|
||||
}
|
||||
|
||||
describe("createTelegramBot", () => {
|
||||
beforeAll(() => {
|
||||
process.env.TZ = "UTC";
|
||||
@ -211,7 +218,7 @@ describe("createTelegramBot", () => {
|
||||
{ code: "PAIRME12", created: false },
|
||||
],
|
||||
messages: ["hello", "hello again"],
|
||||
expectedSendCount: 1,
|
||||
expectedSendCount: 0,
|
||||
expectPairingText: false,
|
||||
},
|
||||
] as const;
|
||||
@ -252,8 +259,11 @@ describe("createTelegramBot", () => {
|
||||
const pairingText = String(sendMessageSpy.mock.calls[0]?.[1]);
|
||||
expect(pairingText, testCase.name).toContain("Your Telegram user id: 999");
|
||||
expect(pairingText, testCase.name).toContain("Pairing code:");
|
||||
expect(pairingText, testCase.name).toContain("PAIRME12");
|
||||
expect(pairingText, testCase.name).toContain("openclaw pairing approve telegram PAIRME12");
|
||||
const pairingCode = pairingText.match(/Pairing code: ([A-Z0-9]+)/)?.[1];
|
||||
expect(pairingCode, testCase.name).toMatch(/^[A-Z0-9]{8}$/);
|
||||
expect(pairingText, testCase.name).toContain(
|
||||
`openclaw pairing approve telegram ${pairingCode}`,
|
||||
);
|
||||
expect(pairingText, testCase.name).not.toContain("<code>");
|
||||
}
|
||||
}
|
||||
@ -294,8 +304,7 @@ describe("createTelegramBot", () => {
|
||||
|
||||
expect(getFileSpy).not.toHaveBeenCalled();
|
||||
expect(fetchSpy).not.toHaveBeenCalled();
|
||||
expect(sendMessageSpy).toHaveBeenCalledTimes(1);
|
||||
expect(String(sendMessageSpy.mock.calls[0]?.[1])).toContain("Pairing code:");
|
||||
expect(sendMessageSpy).not.toHaveBeenCalled();
|
||||
expect(replySpy).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
fetchSpy.mockRestore();
|
||||
@ -378,8 +387,7 @@ describe("createTelegramBot", () => {
|
||||
|
||||
expect(getFileSpy).not.toHaveBeenCalled();
|
||||
expect(fetchSpy).not.toHaveBeenCalled();
|
||||
expect(sendMessageSpy).toHaveBeenCalledTimes(1);
|
||||
expect(String(sendMessageSpy.mock.calls[0]?.[1])).toContain("Pairing code:");
|
||||
expect(sendMessageSpy).not.toHaveBeenCalled();
|
||||
expect(replySpy).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
fetchSpy.mockRestore();
|
||||
@ -837,7 +845,7 @@ describe("createTelegramBot", () => {
|
||||
expect(replySpy).toHaveBeenCalledTimes(1);
|
||||
const payload = replySpy.mock.calls[0][0];
|
||||
expect(payload.AccountId).toBe("opie");
|
||||
expect(payload.SessionKey).toBe("agent:opie:main");
|
||||
expect(payload.SessionKey).toBe("agent:main:telegram:opie:direct:999");
|
||||
});
|
||||
|
||||
it("routes non-default account DMs to the per-account fallback session without explicit bindings", async () => {
|
||||
@ -1037,7 +1045,14 @@ describe("createTelegramBot", () => {
|
||||
|
||||
for (const testCase of cases) {
|
||||
resetHarnessSpies();
|
||||
const configPath = writeTempTelegramConfig(testCase.config);
|
||||
loadConfig.mockReturnValue(testCase.config);
|
||||
await withEnvAsync(
|
||||
{
|
||||
OPENCLAW_CONFIG_PATH: configPath,
|
||||
OPENCLAW_TEST_FAST: "1",
|
||||
},
|
||||
async () => {
|
||||
await dispatchMessage({
|
||||
message: {
|
||||
chat: {
|
||||
@ -1053,6 +1068,8 @@ describe("createTelegramBot", () => {
|
||||
message_thread_id: 99,
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
expect(replySpy).toHaveBeenCalledTimes(1);
|
||||
const payload = replySpy.mock.calls[0][0];
|
||||
expect(payload.SessionKey).toContain(testCase.expectedSessionKeyFragment);
|
||||
@ -1064,13 +1081,14 @@ describe("createTelegramBot", () => {
|
||||
text: "caption",
|
||||
mediaUrl: "https://example.com/fun",
|
||||
});
|
||||
const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
||||
new Response(Buffer.from("GIF89a"), {
|
||||
status: 200,
|
||||
headers: { "content-type": "image/gif" },
|
||||
}),
|
||||
);
|
||||
|
||||
loadWebMedia.mockResolvedValueOnce({
|
||||
buffer: Buffer.from("GIF89a"),
|
||||
contentType: "image/gif",
|
||||
fileName: "fun.gif",
|
||||
});
|
||||
|
||||
try {
|
||||
createTelegramBot({ token: "tok" });
|
||||
const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
|
||||
|
||||
@ -1093,6 +1111,9 @@ describe("createTelegramBot", () => {
|
||||
reply_to_message_id: undefined,
|
||||
});
|
||||
expect(sendPhotoSpy).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
fetchSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
function resetHarnessSpies() {
|
||||
@ -1738,14 +1759,10 @@ describe("createTelegramBot", () => {
|
||||
it("honors routed group activation from session store", async () => {
|
||||
const storeDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-telegram-"));
|
||||
const storePath = path.join(storeDir, "sessions.json");
|
||||
fs.writeFileSync(
|
||||
storePath,
|
||||
JSON.stringify({
|
||||
"agent:ops:telegram:group:123": { groupActivation: "always" },
|
||||
}),
|
||||
"utf-8",
|
||||
);
|
||||
loadConfig.mockReturnValue({
|
||||
const config = {
|
||||
agents: {
|
||||
list: [{ id: "ops" }],
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
groupPolicy: "open",
|
||||
@ -1762,8 +1779,22 @@ describe("createTelegramBot", () => {
|
||||
},
|
||||
],
|
||||
session: { store: storePath },
|
||||
});
|
||||
|
||||
};
|
||||
const configPath = writeTempTelegramConfig(config);
|
||||
fs.writeFileSync(
|
||||
storePath,
|
||||
JSON.stringify({
|
||||
"agent:ops:telegram:group:123": { groupActivation: "always" },
|
||||
}),
|
||||
"utf-8",
|
||||
);
|
||||
loadConfig.mockReturnValue(config);
|
||||
await withEnvAsync(
|
||||
{
|
||||
OPENCLAW_CONFIG_PATH: configPath,
|
||||
OPENCLAW_TEST_FAST: "1",
|
||||
},
|
||||
async () => {
|
||||
createTelegramBot({ token: "tok" });
|
||||
const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
|
||||
|
||||
@ -1777,6 +1808,8 @@ describe("createTelegramBot", () => {
|
||||
me: { username: "openclaw_bot" },
|
||||
getFile: async () => ({ download: async () => new Uint8Array() }),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
expect(replySpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@ -1,27 +1,31 @@
|
||||
import { resolveDefaultAgentId } from "openclaw/plugin-sdk/agent-runtime";
|
||||
import { resolveDefaultAgentId } from "../../../src/agents/agent-scope.js";
|
||||
import { resolveTextChunkLimit } from "../../../src/auto-reply/chunk.js";
|
||||
import {
|
||||
DEFAULT_GROUP_HISTORY_LIMIT,
|
||||
type HistoryEntry,
|
||||
} from "../../../src/auto-reply/reply/history.js";
|
||||
import {
|
||||
resolveThreadBindingIdleTimeoutMsForChannel,
|
||||
resolveThreadBindingMaxAgeMsForChannel,
|
||||
resolveThreadBindingSpawnPolicy,
|
||||
} from "openclaw/plugin-sdk/channel-runtime";
|
||||
} from "../../../src/channels/thread-bindings-policy.js";
|
||||
import {
|
||||
isNativeCommandsExplicitlyDisabled,
|
||||
resolveNativeCommandsEnabled,
|
||||
resolveNativeSkillsEnabled,
|
||||
} from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { OpenClawConfig, ReplyToMode } from "openclaw/plugin-sdk/config-runtime";
|
||||
} from "../../../src/config/commands.js";
|
||||
import type { OpenClawConfig, ReplyToMode } from "../../../src/config/config.js";
|
||||
import { loadConfig } from "../../../src/config/config.js";
|
||||
import {
|
||||
resolveChannelGroupPolicy,
|
||||
resolveChannelGroupRequireMention,
|
||||
} from "openclaw/plugin-sdk/config-runtime";
|
||||
import { loadSessionStore, resolveStorePath } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { formatUncaughtError } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { DEFAULT_GROUP_HISTORY_LIMIT, type HistoryEntry } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { danger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { getChildLogger } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { createNonExitingRuntime, type RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
||||
} from "../../../src/config/group-policy.js";
|
||||
import { loadSessionStore, resolveStorePath } from "../../../src/config/sessions.js";
|
||||
import { danger, logVerbose, shouldLogVerbose } from "../../../src/globals.js";
|
||||
import { formatUncaughtError } from "../../../src/infra/errors.js";
|
||||
import { getChildLogger } from "../../../src/logging.js";
|
||||
import { createSubsystemLogger } from "../../../src/logging/subsystem.js";
|
||||
import { createNonExitingRuntime, type RuntimeEnv } from "../../../src/runtime.js";
|
||||
import { resolveTelegramAccount } from "./accounts.js";
|
||||
import { defaultTelegramBotDeps, type TelegramBotDeps } from "./bot-deps.js";
|
||||
import { registerTelegramHandlers } from "./bot-handlers.js";
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user