refactor(qa): migrate Telegram scenarios into QA Lab (#108430)

* refactor(qa): migrate Telegram scenarios into QA Lab

* refactor(qa): remove retired Telegram runner exports
This commit is contained in:
Dallin Romney
2026-07-15 12:55:00 -07:00
committed by GitHub
parent d201acd6e8
commit 77d07dc3e9
40 changed files with 1673 additions and 3955 deletions
+41 -13
View File
@@ -6,6 +6,7 @@ import fs from "node:fs/promises";
import path from "node:path";
import { pathToFileURL } from "node:url";
import type { QaProviderMode } from "../../extensions/qa-lab/src/run-config.ts";
import type { QaSuiteRoundTripProbe } from "../../extensions/qa-lab/src/suite-round-trip.ts";
function parseBoolean(value: string | undefined) {
const normalized = value?.trim().toLowerCase();
@@ -53,24 +54,47 @@ function resolvePackageTelegramOutputDir(env: NodeJS.ProcessEnv, repoRoot: strin
);
}
const DEFAULT_RTT_CHECK_ID = "telegram-mentioned-message-reply";
const DEFAULT_RTT_CHECK_ID = "channel-canary";
function resolveRttOptions(env: NodeJS.ProcessEnv, selectedScenarioIds: readonly string[] = []) {
const explicitCheckIds = splitCsv(env.OPENCLAW_NPM_TELEGRAM_RTT_CHECKS);
const checkIds = explicitCheckIds.length > 0 ? explicitCheckIds : [DEFAULT_RTT_CHECK_ID];
const unknownCheckIds = checkIds.filter((checkId) => checkId !== DEFAULT_RTT_CHECK_ID);
if (unknownCheckIds.length > 0) {
throw new Error(`unknown Telegram QA RTT check: ${unknownCheckIds[0]}`);
}
if (
explicitCheckIds.length === 0 &&
selectedScenarioIds.length > 0 &&
!selectedScenarioIds.includes(DEFAULT_RTT_CHECK_ID)
) {
return {};
return undefined;
}
const rttCount = parsePositiveIntegerEnv(env, "OPENCLAW_NPM_TELEGRAM_RTT_SAMPLES") ?? 20;
const count = parsePositiveIntegerEnv(env, "OPENCLAW_NPM_TELEGRAM_RTT_SAMPLES") ?? 20;
return {
rttCount,
rttTimeoutMs: parsePositiveIntegerEnv(env, "OPENCLAW_NPM_TELEGRAM_RTT_TIMEOUT_MS"),
maxRttFailures:
parsePositiveIntegerEnv(env, "OPENCLAW_NPM_TELEGRAM_RTT_MAX_FAILURES") ?? rttCount,
rttCheckIds: explicitCheckIds,
scenarioId: DEFAULT_RTT_CHECK_ID,
count,
timeoutMs: parsePositiveIntegerEnv(env, "OPENCLAW_NPM_TELEGRAM_RTT_TIMEOUT_MS") ?? 30_000,
maxFailures: parsePositiveIntegerEnv(env, "OPENCLAW_NPM_TELEGRAM_RTT_MAX_FAILURES") ?? count,
};
}
function createRoundTripProbe(
options: ReturnType<typeof resolveRttOptions>,
): QaSuiteRoundTripProbe | undefined {
if (!options) {
return undefined;
}
return {
...options,
markerPrefix: "QA-TELEGRAM-RTT",
input: {
conversation: { id: "telegram-rtt-room", kind: "group" },
senderId: "qa-rtt-driver",
senderName: "QA RTT Driver",
},
textPrefix: "@openclaw Telegram RTT check. Reply exactly: ",
chainReplies: true,
};
}
@@ -117,8 +141,8 @@ async function resolveTrustedOpenClawCommand(
}
async function main() {
const { runTelegramQaLive } =
await import("../../extensions/qa-lab/src/live-transports/telegram/telegram-live.runtime.ts");
const { runQaTelegramSuite } =
await import("../../extensions/qa-lab/src/live-transports/telegram/cli.runtime.ts");
const rawSutOpenClawCommand = process.env.OPENCLAW_NPM_TELEGRAM_SUT_COMMAND?.trim();
if (!rawSutOpenClawCommand) {
throw new Error("Missing OPENCLAW_NPM_TELEGRAM_SUT_COMMAND.");
@@ -128,8 +152,8 @@ async function main() {
const repoRoot = path.resolve(process.env.OPENCLAW_NPM_TELEGRAM_REPO_ROOT ?? process.cwd());
const outputDir = resolvePackageTelegramOutputDir(process.env, repoRoot);
const scenarioIds = splitCsv(process.env.OPENCLAW_NPM_TELEGRAM_SCENARIOS);
const result = await runTelegramQaLive({
env: process.env,
const result = await runQaTelegramSuite({
allowFailures: true,
repoRoot,
outputDir,
sutOpenClawCommand,
@@ -138,11 +162,14 @@ async function main() {
alternateModel: process.env.OPENCLAW_NPM_TELEGRAM_ALT_MODEL,
fastMode: parseBoolean(process.env.OPENCLAW_NPM_TELEGRAM_FAST),
scenarioIds,
...resolveRttOptions(process.env, scenarioIds),
roundTripProbe: createRoundTripProbe(resolveRttOptions(process.env, scenarioIds)),
sutAccountId: process.env.OPENCLAW_NPM_TELEGRAM_SUT_ACCOUNT,
credentialSource: resolveCredentialSource(process.env),
credentialRole: resolveCredentialRole(process.env),
});
if (!result) {
throw new Error("Package Telegram QA did not produce suite artifacts.");
}
process.stdout.write(`Package Telegram QA report: ${result.reportPath}\n`);
process.stdout.write(`Package Telegram QA summary: ${result.summaryPath}\n`);
@@ -180,6 +207,7 @@ export const testing = {
resolvePackageTelegramOutputDir,
resolveCredentialRole,
resolveCredentialSource,
createRoundTripProbe,
resolveRttOptions,
resolveTrustedOpenClawCommand,
shouldFailPackageTelegramRun,
+7 -71
View File
@@ -70,62 +70,6 @@ function evidenceCredentialSource(summary) {
);
}
// Historical Telegram summary artifacts can still appear in old Mantis uploads.
// Current QA producers write qa-evidence.json for gate inputs.
function legacyTelegramSummaryToEvidenceSummary(summary) {
const scenarios = Array.isArray(summary.scenarios) ? summary.scenarios : [];
return {
kind: "openclaw.qa.evidence-summary",
schemaVersion: 2,
generatedAt: new Date().toISOString(),
entries: scenarios.map((scenario) => ({
test: {
kind: "legacy-telegram-qa-scenario",
id: scenario?.id ?? "legacy-telegram-scenario",
title: scenario?.title ?? scenario?.id ?? "Legacy Telegram scenario",
},
mapping: {
profile: "release",
coverage: [],
},
execution: {
runner: "legacy-telegram-qa",
environment: {
ref: null,
os: "unknown",
nodeVersion: "unknown",
},
provider: {
id: "unknown",
live: true,
model: { name: null, ref: null },
auth: summary.credentials?.source ?? "unknown",
},
channel: {
id: "telegram",
live: true,
driver: "native",
},
packageSource: { kind: "unknown" },
artifacts: [
{
kind: "summary",
path: "telegram-qa-summary.json",
source: "legacy-telegram-qa",
},
],
},
result: {
status: scenario?.status === "skip" ? "skipped" : (scenario?.status ?? "fail"),
...(scenario?.status === "pass"
? {}
: { failure: { reason: scenario?.details ?? "legacy scenario did not pass" } }),
...(typeof scenario?.rttMs === "number" ? { timing: { rttMs: scenario.rttMs } } : {}),
},
})),
};
}
function renderScenarioList(summary) {
const entries = evidenceEntries(summary);
if (entries.length === 0) {
@@ -420,7 +364,7 @@ export function buildTelegramEvidenceManifest({
kind: "report",
lane: "run",
label: "Telegram QA report",
path: "telegram-qa-report.md",
path: "qa-suite-report.md",
targetPath: "report.md",
},
];
@@ -453,17 +397,11 @@ export function writeTelegramEvidence(rawArgs = process.argv.slice(2)) {
const outputDir = path.resolve(args.output_dir);
mkdirSync(outputDir, { recursive: true });
const evidenceSummaryPath = path.join(outputDir, "qa-evidence.json");
const legacySummaryPath = path.join(outputDir, "telegram-qa-summary.json");
const usesCurrentEvidenceSummary = existsSync(evidenceSummaryPath);
const summaryPath = usesCurrentEvidenceSummary ? evidenceSummaryPath : legacySummaryPath;
const observedPath = path.join(outputDir, "telegram-qa-observed-messages.json");
const reportPath = path.join(outputDir, "telegram-qa-report.md");
if (!existsSync(summaryPath)) {
const reportPath = path.join(outputDir, "qa-suite-report.md");
if (!existsSync(evidenceSummaryPath)) {
throw new Error(`Missing Telegram QA evidence summary: ${evidenceSummaryPath}`);
}
const summary = usesCurrentEvidenceSummary
? readJson(evidenceSummaryPath)
: legacyTelegramSummaryToEvidenceSummary(readJson(legacySummaryPath));
const summary = readJson(evidenceSummaryPath);
const counts = evidenceCounts(summary);
const pass = counts.failed === 0 && Number(counts.total ?? 0) > 0;
if (!existsSync(reportPath)) {
@@ -472,17 +410,15 @@ export function writeTelegramEvidence(rawArgs = process.argv.slice(2)) {
}
writeFileSync(reportPath, "# Mantis Telegram Live QA\n\nTelegram QA report was unavailable.\n");
}
const hasLegacyObservedMessages = !usesCurrentEvidenceSummary && existsSync(observedPath);
const observedMessages = hasLegacyObservedMessages ? readJson(observedPath) : [];
const transcriptHtml = renderTelegramEvidenceHtml({ observedMessages, summary });
const transcriptHtml = renderTelegramEvidenceHtml({ observedMessages: [], summary });
writeFileSync(path.join(outputDir, "telegram-live-transcript.html"), transcriptHtml, "utf8");
const manifest = buildTelegramEvidenceManifest({
candidateRef: args.candidate_ref,
candidateSha: args.candidate_sha,
hasObservedMessages: hasLegacyObservedMessages,
hasObservedMessages: false,
scenarioLabel: args.scenario_label,
summary,
summaryArtifactPath: path.basename(summaryPath),
summaryArtifactPath: path.basename(evidenceSummaryPath),
});
writeFileSync(
path.join(outputDir, "mantis-evidence.json"),
+1 -1
View File
@@ -449,7 +449,7 @@ async function main(): Promise<void> {
if (telegramRunId) {
await pollRun(options.repo, telegramRunId);
const artifactDir = downloadTelegramArtifact(options.repo, telegramRunId);
const report = findFile(artifactDir, "telegram-qa-report.md");
const report = findFile(artifactDir, "qa-suite-report.md");
if (report && existsSync(report)) {
console.log(`\nTelegram report: ${report}\n`);
console.log(readFileSync(report, "utf8"));