mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
220 lines
7 KiB
TypeScript
220 lines
7 KiB
TypeScript
// Telegram bot-token runtime evidence starts the real monitor through getMe.
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
|
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
|
import { monitorTelegramProvider } from "../../../../extensions/telegram/runtime-api.js";
|
|
import { formatErrorMessage } from "../../../../src/infra/errors.js";
|
|
import { createQaScriptEvidenceWriter } from "./script-evidence.js";
|
|
|
|
const STARTUP_TIMEOUT_MS = 30_000;
|
|
const TOKEN_ENV_KEYS = [
|
|
"OPENCLAW_QA_TELEGRAM_SUT_BOT_TOKEN",
|
|
"TELEGRAM_E2E_SUT_BOT_TOKEN",
|
|
] as const;
|
|
|
|
type TelegramRuntimeOptions = {
|
|
artifactBase: string;
|
|
repoRoot: string;
|
|
startupTimeoutMs: number;
|
|
};
|
|
|
|
async function waitForMonitorShutdown(monitorPromise: Promise<void>, timeoutMs: number) {
|
|
let timeout: NodeJS.Timeout | undefined;
|
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
timeout = setTimeout(
|
|
() => reject(new Error("Telegram runtime shutdown timed out")),
|
|
timeoutMs,
|
|
);
|
|
});
|
|
try {
|
|
await Promise.race([monitorPromise.catch(() => undefined), timeoutPromise]);
|
|
} finally {
|
|
if (timeout) {
|
|
clearTimeout(timeout);
|
|
}
|
|
}
|
|
}
|
|
|
|
function parseOptions(argv: string[], repoRoot = process.cwd()): TelegramRuntimeOptions {
|
|
let artifactBase = path.join(repoRoot, ".artifacts", "qa-e2e", "telegram-bot-token");
|
|
let startupTimeoutMs = STARTUP_TIMEOUT_MS;
|
|
for (let index = 0; index < argv.length; index += 1) {
|
|
const arg = argv[index];
|
|
if (arg === "--output-dir") {
|
|
artifactBase = path.resolve(repoRoot, argv[++index] ?? "");
|
|
continue;
|
|
}
|
|
if (arg === "--timeout-ms") {
|
|
startupTimeoutMs = Number(argv[++index]);
|
|
continue;
|
|
}
|
|
if (arg === "--") {
|
|
continue;
|
|
}
|
|
throw new Error(`Unknown argument: ${arg}`);
|
|
}
|
|
if (!Number.isFinite(startupTimeoutMs) || startupTimeoutMs <= 0) {
|
|
throw new Error("--timeout-ms must be a positive number");
|
|
}
|
|
return { artifactBase, repoRoot, startupTimeoutMs };
|
|
}
|
|
|
|
function resolveLeasedToken(env: NodeJS.ProcessEnv = process.env) {
|
|
for (const key of TOKEN_ENV_KEYS) {
|
|
const token = env[key]?.trim();
|
|
if (token) {
|
|
return { key, token };
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function createWriter(options: TelegramRuntimeOptions) {
|
|
return createQaScriptEvidenceWriter({
|
|
artifactBase: options.artifactBase,
|
|
logFileName: "telegram-bot-token.log",
|
|
primaryModel: "telegram/bot-api",
|
|
providerMode: "live-frontier",
|
|
repoRoot: options.repoRoot,
|
|
target: {
|
|
id: "telegram-bot-token",
|
|
title: "Telegram bot token runtime startup",
|
|
sourcePath: "test/e2e/qa-lab/runtime/telegram-bot-token-runtime.ts",
|
|
primaryCoverageIds: ["telegram.startup-getme"],
|
|
docsRefs: ["docs/channels/telegram.md"],
|
|
codeRefs: [
|
|
"test/e2e/qa-lab/runtime/telegram-bot-token-runtime.ts",
|
|
"extensions/telegram/src/monitor.ts",
|
|
"extensions/telegram/src/polling-session.ts",
|
|
],
|
|
},
|
|
});
|
|
}
|
|
|
|
async function waitForStartup(params: {
|
|
abortController: AbortController;
|
|
monitorPromise: Promise<void>;
|
|
startupPromise: Promise<void>;
|
|
timeoutMs: number;
|
|
}) {
|
|
let timeout: NodeJS.Timeout | undefined;
|
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
timeout = setTimeout(
|
|
() => reject(new Error("Telegram runtime startup timed out")),
|
|
params.timeoutMs,
|
|
);
|
|
});
|
|
try {
|
|
await Promise.race([
|
|
params.startupPromise,
|
|
params.monitorPromise.then(() => {
|
|
throw new Error("Telegram runtime stopped before polling startup");
|
|
}),
|
|
timeoutPromise,
|
|
]);
|
|
} finally {
|
|
if (timeout) {
|
|
clearTimeout(timeout);
|
|
}
|
|
params.abortController.abort();
|
|
await waitForMonitorShutdown(params.monitorPromise, params.timeoutMs);
|
|
}
|
|
}
|
|
|
|
export async function runTelegramBotTokenRuntime(
|
|
options: TelegramRuntimeOptions,
|
|
env: NodeJS.ProcessEnv = process.env,
|
|
) {
|
|
await fs.mkdir(options.artifactBase, { recursive: true });
|
|
const writer = createWriter(options);
|
|
const startedAt = Date.now();
|
|
const credential = resolveLeasedToken(env);
|
|
if (!credential) {
|
|
writer.appendLog(`telegram-bot-token: blocked; none of ${TOKEN_ENV_KEYS.join(", ")} is set\n`);
|
|
return await writer.write({
|
|
details: "Telegram runtime proof requires a leased bot token",
|
|
durationMs: Math.max(1, Date.now() - startedAt),
|
|
status: "blocked",
|
|
});
|
|
}
|
|
|
|
writer.appendLog(`telegram-bot-token: using leased credential from ${credential.key}\n`);
|
|
const abortController = new AbortController();
|
|
let markStarted: (() => void) | undefined;
|
|
const startupPromise = new Promise<void>((resolve) => {
|
|
markStarted = resolve;
|
|
});
|
|
const runtime: RuntimeEnv = {
|
|
log: (...args) => {
|
|
const line = args.map(String).join(" ");
|
|
writer.appendLog(`${line}\n`);
|
|
if (
|
|
line.includes("isolated polling ingress started") ||
|
|
line.includes("polling cycle started")
|
|
) {
|
|
markStarted?.();
|
|
}
|
|
},
|
|
error: (...args) => writer.appendLog(`${args.map(String).join(" ")}\n`),
|
|
exit: (code) => {
|
|
throw new Error(`Telegram runtime requested exit ${code}`);
|
|
},
|
|
};
|
|
const config: OpenClawConfig = {
|
|
channels: { telegram: { enabled: true } },
|
|
};
|
|
const previousStateDir = process.env.OPENCLAW_STATE_DIR;
|
|
process.env.OPENCLAW_STATE_DIR = path.join(options.artifactBase, "state");
|
|
try {
|
|
const monitorPromise = monitorTelegramProvider({
|
|
abortSignal: abortController.signal,
|
|
config,
|
|
isolatedIngress: { enabled: true },
|
|
runtime,
|
|
token: credential.token,
|
|
});
|
|
await waitForStartup({
|
|
abortController,
|
|
monitorPromise,
|
|
startupPromise,
|
|
timeoutMs: options.startupTimeoutMs,
|
|
});
|
|
writer.appendLog("telegram-bot-token: runtime started after Telegram getMe\n");
|
|
return await writer.write({
|
|
details: `Telegram runtime startup completed with ${credential.key}`,
|
|
durationMs: Math.max(1, Date.now() - startedAt),
|
|
status: "pass",
|
|
});
|
|
} catch (error) {
|
|
const details = formatErrorMessage(error);
|
|
writer.appendLog(`telegram-bot-token: ${details}\n`);
|
|
return await writer.write({
|
|
details,
|
|
durationMs: Math.max(1, Date.now() - startedAt),
|
|
status: "fail",
|
|
});
|
|
} finally {
|
|
if (previousStateDir === undefined) {
|
|
delete process.env.OPENCLAW_STATE_DIR;
|
|
} else {
|
|
process.env.OPENCLAW_STATE_DIR = previousStateDir;
|
|
}
|
|
}
|
|
}
|
|
|
|
export const testing = { parseOptions, resolveLeasedToken, waitForMonitorShutdown };
|
|
|
|
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
|
runTelegramBotTokenRuntime(parseOptions(process.argv.slice(2)))
|
|
.then((evidence) => {
|
|
const status = evidence.entries[0]?.result.status;
|
|
process.stdout.write(`telegram-bot-token: ${status}\n`);
|
|
process.exitCode = status === "fail" ? 1 : 0;
|
|
})
|
|
.catch((error: unknown) => {
|
|
process.stderr.write(`telegram-bot-token: ${formatErrorMessage(error)}\n`);
|
|
process.exitCode = 1;
|
|
});
|
|
}
|