#!/usr/bin/env node // Builds an HTML/manifest evidence bundle from Telegram QA scenario summaries. import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; function parseArgs(argv) { const args = {}; for (let index = 0; index < argv.length; index += 1) { const key = argv[index]; if (!key.startsWith("--")) { throw new Error(`Unexpected argument: ${key}`); } const name = key.slice(2).replaceAll("-", "_"); const value = argv[index + 1]; if (!value || value.startsWith("--")) { throw new Error(`Missing value for ${key}`); } args[name] = value; index += 1; } return args; } function readJson(filePath) { return JSON.parse(readFileSync(filePath, "utf8")); } function escapeHtml(value) { return String(value ?? "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function formatMessageText(message) { const text = typeof message.text === "string" ? message.text : ""; const caption = typeof message.caption === "string" ? message.caption : ""; const content = text || caption || ""; if (content.trim()) { return content; } const mediaKinds = Array.isArray(message.mediaKinds) ? message.mediaKinds : []; return mediaKinds.length > 0 ? `[${mediaKinds.join(", ")}]` : "[no text]"; } function evidenceEntries(summary) { return Array.isArray(summary.entries) ? summary.entries : []; } function evidenceCounts(summary) { const entries = evidenceEntries(summary); const failed = entries.filter((entry) => entry?.result?.status !== "pass").length; return { total: entries.length, passed: entries.length - failed, failed, }; } function evidenceCredentialSource(summary) { const entry = evidenceEntries(summary)[0]; return ( entry?.execution?.provider?.auth ?? entry?.execution?.provider?.fixture ?? entry?.execution?.packageSource?.kind ?? "unknown" ); } // 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) { return "
  • No evidence entries recorded.
  • "; } return entries .map((entry) => { const status = entry?.result?.status ?? "unknown"; const statusClass = status === "pass" ? "pass" : "fail"; const timing = entry?.result?.timing; const rttMs = timing?.p50Ms ?? timing?.rttMs; const rtt = typeof rttMs === "number" ? `, ${Math.round(rttMs)}ms RTT` : ""; return `
  • ${escapeHtml(status)} ${escapeHtml(entry?.test?.title ?? entry?.test?.id)} ${escapeHtml(entry?.test?.id ?? "")}${rtt}

    ${escapeHtml(entry?.result?.failure?.reason ?? "")}

  • `; }) .join("\n"); } function renderObservedMessages(observedMessages) { if (!Array.isArray(observedMessages) || observedMessages.length === 0) { return '

    No observed Telegram messages were recorded.

    '; } return observedMessages .map((message, index) => { const sender = message.senderIsBot ? "bot" : "user"; const scenario = message.scenarioTitle ?? message.scenarioId ?? ""; const text = formatMessageText(message); const buttons = Array.isArray(message.inlineButtons) ? message.inlineButtons : typeof message.inlineButtonCount === "number" && message.inlineButtonCount > 0 ? [`${message.inlineButtonCount} inline button(s)`] : []; return [ `
    `, `
    #${index + 1}${escapeHtml(sender)}${scenario ? `${escapeHtml(scenario)}` : ""}
    `, `
    ${escapeHtml(text)}
    `, buttons.length > 0 ? `
    ${buttons.map((button) => `${escapeHtml(button)}`).join("")}
    ` : "", "
    ", ] .filter(Boolean) .join("\n"); }) .join("\n"); } /** * Renders a self-contained Telegram evidence HTML report. */ export function renderTelegramEvidenceHtml({ observedMessages, summary }) { const counts = evidenceCounts(summary); const pass = counts.failed === 0 && Number(counts.total ?? 0) > 0; return ` Mantis Telegram Live Evidence

    Mantis Telegram Live Evidence

    status: ${pass ? "pass" : "fail"} total: ${escapeHtml(counts.total ?? 0)} passed: ${escapeHtml(counts.passed ?? 0)} failed: ${escapeHtml(counts.failed ?? 0)} credentials: ${escapeHtml(evidenceCredentialSource(summary))}

    Scenarios

    Observed Telegram Messages

    ${renderObservedMessages(observedMessages)}
    `; } export function buildTelegramEvidenceManifest({ candidateRef, candidateSha, hasObservedMessages = true, scenarioLabel, summary, summaryArtifactPath = "qa-evidence.json", }) { const counts = evidenceCounts(summary); const pass = counts.failed === 0 && Number(counts.total ?? 0) > 0; const scenarioNames = evidenceEntries(summary) .map((entry) => entry?.test?.id) .filter(Boolean); const scenario = scenarioLabel || scenarioNames.join(",") || "telegram-live"; const status = pass ? "pass" : "fail"; const artifacts = [ { kind: "desktopScreenshot", lane: "candidate", label: "Telegram live transcript", path: "telegram-live-desktop.png", targetPath: "telegram-live-desktop.png", alt: "Rendered Telegram live transcript in a Crabbox desktop browser", width: 720, inline: true, required: false, }, { kind: "motionPreview", lane: "candidate", label: "Telegram motion preview", path: "telegram-live-preview.gif", targetPath: "telegram-live-preview.gif", alt: "Animated Telegram live transcript capture", width: 720, inline: true, required: false, }, { kind: "motionClip", lane: "candidate", label: "Telegram change MP4", path: "telegram-live-change.mp4", targetPath: "telegram-live-change.mp4", required: false, }, { kind: "fullVideo", lane: "candidate", label: "Telegram desktop MP4", path: "telegram-live.mp4", targetPath: "telegram-live.mp4", required: false, }, { kind: "metadata", lane: "run", label: "Telegram QA evidence summary", path: summaryArtifactPath, targetPath: "summary.json", }, ...(hasObservedMessages ? [ { kind: "metadata", lane: "run", label: "Telegram observed messages", path: "telegram-qa-observed-messages.json", targetPath: "observed-messages.json", }, ] : []), { kind: "metadata", lane: "run", label: "Telegram transcript HTML", path: "telegram-live-transcript.html", targetPath: "telegram-live-transcript.html", }, { kind: "metadata", lane: "run", label: "Telegram preview metadata", path: "telegram-live-preview.json", targetPath: "telegram-live-preview.json", required: false, }, { kind: "metadata", lane: "run", label: "Telegram QA error", path: "error.txt", targetPath: "error.txt", required: false, }, { kind: "report", lane: "run", label: "Telegram QA report", path: "telegram-qa-report.md", targetPath: "report.md", }, ]; return { schemaVersion: 1, id: "telegram-live", title: "Mantis Telegram Live QA", summary: "Mantis ran the Telegram live QA lane with Convex-leased credentials, rendered a redacted transcript in a Crabbox desktop browser, and captured screenshot/video evidence for PR review.", scenario, comparison: { candidate: { ...(candidateSha ? { sha: candidateSha } : {}), ...(candidateRef ? { ref: candidateRef } : {}), expected: "Telegram live QA scenarios pass", status, fixed: pass, }, pass, }, artifacts, }; } export function writeTelegramEvidence(rawArgs = process.argv.slice(2)) { const args = parseArgs(rawArgs); if (!args.output_dir) { throw new Error("Missing --output-dir."); } 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)) { throw new Error(`Missing Telegram QA evidence summary: ${evidenceSummaryPath}`); } const summary = usesCurrentEvidenceSummary ? readJson(evidenceSummaryPath) : legacyTelegramSummaryToEvidenceSummary(readJson(legacySummaryPath)); const counts = evidenceCounts(summary); const pass = counts.failed === 0 && Number(counts.total ?? 0) > 0; if (!existsSync(reportPath)) { if (pass) { throw new Error(`Missing Telegram QA report for passing summary: ${reportPath}`); } 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 }); writeFileSync(path.join(outputDir, "telegram-live-transcript.html"), transcriptHtml, "utf8"); const manifest = buildTelegramEvidenceManifest({ candidateRef: args.candidate_ref, candidateSha: args.candidate_sha, hasObservedMessages: hasLegacyObservedMessages, scenarioLabel: args.scenario_label, summary, summaryArtifactPath: path.basename(summaryPath), }); writeFileSync( path.join(outputDir, "mantis-evidence.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8", ); return { manifest, manifestPath: path.join(outputDir, "mantis-evidence.json"), transcriptPath: path.join(outputDir, "telegram-live-transcript.html"), }; } const executedPath = process.argv[1] ? path.resolve(process.argv[1]) : ""; if (executedPath === fileURLToPath(import.meta.url)) { try { writeTelegramEvidence(); } catch (error) { console.error(error instanceof Error ? error.message : String(error)); process.exit(1); } }