test(telegram): support fixed-delay Mantis proof (#122642)

Punchcard-Session: amber-harbor-timber-mb
This commit is contained in:
Vincent Koc
2026-08-12 23:20:34 +08:00
committed by GitHub
parent 0f5984f4e1
commit 282e6a47ae
3 changed files with 64 additions and 0 deletions
@@ -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 <milliseconds>` 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
@@ -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 <name> Crabbox machine class. Default: standard.",
" --desktop-chat-title <name> Telegram Desktop chat to select before recording.",
" --human-delay-fixed-ms <ms> Set a fixed custom human delay before Gateway startup.",
" --id <cbx_id> Reuse an existing Crabbox desktop lease.",
" --keep-box Leave the Crabbox lease running for VNC debugging.",
" --link-preview <true|false> 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,
@@ -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"));