






















@@ -298,6 +298,9 @@ const TELEGRAM_QA_ENV_KEYS = [
298298] as const;
299299const TELEGRAM_QA_CAPTURE_CONTENT_ENV = "OPENCLAW_QA_TELEGRAM_CAPTURE_CONTENT";
300300const QA_REDACT_PUBLIC_METADATA_ENV = "OPENCLAW_QA_REDACT_PUBLIC_METADATA";
301+const QA_SUITE_PROGRESS_ENV = "OPENCLAW_QA_SUITE_PROGRESS";
302+const TELEGRAM_QA_PROGRESS_DETAIL_LIMIT = 240;
303+const TELEGRAM_QA_PROGRESS_PREFIX = "[qa-telegram-live]";
301304302305const telegramQaCredentialPayloadSchema = z.object({
303306groupId: z.string().trim().min(1),
@@ -318,6 +321,57 @@ function isTruthyOptIn(value: string | undefined) {
318321return normalized === "1" || normalized === "true" || normalized === "yes";
319322}
320323324+function parseTelegramQaProgressBooleanEnv(value: string | undefined): boolean | undefined {
325+const normalized = value?.trim().toLowerCase();
326+if (!normalized) {
327+return undefined;
328+}
329+if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
330+return true;
331+}
332+if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
333+return false;
334+}
335+return undefined;
336+}
337+338+function shouldLogTelegramQaLiveProgress(env: NodeJS.ProcessEnv = process.env) {
339+const override = parseTelegramQaProgressBooleanEnv(env[QA_SUITE_PROGRESS_ENV]);
340+if (override !== undefined) {
341+return override;
342+}
343+return parseTelegramQaProgressBooleanEnv(env.CI) === true;
344+}
345+346+function writeTelegramQaProgress(enabled: boolean, message: string) {
347+if (!enabled) {
348+return;
349+}
350+process.stderr.write(`${TELEGRAM_QA_PROGRESS_PREFIX} ${message}\n`);
351+}
352+353+function sanitizeTelegramQaProgressValue(value: string): string {
354+let normalized = "";
355+for (const char of value) {
356+const code = char.codePointAt(0);
357+if (code === undefined) {
358+continue;
359+}
360+const isControl = code <= 0x1f || (code >= 0x7f && code <= 0x9f);
361+normalized += isControl ? " " : char;
362+}
363+normalized = normalized.replace(/\s+/gu, " ").trim();
364+return normalized.length > 0 ? normalized : "<empty>";
365+}
366+367+function formatTelegramQaProgressDetails(details: string): string {
368+const sanitized = sanitizeTelegramQaProgressValue(details);
369+if (sanitized.length <= TELEGRAM_QA_PROGRESS_DETAIL_LIMIT) {
370+return sanitized;
371+}
372+return `${sanitized.slice(0, TELEGRAM_QA_PROGRESS_DETAIL_LIMIT - 3).trimEnd()}...`;
373+}
374+321375export function resolveTelegramQaRuntimeEnv(
322376env: NodeJS.ProcessEnv = process.env,
323377): TelegramQaRuntimeEnv {
@@ -908,6 +962,19 @@ export async function runTelegramQaLive(params: {
908962path.join(repoRoot, ".artifacts", "qa-e2e", `telegram-${Date.now().toString(36)}`);
909963await fs.mkdir(outputDir, { recursive: true });
910964965+const providerMode = normalizeQaProviderMode(
966+params.providerMode ?? DEFAULT_QA_LIVE_PROVIDER_MODE,
967+);
968+const primaryModel = params.primaryModel?.trim() || defaultQaModelForMode(providerMode);
969+const alternateModel = params.alternateModel?.trim() || defaultQaModelForMode(providerMode, true);
970+const sutAccountId = params.sutAccountId?.trim() || "sut";
971+const scenarios = findScenario(params.scenarioIds);
972+const progressEnabled = shouldLogTelegramQaLiveProgress();
973+writeTelegramQaProgress(
974+progressEnabled,
975+`run start: scenarios=${scenarios.length} providerMode=${providerMode} fastMode=${params.fastMode === true ? "on" : "off"}`,
976+);
977+911978const credentialLease = await acquireQaCredentialLease({
912979kind: "telegram",
913980source: params.credentialSource,
@@ -919,18 +986,19 @@ export async function runTelegramQaLive(params: {
919986const assertLeaseHealthy = () => {
920987leaseHeartbeat.throwIfFailed();
921988};
989+writeTelegramQaProgress(
990+progressEnabled,
991+`credentials ready: source=${credentialLease.source} role=${credentialLease.role ?? "<none>"}`,
992+);
922993923994const runtimeEnv = credentialLease.payload;
924-const providerMode = normalizeQaProviderMode(
925-params.providerMode ?? DEFAULT_QA_LIVE_PROVIDER_MODE,
926-);
927-const primaryModel = params.primaryModel?.trim() || defaultQaModelForMode(providerMode);
928-const alternateModel = params.alternateModel?.trim() || defaultQaModelForMode(providerMode, true);
929-const sutAccountId = params.sutAccountId?.trim() || "sut";
930-const scenarios = findScenario(params.scenarioIds);
931995const observedMessages: TelegramObservedMessage[] = [];
932996const redactPublicMetadata = isTruthyOptIn(process.env[QA_REDACT_PUBLIC_METADATA_ENV]);
933997const includeObservedMessageContent = isTruthyOptIn(process.env[TELEGRAM_QA_CAPTURE_CONTENT_ENV]);
998+writeTelegramQaProgress(
999+progressEnabled,
1000+`runtime: redactMetadata=${redactPublicMetadata ? "on" : "off"} captureContent=${includeObservedMessageContent ? "on" : "off"}`,
1001+);
9341002const startedAt = new Date().toISOString();
9351003const scenarioResults: TelegramQaScenarioResult[] = [];
9361004const cleanupIssues: string[] = [];
@@ -976,13 +1044,15 @@ export async function runTelegramQaLive(params: {
9761044await waitForTelegramChannelRunning(gatewayHarness.gateway, sutAccountId);
9771045assertLeaseHealthy();
9781046try {
1047+writeTelegramQaProgress(progressEnabled, "canary start");
9791048await runCanary({
9801049driverToken: runtimeEnv.driverToken,
9811050groupId: runtimeEnv.groupId,
9821051 sutUsername,
9831052sutBotId: sutIdentity.id,
9841053 observedMessages,
9851054});
1055+writeTelegramQaProgress(progressEnabled, "canary pass");
9861056} catch (error) {
9871057canaryFailure = canaryFailureMessage({
9881058 error,
@@ -999,11 +1069,21 @@ export async function runTelegramQaLive(params: {
9991069status: "fail",
10001070details: canaryFailure,
10011071});
1072+writeTelegramQaProgress(
1073+progressEnabled,
1074+`canary fail: details=${formatTelegramQaProgressDetails(canaryFailure)}`,
1075+);
10021076}
10031077assertLeaseHealthy();
10041078if (!canaryFailure) {
10051079let driverOffset = await flushTelegramUpdates(runtimeEnv.driverToken);
1006-for (const scenario of scenarios) {
1080+for (const [scenarioIndex, scenario] of scenarios.entries()) {
1081+const scenarioIndexLabel = `${scenarioIndex + 1}/${scenarios.length}`;
1082+const scenarioIdForLog = sanitizeTelegramQaProgressValue(scenario.id);
1083+writeTelegramQaProgress(
1084+progressEnabled,
1085+`scenario start (${scenarioIndexLabel}): ${scenarioIdForLog}`,
1086+);
10071087assertLeaseHealthy();
10081088const scenarioRun = scenario.buildRun(sutUsername);
10091089try {
@@ -1036,35 +1116,50 @@ export async function runTelegramQaLive(params: {
10361116expectedTextIncludes: scenarioRun.expectedTextIncludes,
10371117message: matched.message,
10381118});
1039-scenarioResults.push({
1119+const result = {
10401120id: scenario.id,
10411121title: scenario.title,
10421122status: "pass",
10431123details: redactPublicMetadata
10441124 ? "reply matched"
10451125 : `reply message ${matched.message.messageId} matched`,
1046-});
1126+} satisfies TelegramQaScenarioResult;
1127+scenarioResults.push(result);
1128+writeTelegramQaProgress(
1129+progressEnabled,
1130+`scenario pass (${scenarioIndexLabel}): ${scenarioIdForLog} details=${formatTelegramQaProgressDetails(result.details)}`,
1131+);
10471132} catch (error) {
10481133if (!scenarioRun.expectReply) {
10491134const details = formatErrorMessage(error);
10501135if (
10511136details === `timed out after ${scenario.timeoutMs}ms waiting for Telegram message`
10521137) {
1053-scenarioResults.push({
1138+const result = {
10541139id: scenario.id,
10551140title: scenario.title,
10561141status: "pass",
10571142details: "no reply",
1058-});
1143+} satisfies TelegramQaScenarioResult;
1144+scenarioResults.push(result);
1145+writeTelegramQaProgress(
1146+progressEnabled,
1147+`scenario pass (${scenarioIndexLabel}): ${scenarioIdForLog} details=${formatTelegramQaProgressDetails(result.details)}`,
1148+);
10591149continue;
10601150}
10611151}
1062-scenarioResults.push({
1152+const result = {
10631153id: scenario.id,
10641154title: scenario.title,
10651155status: "fail",
10661156details: formatErrorMessage(error),
1067-});
1157+} satisfies TelegramQaScenarioResult;
1158+scenarioResults.push(result);
1159+writeTelegramQaProgress(
1160+progressEnabled,
1161+`scenario fail (${scenarioIndexLabel}): ${scenarioIdForLog} details=${formatTelegramQaProgressDetails(result.details)}`,
1162+);
10681163}
10691164assertLeaseHealthy();
10701165}
@@ -1089,6 +1184,15 @@ export async function runTelegramQaLive(params: {
10891184const publishedCleanupIssues = redactPublicMetadata
10901185 ? cleanupIssues.map(() => "details redacted (OPENCLAW_QA_REDACT_PUBLIC_METADATA=1)")
10911186 : cleanupIssues;
1187+const passedCount = scenarioResults.filter((entry) => entry.status === "pass").length;
1188+const failedCount = scenarioResults.filter((entry) => entry.status === "fail").length;
1189+writeTelegramQaProgress(
1190+progressEnabled,
1191+`run complete: passed=${passedCount} failed=${failedCount} total=${scenarioResults.length}`,
1192+);
1193+if (cleanupIssues.length > 0) {
1194+writeTelegramQaProgress(progressEnabled, `cleanup issues: count=${cleanupIssues.length}`);
1195+}
10921196const summary: TelegramQaSummary = {
10931197credentials: {
10941198source: credentialLease.source,
@@ -1103,8 +1207,8 @@ export async function runTelegramQaLive(params: {
11031207cleanupIssues: publishedCleanupIssues,
11041208counts: {
11051209total: scenarioResults.length,
1106-passed: scenarioResults.filter((entry) => entry.status === "pass").length,
1107-failed: scenarioResults.filter((entry) => entry.status === "fail").length,
1210+passed: passedCount,
1211+failed: failedCount,
11081212},
11091213scenarios: scenarioResults,
11101214};
@@ -1185,6 +1289,10 @@ export const __testing = {
11851289 findScenario,
11861290 matchesTelegramScenarioReply,
11871291 normalizeTelegramObservedMessage,
1292+ parseTelegramQaProgressBooleanEnv,
11881293 parseTelegramQaCredentialPayload,
11891294 resolveTelegramQaRuntimeEnv,
1295+ sanitizeTelegramQaProgressValue,
1296+ shouldLogTelegramQaLiveProgress,
1297+ formatTelegramQaProgressDetails,
11901298};
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。