Skip to content

Commit cd15ce3

Browse files
committed
fix(qa): keep telegram user creds mantis-only
1 parent 395bd57 commit cd15ce3

9 files changed

Lines changed: 57 additions & 198 deletions

File tree

docs/concepts/qa-e2e-automation.md

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -594,34 +594,20 @@ Telegram, Discord, Slack, and WhatsApp lanes can lease credentials from a shared
594594
Payload shapes the broker validates on `admin/add`:
595595

596596
- Telegram (`kind: "telegram"`): `{ groupId: string, driverToken: string, sutToken: string }` - `groupId` must be a numeric chat-id string.
597-
- Telegram real user (`kind: "telegram-user"`): `{ groupId: string, sutToken: string, testerUserId: string, testerUsername: string, telegramApiId: string, telegramApiHash: string, tdlibDatabaseEncryptionKey: string, tdlibArchiveBase64: string, tdlibArchiveSha256: string, desktopTdataArchiveBase64: string, desktopTdataArchiveSha256: string }` - one exclusive burner-account lease used by both the TDLib CLI driver and Telegram Desktop visual witness.
597+
- Telegram real user (`kind: "telegram-user"`): `{ groupId: string, sutToken: string, testerUserId: string, testerUsername: string, telegramApiId: string, telegramApiHash: string, tdlibDatabaseEncryptionKey: string, tdlibArchiveBase64: string, tdlibArchiveSha256: string, desktopTdataArchiveBase64: string, desktopTdataArchiveSha256: string }` - Mantis Telegram Desktop proof only. Generic QA Lab lanes must not acquire this kind.
598598
- Discord (`kind: "discord"`): `{ guildId: string, channelId: string, driverBotToken: string, sutBotToken: string, sutApplicationId: string }`.
599599
- WhatsApp (`kind: "whatsapp"`): `{ driverPhoneE164: string, sutPhoneE164: string, driverAuthArchiveBase64: string, sutAuthArchiveBase64: string, groupJid?: string }` - phone numbers must be distinct E.164 strings.
600600

601-
For visual real-user Telegram proof, prefer a held Crabbox session:
601+
The Mantis Telegram Desktop proof workflow holds one exclusive Convex
602+
`telegram-user` lease for both the TDLib CLI driver and Telegram Desktop
603+
witness, then releases it after publishing proof.
602604

603-
```bash
604-
pnpm qa:telegram-user:crabbox -- start --tdlib-url http://artifacts.openclaw.ai/tdlib-v1.8.0-linux-x64.tgz --output-dir .artifacts/qa-e2e/telegram-user-crabbox/pr-review
605-
pnpm qa:telegram-user:crabbox -- send --session .artifacts/qa-e2e/telegram-user-crabbox/pr-review/session.json --text /status
606-
pnpm qa:telegram-user:crabbox -- finish --session .artifacts/qa-e2e/telegram-user-crabbox/pr-review/session.json
607-
```
608-
609-
`start` holds one exclusive Convex `telegram-user` lease for both the TDLib CLI
610-
driver and Telegram Desktop witness, starts desktop recording, and leaves the
611-
Crabbox alive for arbitrary agent-driven repro steps. Agents can use `send`,
612-
`run`, `screenshot`, and `status` until they are satisfied, then `finish`
613-
collects the screenshot, video, motion-trimmed video/GIF, TDLib probe outputs,
614-
and logs before releasing the credential. `publish --session <file> --pr
615-
<number>` comments only the motion GIF by default; `--full-artifacts` is the
616-
explicit opt-in for logs and JSON output. The default `probe` command remains a
617-
one-command shorthand for quick `/status` smoke checks.
618-
619-
Use `--mock-response-file <path>` when a PR needs a deterministic visual diff:
620-
the same mock model reply can be run on `main` and on the PR head while the
621-
Telegram formatter or delivery layer changes. Capture defaults are tuned for PR
622-
comments: standard Crabbox class, 24fps desktop recording, 24fps motion GIF, and
623-
1920px preview width. Before/after comments should publish a clean bundle that
624-
contains only the intended GIFs.
605+
When a PR needs a deterministic visual diff, Mantis can use the same mock model
606+
reply on `main` and on the PR head while the Telegram formatter or delivery
607+
layer changes. Capture defaults are tuned for PR comments: standard Crabbox
608+
class, 24fps desktop recording, 24fps motion GIF, and 1920px preview width.
609+
Before/after comments should publish a clean bundle that contains only the
610+
intended GIFs.
625611

