diff --git a/.github/workflows/install-smoke.yml b/.github/workflows/install-smoke.yml index c051e13b0a0..9f7a1241a90 100644 --- a/.github/workflows/install-smoke.yml +++ b/.github/workflows/install-smoke.yml @@ -114,7 +114,21 @@ jobs: - name: Run root Dockerfile CLI smoke run: | - docker run --rm --entrypoint sh openclaw-dockerfile-smoke:local -lc 'which openclaw && openclaw --version' + docker run --rm --entrypoint sh openclaw-dockerfile-smoke:local -lc ' + which openclaw && + openclaw --version && + node -e " + const fs = require(\"node:fs\"); + const path = require(\"node:path\"); + const pkg = require(\"/app/package.json\"); + for (const [dep, rel] of Object.entries(pkg.pnpm?.patchedDependencies ?? {})) { + const absolute = path.join(\"/app\", rel); + if (!fs.existsSync(absolute)) { + throw new Error(`missing patch for ${dep}: ${rel}`); + } + } + " + ' - name: Run agents delete shared workspace Docker CLI smoke env: diff --git a/CHANGELOG.md b/CHANGELOG.md index 638274fee4a..0595919c2af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Docker: copy patched dependency files into runtime images so downstream `pnpm install` layers keep working. Fixes #69224. Thanks @gucasbrg. - Agents/runtime: submit heartbeat, cron, and exec wakeups as transient runtime context instead of visible user prompts, keeping synthetic system work out of chat transcripts. Fixes #66496 and #66814. Thanks @jeades and @mandomaker. - Telegram: preserve exact selected quote text when sending native quote replies, and retry with legacy replies if Telegram rejects quote parameters. (#71952) Thanks @rubencu. - Plugins/CLI: preserve manifest name, description, format, and source metadata in cold `openclaw plugins list` output without importing plugin runtime. Thanks @shakkernerd. diff --git a/Dockerfile b/Dockerfile index 70495735842..df78b09d10f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -173,6 +173,7 @@ RUN chown node:node /app COPY --from=runtime-assets --chown=node:node /app/dist ./dist COPY --from=runtime-assets --chown=node:node /app/node_modules ./node_modules COPY --from=runtime-assets --chown=node:node /app/package.json . +COPY --from=runtime-assets --chown=node:node /app/patches ./patches COPY --from=runtime-assets --chown=node:node /app/openclaw.mjs . COPY --from=runtime-assets --chown=node:node /app/${OPENCLAW_BUNDLED_PLUGIN_DIR} ./${OPENCLAW_BUNDLED_PLUGIN_DIR} COPY --from=runtime-assets --chown=node:node /app/skills ./skills diff --git a/src/dockerfile.test.ts b/src/dockerfile.test.ts index 353dce89584..4f97b724a61 100644 --- a/src/dockerfile.test.ts +++ b/src/dockerfile.test.ts @@ -6,6 +6,7 @@ import { BUNDLED_PLUGIN_ROOT_DIR } from "../test/helpers/bundled-plugin-paths.js const repoRoot = resolve(fileURLToPath(new URL(".", import.meta.url)), ".."); const dockerfilePath = join(repoRoot, "Dockerfile"); +const packageJsonPath = join(repoRoot, "package.json"); function collapseDockerContinuations(dockerfile: string): string { return dockerfile.replace(/\\\r?\n[ \t]*/g, " "); @@ -68,6 +69,18 @@ describe("Dockerfile", () => { ); }); + it("keeps package manager patch files in runtime images", async () => { + const dockerfile = await readFile(dockerfilePath, "utf8"); + const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8")) as { + pnpm?: { patchedDependencies?: Record }; + }; + + expect(Object.keys(packageJson.pnpm?.patchedDependencies ?? {})).not.toHaveLength(0); + expect(dockerfile).toContain( + "COPY --from=runtime-assets --chown=node:node /app/patches ./patches", + ); + }); + it("does not override bundled plugin discovery in runtime images", async () => { const dockerfile = collapseDockerContinuations(await readFile(dockerfilePath, "utf8")); expect(dockerfile).toContain(`ARG OPENCLAW_BUNDLED_PLUGIN_DIR=${BUNDLED_PLUGIN_ROOT_DIR}`);