From 282e6a47ae6f9fa45251960d52382ba1cd65dbcd Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Wed, 12 Aug 2026 23:20:34 +0800 Subject: [PATCH] test(telegram): support fixed-delay Mantis proof (#122642) Punchcard-Session: amber-harbor-timber-mb --- .../prompts/mantis-telegram-desktop-proof.md | 1 + scripts/e2e/telegram-user-crabbox-proof.ts | 21 ++++++++++ .../telegram-user-crabbox-proof.test.ts | 42 +++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/.github/codex/prompts/mantis-telegram-desktop-proof.md b/.github/codex/prompts/mantis-telegram-desktop-proof.md index d36b0b63d39a..fc2643ea3a5f 100644 --- a/.github/codex/prompts/mantis-telegram-desktop-proof.md +++ b/.github/codex/prompts/mantis-telegram-desktop-proof.md @@ -140,6 +140,7 @@ than Telegram-visible behavior`. Use this manifest shape and do not create pass `--link-preview false` to `start`. The runner injects that setting into the isolated SUT config before Gateway startup. Do not edit the generated config or restart the Gateway to apply it. + To prove fixed pacing between streamed blocks, pass `--human-delay-fixed-ms ` to `start`. When the proof must show an in-place streamed edit, also pass `--mock-response-chunk-delay-ms 1200` and use a mock response long enough for the first chunk to clear the preview debounce. Capture both the initial diff --git a/scripts/e2e/telegram-user-crabbox-proof.ts b/scripts/e2e/telegram-user-crabbox-proof.ts index 19fcb3d33c6f..a180d80d8de2 100644 --- a/scripts/e2e/telegram-user-crabbox-proof.ts +++ b/scripts/e2e/telegram-user-crabbox-proof.ts @@ -69,6 +69,7 @@ type Options = { envFile?: string; expect: string[]; gatewayPort: number; + humanDelayFixedMs?: number; idleTimeout: string; keepBox: boolean; leaseId?: string; @@ -223,6 +224,7 @@ function usageText() { "Useful options:", " --class Crabbox machine class. Default: standard.", " --desktop-chat-title Telegram Desktop chat to select before recording.", + " --human-delay-fixed-ms Set a fixed custom human delay before Gateway startup.", " --id Reuse an existing Crabbox desktop lease.", " --keep-box Leave the Crabbox lease running for VNC debugging.", " --link-preview Set channels.telegram.linkPreview before Gateway startup.", @@ -402,6 +404,8 @@ export function parseArgs(argvInput: string[]): Options { opts.expect.push(readValue({ repeatable: true })); } else if (arg === "--gateway-port") { opts.gatewayPort = parseTcpPort(readValue(), "--gateway-port"); + } else if (arg === "--human-delay-fixed-ms") { + opts.humanDelayFixedMs = parsePositiveTimerMs(readValue(), "--human-delay-fixed-ms"); } else if (arg === "--id") { opts.leaseId = readValue(); } else if (arg === "--idle-timeout") { @@ -503,6 +507,9 @@ export function parseArgs(argvInput: string[]): Options { if (command === "publish" && !opts.publishPr) { throw new Error("publish requires --pr."); } + if (command !== "start" && opts.humanDelayFixedMs !== undefined) { + throw new Error("--human-delay-fixed-ms is available only for start sessions."); + } if (opts.mcpAppFixture && command !== "start") { throw new Error("--mcp-app-fixture is available only for start sessions."); } @@ -1241,6 +1248,7 @@ function telegramResultObject(value: unknown, label: string): JsonObject { export function writeSutConfig(params: { gatewayPort: number; groupId: string; + humanDelayFixedMs?: number; linkPreview?: boolean; mcpAppFixture?: boolean; mockPort: number; @@ -1257,6 +1265,15 @@ export function writeSutConfig(params: { const config = { agents: { defaults: { + ...(params.humanDelayFixedMs === undefined + ? {} + : { + humanDelay: { + maxMs: params.humanDelayFixedMs, + minMs: params.humanDelayFixedMs, + mode: "custom", + }, + }), model: { primary: "openai/gpt-5.6-luna" }, models: { "openai/gpt-5.6-luna": { params: { openaiWsWarmup: false, transport: "sse" } }, @@ -1368,6 +1385,7 @@ export async function startLocalSut( params: { gatewayPort: number; groupId: string; + humanDelayFixedMs?: number; mockResponseText: string; mockPort: number; linkPreview?: boolean; @@ -1668,6 +1686,7 @@ async function startLocalSutDaemon(params: { funnelBridge?: FunnelBridge; gatewayPort: number; groupId: string; + humanDelayFixedMs?: number; mockResponseText: string; mockPort: number; linkPreview?: boolean; @@ -2888,6 +2907,7 @@ async function startSession(root: string, opts: Options, outputDir: string) { funnelBridge, gatewayPort: opts.gatewayPort, groupId: credential.groupId, + humanDelayFixedMs: opts.humanDelayFixedMs, linkPreview: opts.linkPreview, mockResponseText: opts.mockResponseText, mockResponseChunkDelayMs: opts.mockResponseChunkDelayMs, @@ -3508,6 +3528,7 @@ async function main() { const sutRuntime = await startLocalSut({ gatewayPort: opts.gatewayPort, groupId: credential.groupId, + humanDelayFixedMs: opts.humanDelayFixedMs, linkPreview: opts.linkPreview, mockResponseText: opts.mockResponseText, mockResponseChunkDelayMs: opts.mockResponseChunkDelayMs, diff --git a/test/scripts/telegram-user-crabbox-proof.test.ts b/test/scripts/telegram-user-crabbox-proof.test.ts index 755351ad9f93..4066e327f87f 100644 --- a/test/scripts/telegram-user-crabbox-proof.test.ts +++ b/test/scripts/telegram-user-crabbox-proof.test.ts @@ -388,6 +388,19 @@ describe("telegram user Crabbox proof log polling", () => { ); }); + it("accepts only a positive fixed human delay", () => { + expect(parseArgs(["start", "--human-delay-fixed-ms", "1200"]).humanDelayFixedMs).toBe(1200); + expect(() => parseArgs(["start", "--human-delay-fixed-ms", "0"])).toThrow( + "--human-delay-fixed-ms must be a positive integer.", + ); + expect(() => parseArgs(["start", "--human-delay-fixed-ms", "1e3"])).toThrow( + "--human-delay-fixed-ms must be a positive integer.", + ); + expect(() => + parseArgs(["send", "--session", "session.json", "--human-delay-fixed-ms", "1200"]), + ).toThrow("--human-delay-fixed-ms is available only for start sessions."); + }); + it("rejects duplicate single-value proof controls while keeping repeated expectations", () => { expect(() => parseArgs(["--output-dir", ".artifacts/one", "--output-dir", ".artifacts/two"]), @@ -498,6 +511,35 @@ describe("telegram user Crabbox proof log polling", () => { expect(defaultConfig.channels.telegram).not.toHaveProperty("linkPreview"); }); + it("injects the requested fixed human delay before startup", () => { + const delayedConfigRoot = writeSutConfig({ + gatewayPort: 19042, + groupId: "group", + humanDelayFixedMs: 1200, + mockPort: 19043, + outputDir: makeTempDir(tempDirs, "openclaw-telegram-proof-"), + testerId: "tester", + }); + const defaultConfigRoot = writeSutConfig({ + gatewayPort: 19044, + groupId: "group", + mockPort: 19045, + outputDir: makeTempDir(tempDirs, "openclaw-telegram-proof-"), + testerId: "tester", + }); + tempDirs.push(delayedConfigRoot.tempRoot, defaultConfigRoot.tempRoot); + + const delayedConfig = JSON.parse(fs.readFileSync(delayedConfigRoot.configPath, "utf8")); + const defaultConfig = JSON.parse(fs.readFileSync(defaultConfigRoot.configPath, "utf8")); + + expect(delayedConfig.agents.defaults.humanDelay).toEqual({ + maxMs: 1200, + minMs: 1200, + mode: "custom", + }); + expect(defaultConfig.agents.defaults).not.toHaveProperty("humanDelay"); + }); + it("pins the browser fixture SDK and exposes only the required app capabilities", () => { const fixture = fs.readFileSync("scripts/e2e/mcp-app-conformance-server.mjs", "utf8"); const uiPackage = JSON.parse(fs.readFileSync("ui/package.json", "utf8"));