626612
Slack lanes can also use the pool. Slack payload shape checks currently live in the Slack QA runner rather than the broker; use `{ channelId: string, driverBotToken: string, sutBotToken: string, sutAppToken: string }`, with a Slack channel id like `Cxxxxxxxxxx`. See [Setting up the Slack workspace](#setting-up-the-slack-workspace) for app and scope provisioning.
627613

docs/help/testing.md

Lines changed: 1 addition & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -450,70 +450,7 @@ Payload shape for Telegram real-user kind:
450450
- `{ groupId: string, sutToken: string, testerUserId: string, testerUsername: string, telegramApiId: string, telegramApiHash: string, tdlibDatabaseEncryptionKey: string, tdlibArchiveBase64: string, tdlibArchiveSha256: string, desktopTdataArchiveBase64: string, desktopTdataArchiveSha256: string }`
451451
- `groupId`, `testerUserId`, and `telegramApiId` must be numeric strings.
452452
- `tdlibArchiveSha256` and `desktopTdataArchiveSha256` must be SHA-256 hex strings.
453-
- `kind: "telegram-user"` represents one Telegram burner account. Treat the lease as account-wide: the TDLib CLI driver and Telegram Desktop visual witness restore from the same payload, and only one job should hold the lease at a time.
454-
455-
Telegram real-user lease restore:
456-
457-
```bash
458-
tmp=$(mktemp -d /tmp/openclaw-telegram-user.XXXXXX)
459-
node --import tsx scripts/e2e/telegram-user-credential.ts lease-restore \
460-
--user-driver-dir "$tmp/user-driver" \
461-
--desktop-workdir "$tmp/desktop" \
462-
--lease-file "$tmp/lease.json"
463-
TELEGRAM_USER_DRIVER_STATE_DIR="$tmp/user-driver" \
464-
uv run ~/.codex/skills/custom/telegram-e2e-bot-to-bot/scripts/user-driver.py status --json
465-
node --import tsx scripts/e2e/telegram-user-credential.ts release --lease-file "$tmp/lease.json"
466-
```
467-
468-
Use the restored Desktop profile with `Telegram -workdir "$tmp/desktop"` when a visual recording is needed. In local operator environments, `scripts/e2e/telegram-user-credential.ts` reads `~/.codex/skills/custom/telegram-e2e-bot-to-bot/convex.local.env` by default if process env vars are absent.
469-
470-
Agent-driven Crabbox session:
471-
472-
```bash
473-
pnpm qa:telegram-user:crabbox -- start \
474-
--tdlib-url http://artifacts.openclaw.ai/tdlib-v1.8.0-linux-x64.tgz \
475-
--output-dir .artifacts/qa-e2e/telegram-user-crabbox/pr-review
476-
pnpm qa:telegram-user:crabbox -- send \
477-
--session .artifacts/qa-e2e/telegram-user-crabbox/pr-review/session.json \
478-
--text /status
479-
pnpm qa:telegram-user:crabbox -- finish \
480-
--session .artifacts/qa-e2e/telegram-user-crabbox/pr-review/session.json
481-
```
482-
483-
`start` leases the `telegram-user` credential, restores the same account into
484-
TDLib and Telegram Desktop on a Crabbox Linux desktop, starts a local mock SUT
485-
gateway from the current checkout, opens the visible Telegram chat, starts
486-
desktop recording, and writes a private `session.json`. While the session is
487-
alive, an agent can keep testing until satisfied:
488-
489-
- `send --session <file> --text <message>` sends through the real TDLib user and waits for the SUT reply.
490-
- `run --session <file> -- <remote command>` runs an arbitrary command on the Crabbox and saves its output, for example `bash -lc 'source /tmp/openclaw-telegram-user-crabbox/env.sh && python3 /tmp/openclaw-telegram-user-crabbox/user-driver.py transcript --limit 20 --json'`.
491-
- `screenshot --session <file>` captures the current visible desktop.
492-
- `status --session <file>` prints the lease and WebVNC command.
493-
- `finish --session <file>` stops the recorder, captures screenshot/video/motion-trim artifacts, releases the Convex credential, stops local SUT processes, and stops the Crabbox lease unless `--keep-box` is passed.
494-
- `publish --session <file> --pr <number>` publishes a GIF-only PR comment by default. Pass `--full-artifacts` only when logs or JSON artifacts are intentionally needed.
495-
496-
For deterministic visual repros, pass `--mock-response-file <path>` to `start`
497-
or to the one-command `probe` shorthand. The runner defaults to a standard
498-
Crabbox class, 24fps recording, 24fps motion GIF previews, and 1920px GIF
499-
width. Override with `--class`, `--record-fps`, `--preview-fps`, and
500-
`--preview-width` only when the proof needs different capture settings.
501-
502-
One-command Crabbox proof:
503-
504-
```bash
505-
pnpm qa:telegram-user:crabbox -- --text /status
506-
```
507-
508-
The default `probe` command is shorthand for one start/send/finish cycle. Use
509-
it for a quick `/status` smoke. Use the session commands for PR review,
510-
bug-reproduction work, or any case where the agent needs minutes of arbitrary
511-
experimentation before deciding the proof is complete. Use `--id <cbx_...>` to
512-
reuse a warm desktop lease, `--keep-box` to keep VNC open after finish,
513-
`--desktop-chat-title <name>` to pick the visible chat, and `--tdlib-url <tgz>`
514-
when using a prebaked Linux `libtdjson.so` archive instead of building TDLib on
515-
a fresh box. The runner verifies `--tdlib-url` with `--tdlib-sha256 <hex>` or,
516-
by default, a sibling `<url>.sha256` file.
453+
- `kind: "telegram-user"` is reserved for the Mantis Telegram Desktop proof workflow. Generic QA Lab lanes must not acquire it.
517454

518455
Broker-validated multi-channel payloads:
519456

extensions/qa-lab/runtime-api.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,3 @@ export {
3939
setQaChannelRuntime,
4040
} from "./src/runtime-api.js";
4141
export { startQaLiveLaneGateway } from "./src/live-transports/shared/live-gateway.runtime.js";
42-
export {
43-
TELEGRAM_USER_QA_CREDENTIAL_KIND,
44-
parseTelegramUserQaCredentialPayload,
45-
type TelegramUserQaCredentialPayload,
46-
} from "./src/live-transports/telegram/telegram-user-credential.runtime.js";

extensions/qa-lab/src/live-transports/telegram/telegram-user-credential.runtime.test.ts

Lines changed: 0 additions & 58 deletions
This file was deleted.

extensions/qa-lab/src/live-transports/telegram/telegram-user-credential.runtime.ts

Lines changed: 0 additions & 36 deletions
This file was deleted.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1559,7 +1559,6 @@
15591559
"qa:lab:up:fast": "node --import tsx scripts/qa-lab-up.ts --use-prebuilt-image --bind-ui-dist --skip-ui-build",
15601560
"qa:lab:watch": "vite build --watch --config extensions/qa-lab/web/vite.config.ts",
15611561
"qa:otel:smoke": "node --import tsx scripts/qa-otel-smoke.ts",
1562-
"qa:telegram-user:crabbox": "node --import tsx scripts/e2e/telegram-user-crabbox-proof.ts",
15631562
"release-metadata:check": "node scripts/check-release-metadata-only.mjs",
15641563
"release:beta": "node scripts/release-candidate-checklist.mjs",
15651564
"release:beta-smoke": "node --import tsx scripts/release-beta-smoke.ts",

scripts/e2e/telegram-user-crabbox-proof.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1697,10 +1697,10 @@ async function startSession(root: string, opts: Options, outputDir: string) {
16971697
},
16981698
webvnc: `${opts.crabboxBin} webvnc --provider ${opts.provider} --target ${opts.target} --id ${leaseId} --open`,
16991699
commands: {
1700-
send: `pnpm qa:telegram-user:crabbox -- send --session ${path.relative(root, pathname)} --text '/status'`,
1701-
view: `pnpm qa:telegram-user:crabbox -- view --session ${path.relative(root, pathname)} --message-id <message-id>`,
1702-
run: `pnpm qa:telegram-user:crabbox -- run --session ${path.relative(root, pathname)} -- bash -lc 'source ${REMOTE_ROOT}/env.sh && python3 ${REMOTE_ROOT}/user-driver.py transcript --limit 20 --json'`,
1703-
finish: `pnpm qa:telegram-user:crabbox -- finish --session ${path.relative(root, pathname)} --preview-crop telegram-window`,
1700+
send: `openclaw-telegram-user-crabbox-proof send --session ${path.relative(root, pathname)} --text '/status'`,
1701+
view: `openclaw-telegram-user-crabbox-proof view --session ${path.relative(root, pathname)} --message-id <message-id>`,
1702+
run: `openclaw-telegram-user-crabbox-proof run --session ${path.relative(root, pathname)} -- bash -lc 'source ${REMOTE_ROOT}/env.sh && python3 ${REMOTE_ROOT}/user-driver.py transcript --limit 20 --json'`,
1703+
finish: `openclaw-telegram-user-crabbox-proof finish --session ${path.relative(root, pathname)} --preview-crop telegram-window`,
17041704
},
17051705
};
17061706
} catch (error) {

scripts/e2e/telegram-user-credential.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33
import { spawn } from "node:child_process";
44
import { createHash } from "node:crypto";
55
import { chmod, copyFile, mkdir, readFile, rm, unlink, writeFile } from "node:fs/promises";
6-
import {
7-
TELEGRAM_USER_QA_CREDENTIAL_KIND,
8-
parseTelegramUserQaCredentialPayload,
9-
} from "../../extensions/qa-lab/runtime-api.js";
6+
import { normalizeCredentialPayloadForKind } from "../qa/convex-credential-broker/convex/payload-validation.js";
107

118
type JsonObject = Record<string, unknown>;
129

@@ -15,6 +12,7 @@ const DEFAULT_BOT_CREDENTIALS_FILE =
1512
"~/.codex/skills/custom/telegram-e2e-bot-to-bot/credentials.local.json";
1613
const DEFAULT_CONVEX_ENV_FILE = "~/.codex/skills/custom/telegram-e2e-bot-to-bot/convex.local.env";
1714
const CHUNKED_PAYLOAD_MARKER = "__openclawQaCredentialPayloadChunksV1";
15+
const TELEGRAM_USER_QA_CREDENTIAL_KIND = "telegram-user";
1816

1917
function usage(): never {
2018
throw new Error(
@@ -156,6 +154,10 @@ function optionalPositiveInteger(value: string | undefined, fallback: number) {
156154
return parsed;
157155
}
158156

157+
function parseTelegramUserQaCredentialPayload(payload: Record<string, unknown>): JsonObject {
158+
return normalizeCredentialPayloadForKind(TELEGRAM_USER_QA_CREDENTIAL_KIND, payload);
159+
}
160+
159161
async function fileSha256(path: string) {
160162
return createHash("sha256")
161163
.update(await readFile(path))
@@ -342,7 +344,7 @@ async function hydratePayloadFromLease(params: {
342344
if (serialized.length !== marker.byteLength) {
343345
throw new Error("Chunked payload length mismatch.");
344346
}
345-
return parseTelegramUserQaCredentialPayload(JSON.parse(serialized)) as JsonObject;
347+
return parseTelegramUserQaCredentialPayload(JSON.parse(serialized));
346348
}
347349

348350
async function createTelegramUserPayload(opts: Map<string, string>) {

test/scripts/mantis-telegram-desktop-proof-workflow.test.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
import { existsSync, readFileSync } from "node:fs";
1+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
22
import { describe, expect, it } from "vitest";
33
import { parse } from "yaml";
44

55
const PROOF_SCRIPT = "scripts/e2e/telegram-user-crabbox-proof.ts";
6+
const CREDENTIAL_SCRIPT = "scripts/e2e/telegram-user-credential.ts";
67
const USER_DRIVER = "scripts/e2e/telegram-user-driver.py";
8+
const QA_LAB_RUNTIME_API = "extensions/qa-lab/runtime-api.ts";
79
const PACKAGE_JSON = "package.json";
810
const WORKFLOW = ".github/workflows/mantis-telegram-desktop-proof.yml";
911
const LIVE_WORKFLOW = ".github/workflows/mantis-telegram-live.yml";
1012
const PROMPT = ".github/codex/prompts/mantis-telegram-desktop-proof.md";
13+
const DOCS = ["docs/help/testing.md", "docs/concepts/qa-e2e-automation.md"];
1114

1215
type WorkflowStep = {
1316
env?: Record<string, string>;
@@ -76,6 +79,13 @@ function jobStep(workflowFile: string, jobName: string, stepName: string): Workf
7679
return step;
7780
}
7881

82+
function filesUnder(root: string): string[] {
83+
return readdirSync(root).flatMap((name) => {
84+
const file = `${root}/${name}`;
85+
return statSync(file).isDirectory() ? filesUnder(file) : [file];
86+
});
87+
}
88+
7989
describe("Mantis Telegram Desktop proof workflow", () => {
8090
it("runs with the repository pnpm major", () => {
8191
const workflow = parse(readFileSync(WORKFLOW, "utf8")) as Workflow;
@@ -171,6 +181,30 @@ describe("Mantis Telegram Desktop proof workflow", () => {
171181
expect(readFileSync(USER_DRIVER, "utf8")).toContain("/usr/local/lib/libtdjson.so");
172182
});
173183

184+
it("keeps Telegram Desktop proof credentials out of the generic qa-lab API", () => {
185+
const packageJson = JSON.parse(readFileSync(PACKAGE_JSON, "utf8")) as {
186+
scripts?: Record<string, string>;
187+
};
188+
const workflowFiles = filesUnder(".github/workflows").filter((file) => file.endsWith(".yml"));
189+
const telegramUserWorkflows = workflowFiles.filter((file) =>
190+
readFileSync(file, "utf8").includes("telegram-user"),
191+
);
192+
193+
expect(readFileSync(QA_LAB_RUNTIME_API, "utf8")).not.toContain("telegram-user");
194+
expect(packageJson.scripts).not.toHaveProperty("qa:telegram-user:crabbox");
195+
expect(telegramUserWorkflows).toEqual([WORKFLOW]);
196+
for (const doc of DOCS) {
197+
expect(readFileSync(doc, "utf8")).not.toContain("pnpm qa:telegram-user:crabbox");
198+
}
199+
expect(readFileSync(PROOF_SCRIPT, "utf8")).not.toContain("pnpm qa:telegram-user:crabbox");
200+
expect(readFileSync(CREDENTIAL_SCRIPT, "utf8")).toContain(
201+
'const TELEGRAM_USER_QA_CREDENTIAL_KIND = "telegram-user";',
202+
);
203+
expect(readFileSync(CREDENTIAL_SCRIPT, "utf8")).toContain(
204+
"../qa/convex-credential-broker/convex/payload-validation.js",
205+
);
206+
});
207+
174208
it("authorizes Telegram Desktop from the leased TDLib user session", () => {
175209
const proofScript = readFileSync(PROOF_SCRIPT, "utf8");
176210
const userDriver = readFileSync(USER_DRIVER, "utf8");

0 commit comments

Comments
 (0)