diff --git a/.github/workflows/openclaw-release-checks.yml b/.github/workflows/openclaw-release-checks.yml index 30bc3bad0cb..b7b35de6681 100644 --- a/.github/workflows/openclaw-release-checks.yml +++ b/.github/workflows/openclaw-release-checks.yml @@ -667,8 +667,8 @@ jobs: --repo-root . \ --output-dir "${output_dir}" \ --provider-mode mock-openai \ - --model "${OPENCLAW_CI_OPENAI_MODEL}" \ - --alt-model openai/gpt-5.4-alt \ + --model mock-openai/gpt-5.5 \ + --alt-model mock-openai/gpt-5.5-alt \ --profile fast \ --fast ) @@ -755,8 +755,8 @@ jobs: --repo-root . \ --output-dir "${output_dir}" \ --provider-mode mock-openai \ - --model "${OPENCLAW_CI_OPENAI_MODEL}" \ - --alt-model openai/gpt-5.4-alt \ + --model mock-openai/gpt-5.5 \ + --alt-model mock-openai/gpt-5.5-alt \ --fast \ --credential-source convex \ --credential-role ci diff --git a/extensions/qa-lab/src/gateway-child.test.ts b/extensions/qa-lab/src/gateway-child.test.ts index 6c042046a1c..2036bb064ef 100644 --- a/extensions/qa-lab/src/gateway-child.test.ts +++ b/extensions/qa-lab/src/gateway-child.test.ts @@ -87,6 +87,7 @@ describe("buildQaRuntimeEnv", () => { expect(env.OPENCLAW_TEST_FAST).toBe("1"); expect(env.OPENCLAW_QA_ALLOW_LOCAL_IMAGE_PROVIDER).toBe("1"); expect(env.OPENCLAW_ALLOW_SLOW_REPLY_TESTS).toBe("1"); + expect(env.OPENCLAW_SKIP_STARTUP_MODEL_PREWARM).toBe("1"); expect(env.OPENCLAW_BUNDLED_PLUGINS_DIR).toBe("/tmp/openclaw-qa/bundled-plugins"); expect(env.OPENCLAW_COMPATIBILITY_HOST_VERSION).toBe("2026.4.8"); }); diff --git a/extensions/qa-lab/src/gateway-child.ts b/extensions/qa-lab/src/gateway-child.ts index 77847acf5df..894d4d5e828 100644 --- a/extensions/qa-lab/src/gateway-child.ts +++ b/extensions/qa-lab/src/gateway-child.ts @@ -212,6 +212,7 @@ export function buildQaRuntimeEnv(params: { OPENCLAW_SKIP_BROWSER_CONTROL_SERVER: "1", OPENCLAW_SKIP_GMAIL_WATCHER: "1", OPENCLAW_SKIP_CANVAS_HOST: "1", + OPENCLAW_SKIP_STARTUP_MODEL_PREWARM: "1", OPENCLAW_NO_RESPAWN: "1", OPENCLAW_TEST_FAST: "1", OPENCLAW_QA_ALLOW_LOCAL_IMAGE_PROVIDER: "1", diff --git a/extensions/qa-lab/src/live-transports/shared/live-gateway.runtime.test.ts b/extensions/qa-lab/src/live-transports/shared/live-gateway.runtime.test.ts index 22d87fefa58..26d0e6382a8 100644 --- a/extensions/qa-lab/src/live-transports/shared/live-gateway.runtime.test.ts +++ b/extensions/qa-lab/src/live-transports/shared/live-gateway.runtime.test.ts @@ -94,6 +94,54 @@ describe("startQaLiveLaneGateway", () => { expect(mockStop).toHaveBeenCalledTimes(1); }); + it("disables memory search for transport-only live lanes", async () => { + await startQaLiveLaneGateway({ + repoRoot: "/tmp/openclaw-repo", + transport: createStubTransport(), + transportBaseUrl: "http://127.0.0.1:43123", + providerMode: "mock-openai", + primaryModel: "mock-openai/gpt-5.5", + alternateModel: "mock-openai/gpt-5.5-alt", + controlUiEnabled: false, + }); + + const [{ mutateConfig }] = startQaGatewayChild.mock.calls[0] ?? []; + expect(typeof mutateConfig).toBe("function"); + const cfg = mutateConfig?.({ + plugins: { + allow: ["acpx", "memory-core", "qa-channel"], + entries: { + acpx: { enabled: true }, + "memory-core": { enabled: true }, + "qa-channel": { enabled: true }, + }, + }, + agents: { + defaults: { + memorySearch: { + enabled: true, + sync: { + onSearch: true, + onSessionStart: true, + watch: true, + }, + }, + }, + }, + }); + + expect(cfg?.plugins?.allow).toEqual(["acpx", "qa-channel"]); + expect(cfg?.plugins?.entries).not.toHaveProperty("memory-core"); + expect(cfg?.agents?.defaults?.memorySearch).toMatchObject({ + enabled: false, + sync: { + onSearch: false, + onSessionStart: false, + watch: false, + }, + }); + }); + it("forwards gateway stop options to the child harness", async () => { const harness = await startQaLiveLaneGateway({ repoRoot: "/tmp/openclaw-repo", diff --git a/extensions/qa-lab/src/live-transports/shared/live-gateway.runtime.ts b/extensions/qa-lab/src/live-transports/shared/live-gateway.runtime.ts index 9ad48c679b2..336bd64bf88 100644 --- a/extensions/qa-lab/src/live-transports/shared/live-gateway.runtime.ts +++ b/extensions/qa-lab/src/live-transports/shared/live-gateway.runtime.ts @@ -34,6 +34,44 @@ async function stopQaLiveLaneResources( } } +function omitMemoryCoreEntry | undefined>(entries: T): T { + if (!entries || !Object.prototype.hasOwnProperty.call(entries, "memory-core")) { + return entries; + } + const { "memory-core": _memoryCore, ...rest } = entries; + return rest as T; +} + +function prepareLiveTransportGatewayConfig(cfg: OpenClawConfig): OpenClawConfig { + const defaults = cfg.agents?.defaults ?? {}; + return { + ...cfg, + plugins: cfg.plugins + ? { + ...cfg.plugins, + allow: cfg.plugins.allow?.filter((pluginId) => pluginId !== "memory-core"), + entries: omitMemoryCoreEntry(cfg.plugins.entries), + } + : cfg.plugins, + agents: { + ...cfg.agents, + defaults: { + ...defaults, + memorySearch: { + ...defaults.memorySearch, + enabled: false, + sync: { + ...defaults.memorySearch?.sync, + onSearch: false, + onSessionStart: false, + watch: false, + }, + }, + }, + }, + }; +} + export async function startQaLiveLaneGateway(params: { repoRoot: string; command?: QaGatewayChildCommand; @@ -70,7 +108,8 @@ export async function startQaLiveLaneGateway(params: { thinkingDefault: params.thinkingDefault, claudeCliAuthMode: params.claudeCliAuthMode, controlUiEnabled: params.controlUiEnabled, - mutateConfig: params.mutateConfig, + mutateConfig: (cfg) => + prepareLiveTransportGatewayConfig(params.mutateConfig ? params.mutateConfig(cfg) : cfg), }); return { gateway, diff --git a/src/gateway/server-startup-post-attach.ts b/src/gateway/server-startup-post-attach.ts index a0d791b605c..3b533438b1e 100644 --- a/src/gateway/server-startup-post-attach.ts +++ b/src/gateway/server-startup-post-attach.ts @@ -23,6 +23,7 @@ const ACP_BACKEND_READY_TIMEOUT_MS = 5_000; const ACP_BACKEND_READY_POLL_MS = 50; const PRIMARY_MODEL_PREWARM_TIMEOUT_MS = 5_000; const STARTUP_PROVIDER_DISCOVERY_TIMEOUT_MS = 5_000; +const SKIP_STARTUP_MODEL_PREWARM_ENV = "OPENCLAW_SKIP_STARTUP_MODEL_PREWARM"; type Awaitable = T | Promise; @@ -43,6 +44,11 @@ function shouldCheckRestartSentinel(env: NodeJS.ProcessEnv = process.env): boole return !env.VITEST && env.NODE_ENV !== "test"; } +function shouldSkipStartupModelPrewarm(env: NodeJS.ProcessEnv = process.env): boolean { + const raw = env[SKIP_STARTUP_MODEL_PREWARM_ENV]?.trim().toLowerCase(); + return raw === "1" || raw === "true" || raw === "yes" || raw === "on"; +} + function shouldStartGatewayMemoryBackend(cfg: OpenClawConfig): boolean { return cfg.memory?.backend === "qmd"; } @@ -189,6 +195,9 @@ function schedulePrimaryModelPrewarm( }, prewarm: typeof prewarmConfiguredPrimaryModel = prewarmConfiguredPrimaryModel, ): void { + if (shouldSkipStartupModelPrewarm()) { + return; + } void measureStartup(params.startupTrace, "sidecars.model-prewarm", () => prewarmConfiguredPrimaryModelWithTimeout( { @@ -661,4 +670,5 @@ export const __testing = { prewarmConfiguredPrimaryModel, prewarmConfiguredPrimaryModelWithTimeout, schedulePrimaryModelPrewarm, + shouldSkipStartupModelPrewarm, }; diff --git a/src/gateway/server-startup.test.ts b/src/gateway/server-startup.test.ts index b25c31a6243..21b7dc2fa02 100644 --- a/src/gateway/server-startup.test.ts +++ b/src/gateway/server-startup.test.ts @@ -32,11 +32,12 @@ vi.mock("../agents/pi-embedded-runner/runtime.js", () => ({ })); let prewarmConfiguredPrimaryModel: typeof import("./server-startup.js").__testing.prewarmConfiguredPrimaryModel; +let shouldSkipStartupModelPrewarm: typeof import("./server-startup.js").__testing.shouldSkipStartupModelPrewarm; describe("gateway startup primary model warmup", () => { beforeAll(async () => { ({ - __testing: { prewarmConfiguredPrimaryModel }, + __testing: { prewarmConfiguredPrimaryModel, shouldSkipStartupModelPrewarm }, } = await import("./server-startup.js")); }); @@ -84,6 +85,20 @@ describe("gateway startup primary model warmup", () => { expect(piModelModuleLoadedMock).not.toHaveBeenCalled(); }); + it("honors the startup model prewarm skip env", () => { + expect(shouldSkipStartupModelPrewarm({})).toBe(false); + expect( + shouldSkipStartupModelPrewarm({ + OPENCLAW_SKIP_STARTUP_MODEL_PREWARM: "1", + }), + ).toBe(true); + expect( + shouldSkipStartupModelPrewarm({ + OPENCLAW_SKIP_STARTUP_MODEL_PREWARM: "true", + }), + ).toBe(true); + }); + it("skips static warmup for configured CLI backends", async () => { await prewarmConfiguredPrimaryModel({ cfg: {