diff --git a/CHANGELOG.md b/CHANGELOG.md index a2001be2869..2cffd2d247b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ Docs: https://docs.openclaw.ai - Auto-reply/commands: stop bare `/reset` and `/new` after reset hooks acknowledge the command, so non-ACP channels no longer fall through into empty provider calls while `/reset ` and `/new ` still seed the next model turn. Fixes #73367. Thanks @hoyanhan and @wenxu007. - Agents/Anthropic: send implicit Anthropic beta headers only to direct public Anthropic endpoints, including OAuth, so custom Anthropic-compatible providers no longer mis-handle unsupported beta flags unless explicitly configured. Refs #73346. Thanks @byBrodowski. - Skills: require explicit `skills.entries.coding-agent.enabled` before exposing the bundled coding-agent skill, so installs with Codex on PATH but no OpenAI auth do not silently offer Codex delegation. Fixes #73358. Thanks @LaFleurAdvertising and @Sanjays2402. -- Plugins/startup: precompute bundled runtime mirror fingerprints before taking the mirror lock, including dist-runtime canonical roots, so Docker Desktop/WSL cold starts no longer hold `.openclaw-runtime-mirror.lock` while scanning slow persisted volumes. Fixes #73339. Thanks @1yihui. +- Plugins/startup: precompute bundled runtime mirror fingerprints before taking the mirror lock and keep Docker bundled plugin runtime deps/mirrors in a Docker-managed volume instead of the Windows/WSL config bind mount, so cold starts avoid slow host-volume mirror writes. Fixes #73339. Thanks @1yihui. - Channels/LINE: persist inbound image, video, audio, and file downloads in `~/.openclaw/media/inbound/` instead of temporary files so agents can still read LINE media after `/tmp` cleanup. Fixes #73370. Thanks @hijirii and @wenxu007. - CLI/plugins: keep bundled plugin installs out of `plugins.load.paths` while preserving install records, so install/inspect/doctor loops no longer warn about the current bundled plugin directory. Thanks @vincentkoc. - Control UI/WebChat: keep large attachment payloads out of Lit state and optimistic chat messages, using object URL previews plus send-time payload serialization so PDF/image uploads no longer trigger `RangeError: Maximum call stack size exceeded`. Fixes #73360; refs #54378 and #63432. Thanks @hejunhui-73, @Ansub, and @christianhernandez3-afk. diff --git a/Dockerfile b/Dockerfile index a296b3073a0..a8151663a9f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -258,10 +258,12 @@ RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,shar RUN ln -sf /app/openclaw.mjs /usr/local/bin/openclaw \ && chmod 755 /app/openclaw.mjs -# Pre-create the default state dir so first-run Docker named volumes mounted -# here inherit node ownership instead of starting as root-owned state. +# Pre-create the default state and runtime-deps dirs so first-run Docker named +# volumes mounted here inherit node ownership instead of root-owned state. RUN install -d -m 0700 -o node -g node /home/node/.openclaw && \ - stat -c '%U:%G %a' /home/node/.openclaw | grep -qx 'node:node 700' + install -d -m 0700 -o node -g node /var/lib/openclaw/plugin-runtime-deps && \ + stat -c '%U:%G %a' /home/node/.openclaw | grep -qx 'node:node 700' && \ + stat -c '%U:%G %a' /var/lib/openclaw/plugin-runtime-deps | grep -qx 'node:node 700' ENV NODE_ENV=production diff --git a/docker-compose.yml b/docker-compose.yml index e436b344ce3..39275158132 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,10 +22,12 @@ services: CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY:-} CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY:-} CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE:-} + OPENCLAW_PLUGIN_STAGE_DIR: /var/lib/openclaw/plugin-runtime-deps TZ: ${OPENCLAW_TZ:-UTC} volumes: - ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw - ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace + - openclaw-plugin-runtime-deps:/var/lib/openclaw/plugin-runtime-deps ## Uncomment the lines below to enable sandbox isolation ## (agents.defaults.sandbox). Requires Docker CLI in the image ## (build with --build-arg OPENCLAW_INSTALL_DOCKER_CLI=1) or use @@ -84,13 +86,18 @@ services: CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY:-} CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY:-} CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE:-} + OPENCLAW_PLUGIN_STAGE_DIR: /var/lib/openclaw/plugin-runtime-deps TZ: ${OPENCLAW_TZ:-UTC} volumes: - ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw - ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace + - openclaw-plugin-runtime-deps:/var/lib/openclaw/plugin-runtime-deps stdin_open: true tty: true init: true entrypoint: ["node", "dist/index.js"] depends_on: - openclaw-gateway + +volumes: + openclaw-plugin-runtime-deps: diff --git a/docs/install/docker-vm-runtime.md b/docs/install/docker-vm-runtime.md index 3ef1843848d..bc1eb49be2c 100644 --- a/docs/install/docker-vm-runtime.md +++ b/docs/install/docker-vm-runtime.md @@ -116,18 +116,19 @@ Expected output: OpenClaw runs in Docker, but Docker is not the source of truth. All long-lived state must survive restarts, rebuilds, and reboots. -| Component | Location | Persistence mechanism | Notes | -| ------------------- | --------------------------------- | ---------------------- | ------------------------------------------------------------- | -| Gateway config | `/home/node/.openclaw/` | Host volume mount | Includes `openclaw.json`, `.env` | -| Model auth profiles | `/home/node/.openclaw/agents/` | Host volume mount | `agents//agent/auth-profiles.json` (OAuth, API keys) | -| Skill configs | `/home/node/.openclaw/skills/` | Host volume mount | Skill-level state | -| Agent workspace | `/home/node/.openclaw/workspace/` | Host volume mount | Code and agent artifacts | -| WhatsApp session | `/home/node/.openclaw/` | Host volume mount | Preserves QR login | -| Gmail keyring | `/home/node/.openclaw/` | Host volume + password | Requires `GOG_KEYRING_PASSWORD` | -| External binaries | `/usr/local/bin/` | Docker image | Must be baked at build time | -| Node runtime | Container filesystem | Docker image | Rebuilt every image build | -| OS packages | Container filesystem | Docker image | Do not install at runtime | -| Docker container | Ephemeral | Restartable | Safe to destroy | +| Component | Location | Persistence mechanism | Notes | +| ------------------- | ---------------------------------------- | ---------------------- | ------------------------------------------------------------- | +| Gateway config | `/home/node/.openclaw/` | Host volume mount | Includes `openclaw.json`, `.env` | +| Model auth profiles | `/home/node/.openclaw/agents/` | Host volume mount | `agents//agent/auth-profiles.json` (OAuth, API keys) | +| Skill configs | `/home/node/.openclaw/skills/` | Host volume mount | Skill-level state | +| Agent workspace | `/home/node/.openclaw/workspace/` | Host volume mount | Code and agent artifacts | +| WhatsApp session | `/home/node/.openclaw/` | Host volume mount | Preserves QR login | +| Gmail keyring | `/home/node/.openclaw/` | Host volume + password | Requires `GOG_KEYRING_PASSWORD` | +| Plugin runtime deps | `/var/lib/openclaw/plugin-runtime-deps/` | Docker named volume | Generated bundled plugin deps and runtime mirrors | +| External binaries | `/usr/local/bin/` | Docker image | Must be baked at build time | +| Node runtime | Container filesystem | Docker image | Rebuilt every image build | +| OS packages | Container filesystem | Docker image | Do not install at runtime | +| Docker container | Ephemeral | Restartable | Safe to destroy | ## Updates diff --git a/docs/install/docker.md b/docs/install/docker.md index a4fff8fbde2..85672edf0d8 100644 --- a/docs/install/docker.md +++ b/docs/install/docker.md @@ -129,6 +129,7 @@ The setup script accepts these optional environment variables: | `OPENCLAW_EXTENSIONS` | Pre-install plugin deps at build time (space-separated names) | | `OPENCLAW_EXTRA_MOUNTS` | Extra host bind mounts (comma-separated `source:target[:opts]`) | | `OPENCLAW_HOME_VOLUME` | Persist `/home/node` in a named Docker volume | +| `OPENCLAW_PLUGIN_STAGE_DIR` | Container path for generated bundled plugin deps and mirrors | | `OPENCLAW_SANDBOX` | Opt in to sandbox bootstrap (`1`, `true`, `yes`, `on`) | | `OPENCLAW_DOCKER_SOCKET` | Override Docker socket path | | `OPENCLAW_DISABLE_BONJOUR` | Disable Bonjour/mDNS advertising (defaults to `1` for Docker) | @@ -267,11 +268,24 @@ That mounted config directory is where OpenClaw keeps: - `agents//agent/auth-profiles.json` for stored provider OAuth/API-key auth - `.env` for env-backed runtime secrets such as `OPENCLAW_GATEWAY_TOKEN` +Bundled plugin runtime dependencies and mirrored runtime files are generated +state, not user config. Compose stores them in the named Docker volume +`openclaw-plugin-runtime-deps` mounted at +`/var/lib/openclaw/plugin-runtime-deps`. Keeping that high-churn tree out of the +host config bind mount avoids slow Docker Desktop/WSL file operations and stale +Windows handles during cold Gateway startup. + +The default Compose file sets `OPENCLAW_PLUGIN_STAGE_DIR` to that path for both +`openclaw-gateway` and `openclaw-cli`, so `openclaw doctor --fix`, channel +login/setup commands, and Gateway startup all use the same generated runtime +volume. + For full persistence details on VM deployments, see [Docker VM Runtime - What persists where](/install/docker-vm-runtime#what-persists-where). **Disk growth hotspots:** watch `media/`, session JSONL files, `cron/runs/*.jsonl`, -and rolling file logs under `/tmp/openclaw/`. +the `openclaw-plugin-runtime-deps` Docker volume, and rolling file logs under +`/tmp/openclaw/`. ### Shell helpers (optional) diff --git a/scripts/clawdock/README.md b/scripts/clawdock/README.md index 67430a24690..7936f3add5a 100644 --- a/scripts/clawdock/README.md +++ b/scripts/clawdock/README.md @@ -192,12 +192,14 @@ The `Dockerfile` supports two optional build args: volumes: - ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw - ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace + - openclaw-plugin-runtime-deps:/var/lib/openclaw/plugin-runtime-deps ``` This means: - `~/.openclaw/.env` is available inside the container at `/home/node/.openclaw/.env` — OpenClaw loads it automatically as the global env fallback - `~/.openclaw/openclaw.json` is available at `/home/node/.openclaw/openclaw.json` — the gateway watches it and hot-reloads most changes +- Generated bundled plugin runtime deps and mirrors live in the `openclaw-plugin-runtime-deps` Docker volume at `/var/lib/openclaw/plugin-runtime-deps`, not in the host config bind mount - No need to add API keys to `docker-compose.yml` or configure anything inside the container - Keys survive `clawdock-update`, `clawdock-rebuild`, and `clawdock-clean` because they live on the host diff --git a/src/docker-setup.e2e.test.ts b/src/docker-setup.e2e.test.ts index b17dfe0d744..162dbc4dfb5 100644 --- a/src/docker-setup.e2e.test.ts +++ b/src/docker-setup.e2e.test.ts @@ -580,4 +580,15 @@ describe("scripts/docker/setup.sh", () => { const compose = await readFile(join(repoRoot, "docker-compose.yml"), "utf8"); expect(compose.match(/TZ: \$\{OPENCLAW_TZ:-UTC\}/g)).toHaveLength(2); }); + + it("keeps bundled plugin runtime deps on a Docker-managed volume", async () => { + const compose = await readFile(join(repoRoot, "docker-compose.yml"), "utf8"); + expect( + compose.match(/OPENCLAW_PLUGIN_STAGE_DIR: \/var\/lib\/openclaw\/plugin-runtime-deps/g), + ).toHaveLength(2); + expect( + compose.match(/- openclaw-plugin-runtime-deps:\/var\/lib\/openclaw\/plugin-runtime-deps/g), + ).toHaveLength(2); + expect(compose).toContain("\nvolumes:\n openclaw-plugin-runtime-deps:\n"); + }); });