diff --git a/extensions/openai/openai-codex-provider.ts b/extensions/openai/openai-codex-provider.ts index 4b085104aea..3da270b88c2 100644 --- a/extensions/openai/openai-codex-provider.ts +++ b/extensions/openai/openai-codex-provider.ts @@ -52,9 +52,6 @@ const OPENAI_CODEX_MODERN_MODEL_IDS = [ "gpt-5.2-codex", OPENAI_CODEX_GPT_53_MODEL_ID, OPENAI_CODEX_GPT_53_SPARK_MODEL_ID, - "gpt-5.1-codex", - "gpt-5.1-codex-mini", - "gpt-5.1-codex-max", ] as const; function isOpenAICodexBaseUrl(baseUrl?: string): boolean { diff --git a/extensions/openai/openai-provider.test.ts b/extensions/openai/openai-provider.test.ts index bf998225216..08390c9df96 100644 --- a/extensions/openai/openai-provider.test.ts +++ b/extensions/openai/openai-provider.test.ts @@ -1,5 +1,6 @@ import OpenAI from "openai"; import { describe, expect, it } from "vitest"; +import { buildOpenAICodexProviderPlugin } from "./openai-codex-provider.js"; import { buildOpenAIProvider } from "./openai-provider.js"; const OPENAI_API_KEY = process.env.OPENAI_API_KEY ?? ""; @@ -172,6 +173,55 @@ describe("buildOpenAIProvider", () => { name: "gpt-5.4-nano", }); }); + + it("keeps modern live selection on OpenAI 5.2+ and Codex 5.2+", () => { + const provider = buildOpenAIProvider(); + const codexProvider = buildOpenAICodexProviderPlugin(); + + expect( + provider.isModernModelRef?.({ + provider: "openai", + modelId: "gpt-5.0", + } as never), + ).toBe(false); + expect( + provider.isModernModelRef?.({ + provider: "openai", + modelId: "gpt-5.2", + } as never), + ).toBe(true); + expect( + provider.isModernModelRef?.({ + provider: "openai", + modelId: "gpt-5.4", + } as never), + ).toBe(true); + + expect( + codexProvider.isModernModelRef?.({ + provider: "openai-codex", + modelId: "gpt-5.1-codex", + } as never), + ).toBe(false); + expect( + codexProvider.isModernModelRef?.({ + provider: "openai-codex", + modelId: "gpt-5.1-codex-max", + } as never), + ).toBe(false); + expect( + codexProvider.isModernModelRef?.({ + provider: "openai-codex", + modelId: "gpt-5.2-codex", + } as never), + ).toBe(true); + expect( + codexProvider.isModernModelRef?.({ + provider: "openai-codex", + modelId: "gpt-5.4", + } as never), + ).toBe(true); + }); }); describeLive("buildOpenAIProvider live", () => { diff --git a/extensions/openai/openai-provider.ts b/extensions/openai/openai-provider.ts index dfc38aa706a..91129328f25 100644 --- a/extensions/openai/openai-provider.ts +++ b/extensions/openai/openai-provider.ts @@ -46,7 +46,6 @@ const OPENAI_MODERN_MODEL_IDS = [ "gpt-5.4-mini", "gpt-5.4-nano", "gpt-5.2", - "gpt-5.0", ] as const; const OPENAI_DIRECT_SPARK_MODEL_ID = "gpt-5.3-codex-spark"; const SUPPRESSED_SPARK_PROVIDERS = new Set(["openai", "azure-openai-responses"]); diff --git a/scripts/copy-bundled-plugin-metadata.mjs b/scripts/copy-bundled-plugin-metadata.mjs index 12211f9b29b..e0add010b15 100644 --- a/scripts/copy-bundled-plugin-metadata.mjs +++ b/scripts/copy-bundled-plugin-metadata.mjs @@ -1,6 +1,7 @@ import fs from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; +import { shouldBuildBundledCluster } from "./lib/optional-bundled-clusters.mjs"; import { removeFileIfExists, removePathIfExists, @@ -163,8 +164,16 @@ function copyDeclaredPluginSkillPaths(params) { return copiedSkills; } +/** + * @param {{ + * cwd?: string; + * repoRoot?: string; + * env?: NodeJS.ProcessEnv; + * }} [params] + */ export function copyBundledPluginMetadata(params = {}) { const repoRoot = params.cwd ?? params.repoRoot ?? process.cwd(); + const env = params.env ?? process.env; const extensionsRoot = path.join(repoRoot, "extensions"); const distExtensionsRoot = path.join(repoRoot, "dist", "extensions"); if (!fs.existsSync(extensionsRoot)) { @@ -176,11 +185,17 @@ export function copyBundledPluginMetadata(params = {}) { if (!dirent.isDirectory()) { continue; } - sourcePluginDirs.add(dirent.name); const pluginDir = path.join(extensionsRoot, dirent.name); const manifestPath = path.join(pluginDir, "openclaw.plugin.json"); const distPluginDir = path.join(distExtensionsRoot, dirent.name); + if (!shouldBuildBundledCluster(dirent.name, env)) { + removePathIfExists(distPluginDir); + continue; + } + + sourcePluginDirs.add(dirent.name); + const distManifestPath = path.join(distPluginDir, "openclaw.plugin.json"); const distPackageJsonPath = path.join(distPluginDir, "package.json"); if (!fs.existsSync(manifestPath)) { diff --git a/scripts/docker/cleanup-smoke/Dockerfile b/scripts/docker/cleanup-smoke/Dockerfile index f214ffbabf4..b5fbc372a28 100644 --- a/scripts/docker/cleanup-smoke/Dockerfile +++ b/scripts/docker/cleanup-smoke/Dockerfile @@ -15,6 +15,10 @@ RUN --mount=type=cache,id=openclaw-cleanup-smoke-apt-cache,target=/var/cache/apt WORKDIR /repo COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +COPY ui/package.json ./ui/package.json +COPY packages ./packages +COPY extensions ./extensions +COPY patches ./patches RUN --mount=type=cache,id=openclaw-pnpm-store,target=/root/.local/share/pnpm/store,sharing=locked \ corepack enable \ && pnpm install --frozen-lockfile diff --git a/scripts/test-live-gateway-models-docker.sh b/scripts/test-live-gateway-models-docker.sh index a3e1036171f..3597e89e8ab 100755 --- a/scripts/test-live-gateway-models-docker.sh +++ b/scripts/test-live-gateway-models-docker.sh @@ -45,6 +45,11 @@ tar -C /src \ -cf - . | tar -C "$tmp_dir" -xf - ln -s /app/node_modules "$tmp_dir/node_modules" ln -s /app/dist "$tmp_dir/dist" +if [ -d /app/dist-runtime/extensions ]; then + export OPENCLAW_BUNDLED_PLUGINS_DIR=/app/dist-runtime/extensions +elif [ -d /app/dist/extensions ]; then + export OPENCLAW_BUNDLED_PLUGINS_DIR=/app/dist/extensions +fi cd "$tmp_dir" pnpm test:live EOF diff --git a/scripts/test-live-models-docker.sh b/scripts/test-live-models-docker.sh index c1cec5b2740..56c57d1e3e7 100755 --- a/scripts/test-live-models-docker.sh +++ b/scripts/test-live-models-docker.sh @@ -45,6 +45,11 @@ tar -C /src \ -cf - . | tar -C "$tmp_dir" -xf - ln -s /app/node_modules "$tmp_dir/node_modules" ln -s /app/dist "$tmp_dir/dist" +if [ -d /app/dist-runtime/extensions ]; then + export OPENCLAW_BUNDLED_PLUGINS_DIR=/app/dist-runtime/extensions +elif [ -d /app/dist/extensions ]; then + export OPENCLAW_BUNDLED_PLUGINS_DIR=/app/dist/extensions +fi cd "$tmp_dir" pnpm test:live EOF diff --git a/src/agents/models.profiles.live.test.ts b/src/agents/models.profiles.live.test.ts index 87cbbb6a203..8aa04447c30 100644 --- a/src/agents/models.profiles.live.test.ts +++ b/src/agents/models.profiles.live.test.ts @@ -603,6 +603,11 @@ describeLive("live models (profile keys)", () => { logProgress(`${progressLabel}: rate limit, retrying with next key`); continue; } + if (model.provider === "anthropic" && isAnthropicRateLimitError(message)) { + skipped.push({ model: id, reason: message }); + logProgress(`${progressLabel}: skip (anthropic rate limit)`); + break; + } if (model.provider === "anthropic" && isAnthropicBillingError(message)) { if (attempt + 1 < attemptMax) { logProgress(`${progressLabel}: billing issue, retrying with next key`); diff --git a/src/gateway/gateway-models.profiles.live.test.ts b/src/gateway/gateway-models.profiles.live.test.ts index 973cf952d16..3abadfd93e3 100644 --- a/src/gateway/gateway-models.profiles.live.test.ts +++ b/src/gateway/gateway-models.profiles.live.test.ts @@ -1209,6 +1209,11 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) { logProgress(`${progressLabel}: rate limit, retrying with next key`); continue; } + if (model.provider === "anthropic" && isAnthropicRateLimitError(message)) { + skippedCount += 1; + logProgress(`${progressLabel}: skip (anthropic rate limit)`); + break; + } if (model.provider === "anthropic" && isAnthropicBillingError(message)) { if (attempt + 1 < attemptMax) { logProgress(`${progressLabel}: billing issue, retrying with next key`); diff --git a/src/plugins/copy-bundled-plugin-metadata.test.ts b/src/plugins/copy-bundled-plugin-metadata.test.ts index 48fe75cf02b..5c4163610a1 100644 --- a/src/plugins/copy-bundled-plugin-metadata.test.ts +++ b/src/plugins/copy-bundled-plugin-metadata.test.ts @@ -8,6 +8,11 @@ import { } from "../../scripts/copy-bundled-plugin-metadata.mjs"; const tempDirs: string[] = []; +const includeOptionalEnv = { OPENCLAW_INCLUDE_OPTIONAL_BUNDLED: "1" } as const; +const copyBundledPluginMetadataWithEnv = copyBundledPluginMetadata as (params?: { + repoRoot?: string; + env?: NodeJS.ProcessEnv; +}) => void; function makeRepoRoot(prefix: string): string { const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); @@ -55,7 +60,7 @@ describe("copyBundledPluginMetadata", () => { openclaw: { extensions: ["./index.ts"] }, }); - copyBundledPluginMetadata({ repoRoot }); + copyBundledPluginMetadataWithEnv({ repoRoot, env: includeOptionalEnv }); expect( fs.existsSync(path.join(repoRoot, "dist", "extensions", "acpx", "openclaw.plugin.json")), @@ -126,7 +131,7 @@ describe("copyBundledPluginMetadata", () => { fs.mkdirSync(staleNodeModulesSkillDir, { recursive: true }); fs.writeFileSync(path.join(staleNodeModulesSkillDir, "stale.txt"), "stale\n", "utf8"); - copyBundledPluginMetadata({ repoRoot }); + copyBundledPluginMetadataWithEnv({ repoRoot, env: includeOptionalEnv }); const copiedSkillDir = path.join( repoRoot, @@ -169,7 +174,7 @@ describe("copyBundledPluginMetadata", () => { openclaw: { extensions: ["./index.ts"] }, }); - copyBundledPluginMetadata({ repoRoot }); + copyBundledPluginMetadataWithEnv({ repoRoot, env: includeOptionalEnv }); expect( fs.readFileSync( @@ -222,7 +227,7 @@ describe("copyBundledPluginMetadata", () => { const staleNodeModulesDir = path.join(repoRoot, "dist", "extensions", "tlon", "node_modules"); fs.mkdirSync(staleNodeModulesDir, { recursive: true }); - copyBundledPluginMetadata({ repoRoot }); + copyBundledPluginMetadataWithEnv({ repoRoot, env: includeOptionalEnv }); const bundledManifest = JSON.parse( fs.readFileSync( @@ -264,7 +269,7 @@ describe("copyBundledPluginMetadata", () => { }); try { - copyBundledPluginMetadata({ repoRoot }); + copyBundledPluginMetadataWithEnv({ repoRoot, env: includeOptionalEnv }); } finally { cpSyncSpy.mockRestore(); } @@ -314,7 +319,7 @@ describe("copyBundledPluginMetadata", () => { }); fs.mkdirSync(path.join(repoRoot, "extensions"), { recursive: true }); - copyBundledPluginMetadata({ repoRoot }); + copyBundledPluginMetadataWithEnv({ repoRoot, env: includeOptionalEnv }); expect(fs.existsSync(path.join(repoRoot, "dist", "extensions", "removed-plugin"))).toBe(false); }); @@ -334,8 +339,26 @@ describe("copyBundledPluginMetadata", () => { name: "@openclaw/google-gemini-cli-auth", }); - copyBundledPluginMetadata({ repoRoot }); + copyBundledPluginMetadataWithEnv({ repoRoot, env: includeOptionalEnv }); expect(fs.existsSync(staleDistDir)).toBe(false); }); + + it("skips metadata for optional bundled clusters unless explicitly enabled", () => { + const repoRoot = makeRepoRoot("openclaw-bundled-plugin-optional-skip-"); + const pluginDir = path.join(repoRoot, "extensions", "acpx"); + fs.mkdirSync(pluginDir, { recursive: true }); + writeJson(path.join(pluginDir, "openclaw.plugin.json"), { + id: "acpx", + configSchema: { type: "object" }, + }); + writeJson(path.join(pluginDir, "package.json"), { + name: "@openclaw/acpx-plugin", + openclaw: { extensions: ["./index.ts"] }, + }); + + copyBundledPluginMetadataWithEnv({ repoRoot, env: {} }); + + expect(fs.existsSync(path.join(repoRoot, "dist", "extensions", "acpx"))).toBe(false); + }); }); diff --git a/src/plugins/runtime/types-channel.ts b/src/plugins/runtime/types-channel.ts index 5712f50eb31..5b81aa06161 100644 --- a/src/plugins/runtime/types-channel.ts +++ b/src/plugins/runtime/types-channel.ts @@ -178,14 +178,9 @@ export type PluginRuntimeChannel = { clearReplyMarkup: ( chatIdInput: string | number, messageIdInput: string | number, - opts?: { - token?: string; - accountId?: string; - verbose?: boolean; - api?: Partial; - retry?: import("../../infra/retry.js").RetryConfig; - cfg?: ReturnType; - }, + opts?: Parameters< + typeof import("../../plugin-sdk/telegram.js").editMessageReplyMarkupTelegram + >[3], ) => Promise<{ ok: true; messageId: string; chatId: string }>; deleteMessage: typeof import("../../plugin-sdk/telegram.js").deleteMessageTelegram; renameTopic: typeof import("../../plugin-sdk/telegram.js").renameForumTopicTelegram;