From 2a5c3556882d2ed672de422aa9a07723c604f28d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 6 Apr 2026 04:16:18 +0100 Subject: [PATCH] fix(ci): patch main regression surfaces --- .github/workflows/ci.yml | 8 ++- docs/plugins/sdk-migration.md | 1 + docs/plugins/sdk-overview.md | 1 + docs/tools/image-generation.md | 12 ++-- docs/tools/video-generation.md | 56 +++++++++---------- extensions/minimax/index.test.ts | 2 + extensions/whatsapp/test-api.ts | 1 + package.json | 4 ++ scripts/check-gateway-watch-regression.mjs | 5 ++ scripts/ci-changed-scope.d.mts | 1 + scripts/ci-changed-scope.mjs | 23 +++++++- scripts/lib/plugin-sdk-entrypoints.json | 1 + src/channels/plugins/contracts/registry.ts | 15 ++--- src/infra/scripts-modules.d.ts | 1 + src/plugin-sdk/media-generation-runtime.ts | 3 + src/scripts/ci-changed-scope.test.ts | 38 +++++++++++++ .../channels/channel-catalog-contract.ts | 1 + 17 files changed, 127 insertions(+), 46 deletions(-) create mode 100644 src/plugin-sdk/media-generation-runtime.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc3b91763a6..7d6f7b9bdb0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,7 @@ jobs: run_check_additional: ${{ steps.manifest.outputs.run_check_additional }} run_build_smoke: ${{ steps.manifest.outputs.run_build_smoke }} run_check_docs: ${{ steps.manifest.outputs.run_check_docs }} + run_control_ui_i18n: ${{ steps.manifest.outputs.run_control_ui_i18n }} run_checks_windows: ${{ steps.manifest.outputs.run_checks_windows }} checks_windows_matrix: ${{ steps.manifest.outputs.checks_windows_matrix }} run_macos_node: ${{ steps.manifest.outputs.run_macos_node }} @@ -128,6 +129,7 @@ jobs: OPENCLAW_CI_RUN_ANDROID: ${{ steps.changed_scope.outputs.run_android || 'false' }} OPENCLAW_CI_RUN_WINDOWS: ${{ steps.changed_scope.outputs.run_windows || 'false' }} OPENCLAW_CI_RUN_SKILLS_PYTHON: ${{ steps.changed_scope.outputs.run_skills_python || 'false' }} + OPENCLAW_CI_RUN_CONTROL_UI_I18N: ${{ steps.changed_scope.outputs.run_control_ui_i18n || 'false' }} OPENCLAW_CI_HAS_CHANGED_EXTENSIONS: ${{ steps.changed_extensions.outputs.has_changed_extensions || 'false' }} OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX: ${{ steps.changed_extensions.outputs.changed_extensions_matrix || '{"include":[]}' }} run: | @@ -165,6 +167,8 @@ jobs: const runAndroid = parseBoolean(process.env.OPENCLAW_CI_RUN_ANDROID) && !docsOnly; const runWindows = parseBoolean(process.env.OPENCLAW_CI_RUN_WINDOWS) && !docsOnly; const runSkillsPython = parseBoolean(process.env.OPENCLAW_CI_RUN_SKILLS_PYTHON) && !docsOnly; + const runControlUiI18n = + parseBoolean(process.env.OPENCLAW_CI_RUN_CONTROL_UI_I18N) && !docsOnly; const hasChangedExtensions = parseBoolean(process.env.OPENCLAW_CI_HAS_CHANGED_EXTENSIONS) && !docsOnly; const changedExtensionsMatrix = hasChangedExtensions @@ -241,6 +245,7 @@ jobs: run_check_additional: runNode, run_build_smoke: runNode, run_check_docs: docsChanged, + run_control_ui_i18n: runControlUiI18n, run_skills_python_job: runSkillsPython, run_checks_windows: runWindows, checks_windows_matrix: createMatrix( @@ -745,6 +750,7 @@ jobs: - name: Check control UI locale sync id: control_ui_i18n + if: needs.preflight.outputs.run_control_ui_i18n == 'true' continue-on-error: true run: pnpm ui:i18n:check @@ -780,7 +786,7 @@ jobs: EXTENSION_PLUGIN_SDK_INTERNAL_BOUNDARY_OUTCOME: ${{ steps.extension_plugin_sdk_internal_boundary.outcome }} EXTENSION_RELATIVE_OUTSIDE_PACKAGE_BOUNDARY_OUTCOME: ${{ steps.extension_relative_outside_package_boundary.outcome }} NO_RAW_WINDOW_OPEN_OUTCOME: ${{ steps.no_raw_window_open.outcome }} - CONTROL_UI_I18N_OUTCOME: ${{ steps.control_ui_i18n.outcome }} + CONTROL_UI_I18N_OUTCOME: ${{ steps.control_ui_i18n.outcome == 'skipped' && 'success' || steps.control_ui_i18n.outcome }} GATEWAY_WATCH_REGRESSION_OUTCOME: ${{ steps.gateway_watch_regression.outcome }} run: | failures=0 diff --git a/docs/plugins/sdk-migration.md b/docs/plugins/sdk-migration.md index e94a0fb031a..85ca363d40b 100644 --- a/docs/plugins/sdk-migration.md +++ b/docs/plugins/sdk-migration.md @@ -255,6 +255,7 @@ Current bundled provider examples: | `plugin-sdk/provider-stream` | Provider stream wrapper helpers | `ProviderStreamFamily`, `buildProviderStreamFamilyHooks`, `composeProviderStreamWrappers`, stream wrapper types, and shared Anthropic/Bedrock/Google/Kilocode/Moonshot/OpenAI/OpenRouter/Z.A.I/MiniMax/Copilot wrapper helpers | | `plugin-sdk/keyed-async-queue` | Ordered async queue | `KeyedAsyncQueue` | | `plugin-sdk/media-runtime` | Shared media helpers | Media fetch/transform/store helpers plus media payload builders | + | `plugin-sdk/media-generation-runtime` | Shared media-generation helpers | Shared failover helpers, candidate selection, and missing-model messaging for image/video/music generation | | `plugin-sdk/media-understanding` | Media-understanding helpers | Media understanding provider types plus provider-facing image/audio helper exports | | `plugin-sdk/text-runtime` | Shared text helpers | Assistant-visible-text stripping, markdown render/chunking/table helpers, redaction helpers, directive-tag helpers, safe-text utilities, and related text/logging helpers | | `plugin-sdk/text-chunking` | Text chunking helpers | Outbound text chunking helper | diff --git a/docs/plugins/sdk-overview.md b/docs/plugins/sdk-overview.md index 5a2cd9d7544..6186b6f51ce 100644 --- a/docs/plugins/sdk-overview.md +++ b/docs/plugins/sdk-overview.md @@ -223,6 +223,7 @@ explicitly promotes one as public. | Subpath | Key exports | | --- | --- | | `plugin-sdk/media-runtime` | Shared media fetch/transform/store helpers plus media payload builders | + | `plugin-sdk/media-generation-runtime` | Shared media-generation failover helpers, candidate selection, and missing-model messaging | | `plugin-sdk/media-understanding` | Media understanding provider types plus provider-facing image/audio helper exports | | `plugin-sdk/text-runtime` | Shared text/markdown/logging helpers such as assistant-visible-text stripping, markdown render/chunking/table helpers, redaction helpers, directive-tag helpers, and safe-text utilities | | `plugin-sdk/text-chunking` | Outbound text chunking helper | diff --git a/docs/tools/image-generation.md b/docs/tools/image-generation.md index 990e27216cc..719f51efa84 100644 --- a/docs/tools/image-generation.md +++ b/docs/tools/image-generation.md @@ -124,13 +124,13 @@ MiniMax image generation is available through both bundled MiniMax auth paths: ## Provider capabilities -| Capability | OpenAI | Google | fal | MiniMax | ComfyUI | Vydra | -| --------------------- | -------------------- | -------------------- | ------------------- | -------------------------- | ---------------------------------- | ----- | +| Capability | OpenAI | Google | fal | MiniMax | ComfyUI | Vydra | +| --------------------- | -------------------- | -------------------- | ------------------- | -------------------------- | ---------------------------------- | ------- | | Generate | Yes (up to 4) | Yes (up to 4) | Yes (up to 4) | Yes (up to 9) | Yes (workflow-defined outputs) | Yes (1) | -| Edit/reference | Yes (up to 5 images) | Yes (up to 5 images) | Yes (1 image) | Yes (1 image, subject ref) | Yes (1 image, workflow-configured) | No | -| Size control | Yes | Yes | Yes | No | No | No | -| Aspect ratio | No | Yes | Yes (generate only) | Yes | No | No | -| Resolution (1K/2K/4K) | No | Yes | Yes | No | No | No | +| Edit/reference | Yes (up to 5 images) | Yes (up to 5 images) | Yes (1 image) | Yes (1 image, subject ref) | Yes (1 image, workflow-configured) | No | +| Size control | Yes | Yes | Yes | No | No | No | +| Aspect ratio | No | Yes | Yes (generate only) | Yes | No | No | +| Resolution (1K/2K/4K) | No | Yes | Yes | No | No | No | ## Related diff --git a/docs/tools/video-generation.md b/docs/tools/video-generation.md index 162faab9973..b0707e71ead 100644 --- a/docs/tools/video-generation.md +++ b/docs/tools/video-generation.md @@ -50,20 +50,20 @@ Outside of session-backed agent runs (for example, direct tool invocations), the ## Supported providers -| Provider | Default model | Text | Image ref | Video ref | API key | -| -------- | ------------------------------- | ---- | ---------------- | ---------------- | ---------------------------------------- | -| Alibaba | `wan2.6-t2v` | Yes | Yes (remote URL) | Yes (remote URL) | `MODELSTUDIO_API_KEY` | -| BytePlus | `seedance-1-0-lite-t2v-250428` | Yes | 1 image | No | `BYTEPLUS_API_KEY` | -| ComfyUI | `workflow` | Yes | 1 image | No | `COMFY_API_KEY` or `COMFY_CLOUD_API_KEY` | -| fal | `fal-ai/minimax/video-01-live` | Yes | 1 image | No | `FAL_KEY` | -| Google | `veo-3.1-fast-generate-preview` | Yes | 1 image | 1 video | `GEMINI_API_KEY` | -| MiniMax | `MiniMax-Hailuo-2.3` | Yes | 1 image | No | `MINIMAX_API_KEY` | -| OpenAI | `sora-2` | Yes | 1 image | 1 video | `OPENAI_API_KEY` | -| Qwen | `wan2.6-t2v` | Yes | Yes (remote URL) | Yes (remote URL) | `QWEN_API_KEY` | -| Runway | `gen4.5` | Yes | 1 image | 1 video | `RUNWAYML_API_SECRET` | -| Together | `Wan-AI/Wan2.2-T2V-A14B` | Yes | 1 image | No | `TOGETHER_API_KEY` | -| Vydra | `veo3` | Yes | 1 image (`kling`) | No | `VYDRA_API_KEY` | -| xAI | `grok-imagine-video` | Yes | 1 image | 1 video | `XAI_API_KEY` | +| Provider | Default model | Text | Image ref | Video ref | API key | +| -------- | ------------------------------- | ---- | ----------------- | ---------------- | ---------------------------------------- | +| Alibaba | `wan2.6-t2v` | Yes | Yes (remote URL) | Yes (remote URL) | `MODELSTUDIO_API_KEY` | +| BytePlus | `seedance-1-0-lite-t2v-250428` | Yes | 1 image | No | `BYTEPLUS_API_KEY` | +| ComfyUI | `workflow` | Yes | 1 image | No | `COMFY_API_KEY` or `COMFY_CLOUD_API_KEY` | +| fal | `fal-ai/minimax/video-01-live` | Yes | 1 image | No | `FAL_KEY` | +| Google | `veo-3.1-fast-generate-preview` | Yes | 1 image | 1 video | `GEMINI_API_KEY` | +| MiniMax | `MiniMax-Hailuo-2.3` | Yes | 1 image | No | `MINIMAX_API_KEY` | +| OpenAI | `sora-2` | Yes | 1 image | 1 video | `OPENAI_API_KEY` | +| Qwen | `wan2.6-t2v` | Yes | Yes (remote URL) | Yes (remote URL) | `QWEN_API_KEY` | +| Runway | `gen4.5` | Yes | 1 image | 1 video | `RUNWAYML_API_SECRET` | +| Together | `Wan-AI/Wan2.2-T2V-A14B` | Yes | 1 image | No | `TOGETHER_API_KEY` | +| Vydra | `veo3` | Yes | 1 image (`kling`) | No | `VYDRA_API_KEY` | +| xAI | `grok-imagine-video` | Yes | 1 image | 1 video | `XAI_API_KEY` | Some providers accept additional or alternate API key env vars. See individual [provider pages](#related) for details. @@ -139,20 +139,20 @@ If a provider fails, the next candidate is tried automatically. If all candidate ## Provider notes -| Provider | Notes | -| -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -| Alibaba | Uses DashScope/Model Studio async endpoint. Reference images and videos must be remote `http(s)` URLs. | -| BytePlus | Single image reference only. | -| ComfyUI | Workflow-driven local or cloud execution. Supports text-to-video and image-to-video through the configured graph. | -| fal | Uses queue-backed flow for long-running jobs. Single image reference only. | -| Google | Uses Gemini/Veo. Supports one image or one video reference. | -| MiniMax | Single image reference only. | -| OpenAI | Only `size` override is forwarded. Other style overrides (`aspectRatio`, `resolution`, `audio`, `watermark`) are ignored with a warning. | -| Qwen | Same DashScope backend as Alibaba. Reference inputs must be remote `http(s)` URLs; local files are rejected upfront. | -| Runway | Supports local files via data URIs. Video-to-video requires `runway/gen4_aleph`. Text-only runs expose `16:9` and `9:16` aspect ratios. | -| Together | Single image reference only. | +| Provider | Notes | +| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Alibaba | Uses DashScope/Model Studio async endpoint. Reference images and videos must be remote `http(s)` URLs. | +| BytePlus | Single image reference only. | +| ComfyUI | Workflow-driven local or cloud execution. Supports text-to-video and image-to-video through the configured graph. | +| fal | Uses queue-backed flow for long-running jobs. Single image reference only. | +| Google | Uses Gemini/Veo. Supports one image or one video reference. | +| MiniMax | Single image reference only. | +| OpenAI | Only `size` override is forwarded. Other style overrides (`aspectRatio`, `resolution`, `audio`, `watermark`) are ignored with a warning. | +| Qwen | Same DashScope backend as Alibaba. Reference inputs must be remote `http(s)` URLs; local files are rejected upfront. | +| Runway | Supports local files via data URIs. Video-to-video requires `runway/gen4_aleph`. Text-only runs expose `16:9` and `9:16` aspect ratios. | +| Together | Single image reference only. | | Vydra | Uses `https://www.vydra.ai/api/v1` directly to avoid auth-dropping redirects. `veo3` is bundled as text-to-video only; `kling` requires a remote image URL. | -| xAI | Supports text-to-video, image-to-video, and remote video edit/extend flows. | +| xAI | Supports text-to-video, image-to-video, and remote video edit/extend flows. | ## Configuration @@ -182,7 +182,7 @@ openclaw config set agents.defaults.videoGenerationModel.primary "qwen/wan2.6-t2 - [Tools Overview](/tools) - [Background Tasks](/automation/tasks) -- task tracking for async video generation - [Alibaba Model Studio](/providers/alibaba) -- [BytePlus](/providers/byteplus) +- [BytePlus](/concepts/model-providers#byteplus-international) - [ComfyUI](/providers/comfy) - [fal](/providers/fal) - [Google (Gemini)](/providers/google) diff --git a/extensions/minimax/index.test.ts b/extensions/minimax/index.test.ts index fb03191f2a9..3410fc64d01 100644 --- a/extensions/minimax/index.test.ts +++ b/extensions/minimax/index.test.ts @@ -137,6 +137,8 @@ describe("minimax provider hooks", () => { registerProvider() {}, registerMediaUnderstandingProvider() {}, registerImageGenerationProvider() {}, + registerMusicGenerationProvider() {}, + registerVideoGenerationProvider() {}, registerSpeechProvider() {}, registerWebSearchProvider(provider: unknown) { webSearchProviders.push(provider); diff --git a/extensions/whatsapp/test-api.ts b/extensions/whatsapp/test-api.ts index 82444aab71d..bfea8434f95 100644 --- a/extensions/whatsapp/test-api.ts +++ b/extensions/whatsapp/test-api.ts @@ -1 +1,2 @@ +export { whatsappOutbound } from "./src/outbound-adapter.js"; export { resolveWhatsAppRuntimeGroupPolicy } from "./src/runtime-group-policy.js"; diff --git a/package.json b/package.json index 83f7f87c558..2ec46b4627a 100644 --- a/package.json +++ b/package.json @@ -207,6 +207,10 @@ "types": "./dist/plugin-sdk/media-runtime.d.ts", "default": "./dist/plugin-sdk/media-runtime.js" }, + "./plugin-sdk/media-generation-runtime": { + "types": "./dist/plugin-sdk/media-generation-runtime.d.ts", + "default": "./dist/plugin-sdk/media-generation-runtime.js" + }, "./plugin-sdk/conversation-runtime": { "types": "./dist/plugin-sdk/conversation-runtime.d.ts", "default": "./dist/plugin-sdk/conversation-runtime.js" diff --git a/scripts/check-gateway-watch-regression.mjs b/scripts/check-gateway-watch-regression.mjs index 12dbe1f5f54..86b41457fd6 100644 --- a/scripts/check-gateway-watch-regression.mjs +++ b/scripts/check-gateway-watch-regression.mjs @@ -6,6 +6,7 @@ import net from "node:net"; import os from "node:os"; import path from "node:path"; import process from "node:process"; +import { writeBuildStamp } from "./build-stamp.mjs"; import { resolveBuildRequirement } from "./run-node.mjs"; const DEFAULTS = { @@ -454,6 +455,10 @@ async function main() { ensureDir(options.outputDir); if (!options.skipBuild) { runCheckedCommand("pnpm", ["build"]); + // The watch harness must start from a completed-build baseline. Refresh + // the build stamp after the full build pipeline finishes so run-node does + // not spuriously rebuild inside the bounded watch window. + writeBuildStamp({ cwd: process.cwd() }); } const preflightBuildRequirement = resolveBuildRequirement(buildRunNodeDeps(process.env)); diff --git a/scripts/ci-changed-scope.d.mts b/scripts/ci-changed-scope.d.mts index 5661e98458c..50ab11d3aea 100644 --- a/scripts/ci-changed-scope.d.mts +++ b/scripts/ci-changed-scope.d.mts @@ -5,6 +5,7 @@ export type ChangedScope = { runWindows: boolean; runSkillsPython: boolean; runChangedSmoke: boolean; + runControlUiI18n: boolean; }; export function detectChangedScope(changedPaths: string[]): ChangedScope; diff --git a/scripts/ci-changed-scope.mjs b/scripts/ci-changed-scope.mjs index 0e6f9d35e5b..40244ce2ceb 100644 --- a/scripts/ci-changed-scope.mjs +++ b/scripts/ci-changed-scope.mjs @@ -1,7 +1,7 @@ import { execFileSync } from "node:child_process"; import { appendFileSync } from "node:fs"; -/** @typedef {{ runNode: boolean; runMacos: boolean; runAndroid: boolean; runWindows: boolean; runSkillsPython: boolean; runChangedSmoke: boolean }} ChangedScope */ +/** @typedef {{ runNode: boolean; runMacos: boolean; runAndroid: boolean; runWindows: boolean; runSkillsPython: boolean; runChangedSmoke: boolean; runControlUiI18n: boolean }} ChangedScope */ const DOCS_PATH_RE = /^(docs\/|.*\.mdx?$)/; const SKILLS_PYTHON_SCOPE_RE = /^(skills\/|pyproject\.toml$)/; @@ -15,6 +15,8 @@ const NODE_SCOPE_RE = /^(src\/|test\/|extensions\/|packages\/|scripts\/|ui\/|\.github\/|openclaw\.mjs$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|tsconfig.*\.json$|vitest.*\.ts$|tsdown\.config\.ts$|\.oxlintrc\.json$|\.oxfmtrc\.jsonc$)/; const WINDOWS_SCOPE_RE = /^(src\/|test\/|extensions\/|packages\/|scripts\/|ui\/|openclaw\.mjs$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|tsconfig.*\.json$|vitest.*\.ts$|tsdown\.config\.ts$|\.github\/workflows\/ci\.yml$|\.github\/actions\/setup-node-env\/action\.yml$|\.github\/actions\/setup-pnpm-store-cache\/action\.yml$)/; +const CONTROL_UI_I18N_SCOPE_RE = + /^(ui\/src\/i18n\/|scripts\/control-ui-i18n\.ts$|\.github\/workflows\/control-ui-locale-refresh\.yml$)/; const NATIVE_ONLY_RE = /^(apps\/android\/|apps\/ios\/|apps\/macos\/|apps\/shared\/|Swabble\/|appcast\.xml$)/; const CHANGED_SMOKE_SCOPE_RE = @@ -33,6 +35,7 @@ export function detectChangedScope(changedPaths) { runWindows: true, runSkillsPython: true, runChangedSmoke: true, + runControlUiI18n: true, }; } @@ -42,6 +45,7 @@ export function detectChangedScope(changedPaths) { let runWindows = false; let runSkillsPython = false; let runChangedSmoke = false; + let runControlUiI18n = false; let hasNonDocs = false; let hasNonNativeNonDocs = false; @@ -91,6 +95,10 @@ export function detectChangedScope(changedPaths) { runChangedSmoke = true; } + if (CONTROL_UI_I18N_SCOPE_RE.test(path)) { + runControlUiI18n = true; + } + if (!NATIVE_ONLY_RE.test(path)) { hasNonNativeNonDocs = true; } @@ -100,7 +108,15 @@ export function detectChangedScope(changedPaths) { runNode = true; } - return { runNode, runMacos, runAndroid, runWindows, runSkillsPython, runChangedSmoke }; + return { + runNode, + runMacos, + runAndroid, + runWindows, + runSkillsPython, + runChangedSmoke, + runControlUiI18n, + }; } /** @@ -136,6 +152,7 @@ export function writeGitHubOutput(scope, outputPath = process.env.GITHUB_OUTPUT) appendFileSync(outputPath, `run_windows=${scope.runWindows}\n`, "utf8"); appendFileSync(outputPath, `run_skills_python=${scope.runSkillsPython}\n`, "utf8"); appendFileSync(outputPath, `run_changed_smoke=${scope.runChangedSmoke}\n`, "utf8"); + appendFileSync(outputPath, `run_control_ui_i18n=${scope.runControlUiI18n}\n`, "utf8"); } function isDirectRun() { @@ -172,6 +189,7 @@ if (isDirectRun()) { runWindows: true, runSkillsPython: true, runChangedSmoke: true, + runControlUiI18n: true, }); process.exit(0); } @@ -184,6 +202,7 @@ if (isDirectRun()) { runWindows: true, runSkillsPython: true, runChangedSmoke: true, + runControlUiI18n: true, }); } } diff --git a/scripts/lib/plugin-sdk-entrypoints.json b/scripts/lib/plugin-sdk-entrypoints.json index 17da17ccce5..20960e72fbd 100644 --- a/scripts/lib/plugin-sdk-entrypoints.json +++ b/scripts/lib/plugin-sdk-entrypoints.json @@ -41,6 +41,7 @@ "ssrf-policy", "ssrf-runtime", "media-runtime", + "media-generation-runtime", "conversation-runtime", "matrix-runtime-heavy", "matrix-runtime-shared", diff --git a/src/channels/plugins/contracts/registry.ts b/src/channels/plugins/contracts/registry.ts index a7cd45f887f..a066ab4599c 100644 --- a/src/channels/plugins/contracts/registry.ts +++ b/src/channels/plugins/contracts/registry.ts @@ -3,10 +3,7 @@ import type { OpenClawConfig } from "../../../config/config.js"; import { listBundledChannelPlugins, setBundledChannelRuntime } from "../bundled.js"; import type { ChannelPlugin } from "../types.js"; import { channelPluginSurfaceKeys, type ChannelPluginSurface } from "./manifest.js"; -import { - importBundledChannelContractArtifact, - resolveBundledChannelContractArtifactUrl, -} from "./runtime-artifacts.js"; +import { importBundledChannelContractArtifact } from "./runtime-artifacts.js"; type SurfaceContractEntry = { id: string; @@ -44,6 +41,10 @@ const sendMessageMatrixMock = vi.hoisted(() => roomId: to.replace(/^room:/, ""), })), ); +const matrixRuntimeApiModuleId = new URL( + "../../../../extensions/matrix/runtime-api.js", + import.meta.url, +).href; const lineContractApi = await importBundledChannelContractArtifact<{ listLineAccountIds: () => string[]; @@ -62,11 +63,7 @@ setBundledChannelRuntime("line", { }, } as never); -vi.mock(resolveBundledChannelContractArtifactUrl("matrix", "runtime-api"), async () => { - const matrixRuntimeApiModuleId = resolveBundledChannelContractArtifactUrl( - "matrix", - "runtime-api", - ); +vi.mock(matrixRuntimeApiModuleId, async () => { const actual = await vi.importActual(matrixRuntimeApiModuleId); return { ...actual, diff --git a/src/infra/scripts-modules.d.ts b/src/infra/scripts-modules.d.ts index 4dd21a614bc..15ad8d79d5b 100644 --- a/src/infra/scripts-modules.d.ts +++ b/src/infra/scripts-modules.d.ts @@ -25,5 +25,6 @@ declare module "../../scripts/ci-changed-scope.mjs" { runWindows: boolean; runSkillsPython: boolean; runChangedSmoke: boolean; + runControlUiI18n: boolean; }; } diff --git a/src/plugin-sdk/media-generation-runtime.ts b/src/plugin-sdk/media-generation-runtime.ts new file mode 100644 index 00000000000..c20d579ee02 --- /dev/null +++ b/src/plugin-sdk/media-generation-runtime.ts @@ -0,0 +1,3 @@ +// Narrow shared media-generation runtime helpers for bundled and third-party plugins. + +export * from "../media-generation/runtime-shared.js"; diff --git a/src/scripts/ci-changed-scope.test.ts b/src/scripts/ci-changed-scope.test.ts index d16ea6d15d7..da99fd765c9 100644 --- a/src/scripts/ci-changed-scope.test.ts +++ b/src/scripts/ci-changed-scope.test.ts @@ -13,6 +13,7 @@ const { detectChangedScope, listChangedPaths } = runWindows: boolean; runSkillsPython: boolean; runChangedSmoke: boolean; + runControlUiI18n: boolean; }; listChangedPaths: (base: string, head?: string) => string[]; }; @@ -37,6 +38,7 @@ describe("detectChangedScope", () => { runWindows: true, runSkillsPython: true, runChangedSmoke: true, + runControlUiI18n: true, }); }); @@ -48,6 +50,7 @@ describe("detectChangedScope", () => { runWindows: false, runSkillsPython: false, runChangedSmoke: false, + runControlUiI18n: false, }); }); @@ -59,6 +62,7 @@ describe("detectChangedScope", () => { runWindows: true, runSkillsPython: false, runChangedSmoke: false, + runControlUiI18n: false, }); }); @@ -70,6 +74,7 @@ describe("detectChangedScope", () => { runWindows: false, runSkillsPython: false, runChangedSmoke: false, + runControlUiI18n: false, }); expect(detectChangedScope(["apps/shared/OpenClawKit/Sources/Foo.swift"])).toEqual({ runNode: false, @@ -78,6 +83,7 @@ describe("detectChangedScope", () => { runWindows: false, runSkillsPython: false, runChangedSmoke: false, + runControlUiI18n: false, }); }); @@ -90,6 +96,7 @@ describe("detectChangedScope", () => { runWindows: false, runSkillsPython: false, runChangedSmoke: false, + runControlUiI18n: false, }, ); }); @@ -102,6 +109,7 @@ describe("detectChangedScope", () => { runWindows: false, runSkillsPython: false, runChangedSmoke: false, + runControlUiI18n: false, }); expect(detectChangedScope(["assets/icon.png"])).toEqual({ @@ -111,6 +119,7 @@ describe("detectChangedScope", () => { runWindows: false, runSkillsPython: false, runChangedSmoke: false, + runControlUiI18n: false, }); }); @@ -122,6 +131,7 @@ describe("detectChangedScope", () => { runWindows: false, runSkillsPython: false, runChangedSmoke: false, + runControlUiI18n: false, }); }); @@ -133,6 +143,7 @@ describe("detectChangedScope", () => { runWindows: false, runSkillsPython: true, runChangedSmoke: false, + runControlUiI18n: false, }); }); @@ -144,6 +155,7 @@ describe("detectChangedScope", () => { runWindows: false, runSkillsPython: true, runChangedSmoke: false, + runControlUiI18n: false, }); }); @@ -155,6 +167,7 @@ describe("detectChangedScope", () => { runWindows: true, runSkillsPython: true, runChangedSmoke: false, + runControlUiI18n: false, }); }); @@ -166,6 +179,7 @@ describe("detectChangedScope", () => { runWindows: true, runSkillsPython: false, runChangedSmoke: true, + runControlUiI18n: false, }); expect(detectChangedScope([bundledPluginFile("matrix", "package.json")])).toEqual({ runNode: true, @@ -174,6 +188,7 @@ describe("detectChangedScope", () => { runWindows: true, runSkillsPython: false, runChangedSmoke: true, + runControlUiI18n: false, }); expect(detectChangedScope([".github/workflows/install-smoke.yml"])).toEqual({ runNode: true, @@ -182,6 +197,29 @@ describe("detectChangedScope", () => { runWindows: false, runSkillsPython: false, runChangedSmoke: true, + runControlUiI18n: false, + }); + }); + + it("runs control-ui locale check only for control-ui i18n surfaces", () => { + expect(detectChangedScope(["ui/src/i18n/locales/en.ts"])).toEqual({ + runNode: true, + runMacos: false, + runAndroid: false, + runWindows: true, + runSkillsPython: false, + runChangedSmoke: false, + runControlUiI18n: true, + }); + + expect(detectChangedScope(["scripts/control-ui-i18n.ts"])).toEqual({ + runNode: true, + runMacos: false, + runAndroid: false, + runWindows: true, + runSkillsPython: false, + runChangedSmoke: false, + runControlUiI18n: true, }); }); diff --git a/test/helpers/channels/channel-catalog-contract.ts b/test/helpers/channels/channel-catalog-contract.ts index 49c06773009..0221cf4f707 100644 --- a/test/helpers/channels/channel-catalog-contract.ts +++ b/test/helpers/channels/channel-catalog-contract.ts @@ -70,6 +70,7 @@ export function describeBundledMetadataOnlyChannelCatalogContract(params: { }), "utf8", ); + fs.writeFileSync(path.join(bundledDir, "index.js"), "export default {};\n", "utf8"); fs.writeFileSync( path.join(bundledDir, "openclaw.plugin.json"), JSON.stringify({ id: params.pluginId, channels: [params.meta.id], configSchema: {} }),