From fd166a5318e0303e896ce7cdaf923f881d28c3e2 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 16 Jun 2026 21:11:16 +0200 Subject: [PATCH] fix(qa): reject loose code-mode live task limits --- scripts/repro/code-mode-namespace-live.ts | 41 +++++++++++++------ scripts/test-projects.test-support.mjs | 1 + test/scripts/code-mode-namespace-live.test.ts | 23 +++++++++++ test/scripts/test-projects.test.ts | 7 ++++ 4 files changed, 60 insertions(+), 12 deletions(-) create mode 100644 test/scripts/code-mode-namespace-live.test.ts diff --git a/scripts/repro/code-mode-namespace-live.ts b/scripts/repro/code-mode-namespace-live.ts index bc27021f912..cdea3464df3 100755 --- a/scripts/repro/code-mode-namespace-live.ts +++ b/scripts/repro/code-mode-namespace-live.ts @@ -1,6 +1,7 @@ #!/usr/bin/env -S node --import tsx // Code Mode Namespace Live script supports OpenClaw repository automation. import { performance } from "node:perf_hooks"; +import { pathToFileURL } from "node:url"; import { Type } from "typebox"; import type { Model } from "../../packages/agent-core/src/llm.js"; import type { AgentEvent, AgentTool } from "../../packages/agent-core/src/types.js"; @@ -80,6 +81,7 @@ type RunMetrics = { }; const PLUGIN_ID = "fictions-live"; +const POSITIVE_INTEGER_PATTERN = /^[1-9]\d*$/u; function cloneState(): FictionServiceState { return { @@ -598,19 +600,32 @@ function readArg(name: string): string | undefined { return match?.slice(prefix.length); } -async function main() { +export function parseTaskLimit(raw: string | undefined, label: string): number { + const text = raw?.trim() ?? "3"; + if (!POSITIVE_INTEGER_PATTERN.test(text)) { + throw new Error(`${label} must be a positive integer`); + } + const parsed = Number(text); + if (!Number.isSafeInteger(parsed)) { + throw new Error(`${label} must be a safe positive integer`); + } + return parsed; +} + +export async function main() { + const model = readArg("model") ?? process.env.OPENCLAW_CODE_MODE_LIVE_MODEL ?? "gpt-5.4-mini"; + const modeArg = readArg("modes"); + const modes = (modeArg ? modeArg.split(",") : ["regular", "code-namespace"]) as Mode[]; + const taskArg = readArg("tasks"); + const taskLimit = parseTaskLimit( + taskArg ?? process.env.OPENCLAW_CODE_MODE_LIVE_TASKS, + taskArg === undefined ? "OPENCLAW_CODE_MODE_LIVE_TASKS" : "--tasks", + ); const apiKey = process.env.OPENAI_API_KEY?.trim(); if (!apiKey) { throw new Error("OPENAI_API_KEY is required"); } - const model = readArg("model") ?? process.env.OPENCLAW_CODE_MODE_LIVE_MODEL ?? "gpt-5.4-mini"; - const modeArg = readArg("modes"); - const modes = (modeArg ? modeArg.split(",") : ["regular", "code-namespace"]) as Mode[]; - const taskLimit = Number(readArg("tasks") ?? process.env.OPENCLAW_CODE_MODE_LIVE_TASKS ?? "3"); - const selectedTasks = tasks.slice( - 0, - Number.isFinite(taskLimit) && taskLimit > 0 ? taskLimit : tasks.length, - ); + const selectedTasks = tasks.slice(0, taskLimit); const results: RunMetrics[] = []; for (const task of selectedTasks) { for (const mode of modes) { @@ -640,6 +655,8 @@ async function main() { } } -await main().finally(() => { - clearCodeModeNamespacesForPlugin(PLUGIN_ID); -}); +if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) { + await main().finally(() => { + clearCodeModeNamespacesForPlugin(PLUGIN_ID); + }); +} diff --git a/scripts/test-projects.test-support.mjs b/scripts/test-projects.test-support.mjs index 6078243d235..84a55098073 100644 --- a/scripts/test-projects.test-support.mjs +++ b/scripts/test-projects.test-support.mjs @@ -589,6 +589,7 @@ const TOOLING_SOURCE_TEST_TARGETS = new Map([ ["scripts/tsdown-build.mjs", ["test/scripts/tsdown-build.test.ts"]], ["scripts/verify.mjs", ["test/scripts/verify.test.ts"]], ["scripts/zai-fallback-repro.ts", ["test/scripts/zai-fallback-repro.test.ts"]], + ["scripts/repro/code-mode-namespace-live.ts", ["test/scripts/code-mode-namespace-live.test.ts"]], ["scripts/lib/extension-test-plan.mjs", ["test/scripts/test-extension.test.ts"]], ["scripts/lib/vitest-batch-runner.mjs", ["test/scripts/test-extension.test.ts"]], ["scripts/lib/ci-node-test-plan.mjs", ["test/scripts/ci-node-test-plan.test.ts"]], diff --git a/test/scripts/code-mode-namespace-live.test.ts b/test/scripts/code-mode-namespace-live.test.ts new file mode 100644 index 00000000000..77d2daa8005 --- /dev/null +++ b/test/scripts/code-mode-namespace-live.test.ts @@ -0,0 +1,23 @@ +// Code Mode Namespace Live tests cover live repro argument parsing. +import { describe, expect, it } from "vitest"; +import { parseTaskLimit } from "../../scripts/repro/code-mode-namespace-live.ts"; + +describe("code-mode namespace live repro", () => { + it("parses task limits as strict positive integers", () => { + expect(parseTaskLimit(undefined, "--tasks")).toBe(3); + expect(parseTaskLimit(" 2 ", "--tasks")).toBe(2); + + expect(() => parseTaskLimit("0", "--tasks")).toThrow("--tasks must be a positive integer"); + expect(() => parseTaskLimit("1e3", "--tasks")).toThrow("--tasks must be a positive integer"); + expect(() => parseTaskLimit("2.5", "--tasks")).toThrow("--tasks must be a positive integer"); + expect(() => parseTaskLimit("3 tasks", "--tasks")).toThrow( + "--tasks must be a positive integer", + ); + }); + + it("reports the environment variable name for inherited task limits", () => { + expect(() => parseTaskLimit("1e3", "OPENCLAW_CODE_MODE_LIVE_TASKS")).toThrow( + "OPENCLAW_CODE_MODE_LIVE_TASKS must be a positive integer", + ); + }); +}); diff --git a/test/scripts/test-projects.test.ts b/test/scripts/test-projects.test.ts index 7a0c26ec4d4..9a3caf2dfe5 100644 --- a/test/scripts/test-projects.test.ts +++ b/test/scripts/test-projects.test.ts @@ -335,6 +335,13 @@ describe("scripts/test-projects changed-target routing", () => { }); }); + it("routes code-mode namespace live repro changes through its regression test", () => { + expect(resolveChangedTestTargetPlan(["scripts/repro/code-mode-namespace-live.ts"])).toEqual({ + mode: "targets", + targets: ["test/scripts/code-mode-namespace-live.test.ts"], + }); + }); + it("routes group visible reply config changes through channel delivery regressions", () => { expect( resolveChangedTestTargetPlan([