mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:40:44 +00:00
refactor: simplify plugin dependency handling
Simplify plugin installation and runtime loading around package-manager-owned dependencies, with Jiti reserved for local/TS fallback paths. Also scans npm plugin install roots so hoisted transitive dependencies are covered by dependency denylist and node_modules symlink checks.
This commit is contained in:
committed by
GitHub
parent
2e8e9cd6ca
commit
ed8f50f240
@@ -20,8 +20,7 @@ paths:
|
||||
- src/plugins/bundled-dir.ts
|
||||
- src/plugins/bundled-plugin-metadata.ts
|
||||
- src/plugins/bundled-public-surface-runtime-root.ts
|
||||
- src/plugins/bundled-runtime-deps.ts
|
||||
- src/plugins/bundled-runtime-root.ts
|
||||
- src/plugins/plugin-sdk-dist-alias.ts
|
||||
- src/plugins/captured-registration.ts
|
||||
- src/plugins/config-activation-shared.ts
|
||||
- src/plugins/config-contracts.ts
|
||||
|
||||
@@ -25,8 +25,7 @@ paths:
|
||||
- src/plugins/bundled-dir.ts
|
||||
- src/plugins/bundled-plugin-metadata.ts
|
||||
- src/plugins/bundled-plugin-scan.ts
|
||||
- src/plugins/bundled-runtime-deps*.ts
|
||||
- src/plugins/bundled-runtime-root.ts
|
||||
- src/plugins/plugin-sdk-dist-alias.ts
|
||||
- src/plugins/cli-registry-loader.ts
|
||||
- src/plugins/config-activation-shared.ts
|
||||
- src/plugins/config-contracts.ts
|
||||
|
||||
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -564,9 +564,6 @@ jobs:
|
||||
- name: Smoke test built bundled plugin singleton
|
||||
run: pnpm test:build:singleton
|
||||
|
||||
- name: Smoke test built bundled runtime deps
|
||||
run: pnpm test:build:bundled-runtime-deps
|
||||
|
||||
- name: Check CLI startup memory
|
||||
run: pnpm test:startup:memory
|
||||
|
||||
|
||||
6
.github/workflows/install-smoke.yml
vendored
6
.github/workflows/install-smoke.yml
vendored
@@ -510,9 +510,3 @@ jobs:
|
||||
with:
|
||||
install-bun: "false"
|
||||
install-deps: "true"
|
||||
|
||||
- name: Run fast bundled plugin Docker E2E
|
||||
env:
|
||||
OPENCLAW_BUNDLED_CHANNEL_DEPS_E2E_IMAGE: openclaw-bundled-channel-fast:local
|
||||
OPENCLAW_BUNDLED_CHANNEL_DOCKER_RUN_TIMEOUT: 90s
|
||||
run: timeout 480s pnpm test:docker:bundled-channel-deps:fast
|
||||
|
||||
@@ -646,21 +646,6 @@ jobs:
|
||||
- chunk_id: plugins-runtime-install-h
|
||||
label: plugins/runtime install H
|
||||
timeout_minutes: 120
|
||||
- chunk_id: bundled-channels-core
|
||||
label: bundled channels core
|
||||
timeout_minutes: 90
|
||||
- chunk_id: bundled-channels-update-a
|
||||
label: bundled channels update A
|
||||
timeout_minutes: 45
|
||||
- chunk_id: bundled-channels-update-discord
|
||||
label: bundled channels update Discord
|
||||
timeout_minutes: 30
|
||||
- chunk_id: bundled-channels-update-b
|
||||
label: bundled channels update B
|
||||
timeout_minutes: 45
|
||||
- chunk_id: bundled-channels-contracts
|
||||
label: bundled channels contracts
|
||||
timeout_minutes: 90
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
||||
|
||||
@@ -440,7 +440,7 @@ jobs:
|
||||
artifact_name: ${{ needs.prepare_release_package.outputs.artifact_name }}
|
||||
package_sha256: ${{ needs.prepare_release_package.outputs.package_sha256 }}
|
||||
suite_profile: custom
|
||||
docker_lanes: bundled-channel-deps-compat plugins-offline
|
||||
docker_lanes: plugins-offline plugin-update
|
||||
telegram_mode: mock-openai
|
||||
telegram_scenarios: telegram-help-command,telegram-commands-command,telegram-tools-compact-command,telegram-whoami-command,telegram-context-command,telegram-mention-gating
|
||||
secrets:
|
||||
|
||||
4
.github/workflows/package-acceptance.yml
vendored
4
.github/workflows/package-acceptance.yml
vendored
@@ -386,10 +386,10 @@ jobs:
|
||||
docker_lanes="npm-onboard-channel-agent gateway-network config-reload"
|
||||
;;
|
||||
package)
|
||||
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor bundled-channel-deps-compat plugins-offline plugin-update"
|
||||
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor plugins-offline plugin-update"
|
||||
;;
|
||||
product)
|
||||
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor bundled-channel-deps-compat plugins plugin-update mcp-channels cron-mcp-cleanup openai-web-search-minimal openwebui"
|
||||
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor plugins plugin-update mcp-channels cron-mcp-cleanup openai-web-search-minimal openwebui"
|
||||
include_openwebui=true
|
||||
;;
|
||||
full)
|
||||
|
||||
@@ -63,7 +63,6 @@ COPY openclaw.mjs ./
|
||||
COPY ui/package.json ./ui/package.json
|
||||
COPY patches ./patches
|
||||
COPY scripts/postinstall-bundled-plugins.mjs scripts/preinstall-package-manager-warning.mjs scripts/npm-runner.mjs scripts/windows-cmd-helpers.mjs ./scripts/
|
||||
COPY scripts/lib/bundled-runtime-deps-install.mjs ./scripts/lib/bundled-runtime-deps-install.mjs
|
||||
COPY scripts/lib/package-dist-imports.mjs ./scripts/lib/package-dist-imports.mjs
|
||||
|
||||
COPY --from=ext-deps /out/ ./${OPENCLAW_BUNDLED_PLUGIN_DIR}/
|
||||
@@ -268,12 +267,10 @@ 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 and runtime-deps dirs so first-run Docker named
|
||||
# volumes mounted here inherit node ownership instead of root-owned state.
|
||||
# Pre-create the default state dir 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 && \
|
||||
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'
|
||||
stat -c '%U:%G %a' /home/node/.openclaw | grep -qx 'node:node 700'
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
|
||||
@@ -23,12 +23,10 @@ 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:-/tmp}/.openclaw}:/home/node/.openclaw
|
||||
- ${OPENCLAW_WORKSPACE_DIR:-${HOME:-/tmp}/.openclaw/workspace}:/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
|
||||
@@ -87,18 +85,13 @@ 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:-/tmp}/.openclaw}:/home/node/.openclaw
|
||||
- ${OPENCLAW_WORKSPACE_DIR:-${HOME:-/tmp}/.openclaw/workspace}:/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:
|
||||
|
||||
@@ -11,13 +11,16 @@ QQ Bot connects to OpenClaw via the official QQ Bot API (WebSocket gateway). The
|
||||
plugin supports C2C private chat, group @messages, and guild channel messages with
|
||||
rich media (images, voice, video, files).
|
||||
|
||||
Status: bundled plugin. Direct messages, group chats, guild channels, and
|
||||
Status: downloadable plugin. Direct messages, group chats, guild channels, and
|
||||
media are supported. Reactions and threads are not supported.
|
||||
|
||||
## Bundled plugin
|
||||
## Install
|
||||
|
||||
Current OpenClaw releases bundle QQ Bot, so normal packaged builds do not need
|
||||
a separate `openclaw plugins install` step.
|
||||
Install QQ Bot before setup:
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/qqbot
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
|
||||
22
docs/ci.md
22
docs/ci.md
@@ -181,14 +181,14 @@ Keep `workflow_ref` and `package_ref` separate. `workflow_ref` is the trusted wo
|
||||
### Suite profiles
|
||||
|
||||
- `smoke` — `npm-onboard-channel-agent`, `gateway-network`, `config-reload`
|
||||
- `package` — `npm-onboard-channel-agent`, `doctor-switch`, `update-channel-switch`, `upgrade-survivor`, `published-upgrade-survivor`, `bundled-channel-deps-compat`, `plugins-offline`, `plugin-update`
|
||||
- `package` — `npm-onboard-channel-agent`, `doctor-switch`, `update-channel-switch`, `upgrade-survivor`, `published-upgrade-survivor`, `plugins-offline`, `plugin-update`
|
||||
- `product` — `package` plus `mcp-channels`, `cron-mcp-cleanup`, `openai-web-search-minimal`, `openwebui`
|
||||
- `full` — full Docker release-path chunks with OpenWebUI
|
||||
- `custom` — exact `docker_lanes`; required when `suite_profile=custom`
|
||||
|
||||
The `package` profile uses offline plugin coverage so published-package validation is not gated on live ClawHub availability. The optional Telegram lane reuses the `package-under-test` artifact in `NPM Telegram Beta E2E`, with the published npm spec path kept for standalone dispatches.
|
||||
|
||||
Release checks call Package Acceptance with `source=ref`, `package_ref=<release-ref>`, `workflow_ref=<release workflow ref>`, `suite_profile=custom`, `docker_lanes='bundled-channel-deps-compat plugins-offline'`, and `telegram_mode=mock-openai`. Release-path Docker chunks cover the overlapping package/update/plugin lanes; Package Acceptance keeps the artifact-native bundled-channel compat, offline plugin, and Telegram proof against the same resolved package tarball. Cross-OS release checks still cover OS-specific onboarding, installer, and platform behavior; package/update product validation should start with Package Acceptance. The `published-upgrade-survivor` Docker lane validates one published package baseline per run. In Package Acceptance, the resolved `package-under-test` tarball is always the candidate and `published_upgrade_survivor_baseline` selects the fallback published baseline, defaulting to `openclaw@latest`; failed-lane rerun commands preserve that baseline. Set `published_upgrade_survivor_baselines=release-history` to expand the lane across a deduped history matrix: the latest six stable releases, `2026.4.23`, and the latest stable release before `2026-03-15`. Set `published_upgrade_survivor_scenarios=reported-issues` to expand the same baselines across issue-shaped fixtures for Feishu config/runtime-deps, preserved bootstrap/persona files, tilde log paths, and stale versioned runtime-deps roots. Local aggregate runs can pass exact package specs with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS`, keep a single lane with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC` such as `openclaw@2026.4.15`, or set `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS` for the scenario matrix. The published lane configures the baseline with a baked `openclaw config set` command recipe, records recipe steps in `summary.json`, and probes `/healthz`, `/readyz`, plus RPC status after Gateway start. The Windows packaged and installer fresh lanes also verify that an installed package can import a browser-control override from a raw absolute Windows path. The OpenAI cross-OS agent-turn smoke defaults to `OPENCLAW_CROSS_OS_OPENAI_MODEL` when set, otherwise `openai/gpt-5.4-mini`, so the install and gateway proof stays fast and deterministic.
|
||||
Release checks call Package Acceptance with `source=ref`, `package_ref=<release-ref>`, `workflow_ref=<release workflow ref>`, `suite_profile=custom`, `docker_lanes='plugins-offline plugin-update'`, and `telegram_mode=mock-openai`. Release-path Docker chunks cover the overlapping package/update/plugin lanes; Package Acceptance keeps offline plugin, update, and Telegram proof against the same resolved package tarball. Cross-OS release checks still cover OS-specific onboarding, installer, and platform behavior; package/update product validation should start with Package Acceptance. The `published-upgrade-survivor` Docker lane validates one published package baseline per run. In Package Acceptance, the resolved `package-under-test` tarball is always the candidate and `published_upgrade_survivor_baseline` selects the fallback published baseline, defaulting to `openclaw@latest`; failed-lane rerun commands preserve that baseline. Set `published_upgrade_survivor_baselines=release-history` to expand the lane across a deduped history matrix: the latest six stable releases, `2026.4.23`, and the latest stable release before `2026-03-15`. Set `published_upgrade_survivor_scenarios=reported-issues` to expand the same baselines across issue-shaped fixtures for Feishu config, preserved bootstrap/persona files, tilde log paths, and stale legacy plugin dependency roots. Local aggregate runs can pass exact package specs with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS`, keep a single lane with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC` such as `openclaw@2026.4.15`, or set `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS` for the scenario matrix. The published lane configures the baseline with a baked `openclaw config set` command recipe, records recipe steps in `summary.json`, and probes `/healthz`, `/readyz`, plus RPC status after Gateway start. The Windows packaged and installer fresh lanes also verify that an installed package can import a browser-control override from a raw absolute Windows path. The OpenAI cross-OS agent-turn smoke defaults to `OPENCLAW_CROSS_OS_OPENAI_MODEL` when set, otherwise `openai/gpt-5.4-mini`, so the install and gateway proof stays fast and deterministic.
|
||||
|
||||
### Legacy compatibility windows
|
||||
|
||||
@@ -290,9 +290,9 @@ The reusable live/E2E workflow asks `scripts/test-docker-all.mjs --plan-json` wh
|
||||
Release Docker coverage runs smaller chunked jobs with `OPENCLAW_SKIP_DOCKER_BUILD=1` so each chunk pulls only the image kind it needs and executes multiple lanes through the same weighted scheduler:
|
||||
|
||||
- `OPENCLAW_DOCKER_ALL_PROFILE=release-path`
|
||||
- `OPENCLAW_DOCKER_ALL_CHUNK=core | package-update-openai | package-update-anthropic | package-update-core | plugins-runtime-plugins | plugins-runtime-services | plugins-runtime-install-a..h | bundled-channels`
|
||||
- `OPENCLAW_DOCKER_ALL_CHUNK=core | package-update-openai | package-update-anthropic | package-update-core | plugins-runtime-plugins | plugins-runtime-services | plugins-runtime-install-a..h`
|
||||
|
||||
Current release Docker chunks are `core`, `package-update-openai`, `package-update-anthropic`, `package-update-core`, `plugins-runtime-plugins`, `plugins-runtime-services`, `plugins-runtime-install-a` through `plugins-runtime-install-h`, `bundled-channels-core`, `bundled-channels-update-a`, `bundled-channels-update-discord`, `bundled-channels-update-b`, and `bundled-channels-contracts`. The aggregate `bundled-channels` chunk remains available for manual one-shot reruns, and `plugins-runtime-core`, `plugins-runtime`, and `plugins-integrations` remain aggregate plugin/runtime aliases. The `install-e2e` lane alias remains the aggregate manual rerun alias for both provider installer lanes. The `bundled-channels` chunk runs split `bundled-channel-*` and `bundled-channel-update-*` lanes rather than the serial all-in-one `bundled-channel-deps` lane.
|
||||
Current release Docker chunks are `core`, `package-update-openai`, `package-update-anthropic`, `package-update-core`, `plugins-runtime-plugins`, `plugins-runtime-services`, and `plugins-runtime-install-a` through `plugins-runtime-install-h`. `plugins-runtime-core`, `plugins-runtime`, and `plugins-integrations` remain aggregate plugin/runtime aliases. The `install-e2e` lane alias remains the aggregate manual rerun alias for both provider installer lanes.
|
||||
|
||||
OpenWebUI is folded into `plugins-runtime-services` when full release-path coverage requests it, and keeps a standalone `openwebui` chunk only for OpenWebUI-only dispatches. Bundled-channel update lanes retry once for transient npm network failures.
|
||||
|
||||
@@ -332,13 +332,13 @@ The pull request guard stays light: it only starts for changes under `.github/ac
|
||||
|
||||
### Security categories
|
||||
|
||||
| Category | Surface |
|
||||
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `/codeql-security-high/core-auth-secrets` | Auth, secrets, sandbox, cron, and gateway baseline |
|
||||
| `/codeql-security-high/channel-runtime-boundary` | Core channel implementation contracts plus the channel plugin runtime, gateway, Plugin SDK, secrets, audit touchpoints |
|
||||
| `/codeql-security-high/network-ssrf-boundary` | Core SSRF, IP parsing, network guard, web-fetch, and Plugin SDK SSRF policy surfaces |
|
||||
| `/codeql-security-high/mcp-process-tool-boundary` | MCP servers, process execution helpers, outbound delivery, and agent tool-execution gates |
|
||||
| `/codeql-security-high/plugin-trust-boundary` | Plugin install, loader, manifest, registry, runtime-dependency staging, source-loading, and Plugin SDK package contract trust surfaces |
|
||||
| Category | Surface |
|
||||
| ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `/codeql-security-high/core-auth-secrets` | Auth, secrets, sandbox, cron, and gateway baseline |
|
||||
| `/codeql-security-high/channel-runtime-boundary` | Core channel implementation contracts plus the channel plugin runtime, gateway, Plugin SDK, secrets, audit touchpoints |
|
||||
| `/codeql-security-high/network-ssrf-boundary` | Core SSRF, IP parsing, network guard, web-fetch, and Plugin SDK SSRF policy surfaces |
|
||||
| `/codeql-security-high/mcp-process-tool-boundary` | MCP servers, process execution helpers, outbound delivery, and agent tool-execution gates |
|
||||
| `/codeql-security-high/plugin-trust-boundary` | Plugin install, loader, manifest, registry, package-manager install, source-loading, and Plugin SDK package contract trust surfaces |
|
||||
|
||||
### Platform-specific security shards
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ Available sections:
|
||||
Notes:
|
||||
|
||||
- Choosing where the Gateway runs always updates `gateway.mode`. You can select "Continue" without other sections if that is all you need.
|
||||
- After local config writes, configure materializes newly required bundled plugin runtime dependencies. This is a narrow package-manager repair step, not a full `openclaw doctor` run. Remote gateway config does not install local plugin dependencies.
|
||||
- After local config writes, configure installs selected downloadable plugins when the chosen setup path requires them. Remote gateway config does not install local plugin packages.
|
||||
- Channel-oriented services (Slack/Discord/Matrix/Microsoft Teams) prompt for channel/room allowlists during setup. You can enter names or IDs; the wizard resolves names to IDs when possible.
|
||||
- If you run the daemon install step, token auth requires a token, and `gateway.auth.token` is SecretRef-managed, configure validates the SecretRef but does not persist resolved plaintext token values into supervisor service environment metadata.
|
||||
- If token auth requires a token and the configured token SecretRef is unresolved, configure blocks daemon install with actionable remediation guidance.
|
||||
|
||||
@@ -44,7 +44,7 @@ Notes:
|
||||
- `doctor --fix --non-interactive` reports missing or stale gateway service definitions but does not install or rewrite them outside update repair mode. Run `openclaw gateway install` for a missing service, or `openclaw gateway install --force` when you intentionally want to replace the launcher.
|
||||
- State integrity checks now detect orphan transcript files in the sessions directory. Archiving them as `.deleted.<timestamp>` requires an interactive confirmation; `--fix`, `--yes`, and headless runs leave them in place.
|
||||
- Doctor also scans `~/.openclaw/cron/jobs.json` (or `cron.store`) for legacy cron job shapes and can rewrite them in place before the scheduler has to auto-normalize them at runtime.
|
||||
- Doctor repairs missing bundled plugin runtime dependencies without writing into packaged global installs. For root-owned npm installs or hardened systemd units, set `OPENCLAW_PLUGIN_STAGE_DIR` to a writable directory such as `/var/lib/openclaw/plugin-runtime-deps`; it can also be a path-list such as `/opt/openclaw/plugin-runtime-deps:/var/lib/openclaw/plugin-runtime-deps`, where earlier roots are read-only lookup layers and the final root is the repair target.
|
||||
- Doctor cleans legacy plugin dependency staging state created by older OpenClaw versions. It also repairs missing configured downloadable plugins when the registry can resolve them.
|
||||
- Doctor repairs stale plugin config by removing missing plugin ids from `plugins.allow`/`plugins.entries`, plus matching dangling channel config, heartbeat targets, and channel model overrides when plugin discovery is healthy.
|
||||
- Doctor quarantines invalid plugin config by disabling the affected `plugins.entries.<id>` entry and removing its invalid `config` payload. Gateway startup already skips only that bad plugin so other plugins and channels can keep running.
|
||||
- Set `OPENCLAW_SERVICE_REPAIR_POLICY=external` when another supervisor owns the gateway lifecycle. Doctor still reports gateway/service health and applies non-service repairs, but skips service install/start/restart/bootstrap and legacy service cleanup.
|
||||
|
||||
@@ -146,7 +146,7 @@ When you set `--url`, the CLI does not fall back to config or environment creden
|
||||
openclaw gateway health --url ws://127.0.0.1:18789
|
||||
```
|
||||
|
||||
The HTTP `/healthz` endpoint is a liveness probe: it returns once the server can answer HTTP. The HTTP `/readyz` endpoint is stricter and stays red while startup plugin runtime dependencies, sidecars, channels, or configured hooks are still settling. Local or authenticated detailed readiness responses include an `eventLoop` diagnostic block with event-loop delay, event-loop utilization, CPU core ratio, and a `degraded` flag.
|
||||
The HTTP `/healthz` endpoint is a liveness probe: it returns once the server can answer HTTP. The HTTP `/readyz` endpoint is stricter and stays red while startup plugin sidecars, channels, or configured hooks are still settling. Local or authenticated detailed readiness responses include an `eventLoop` diagnostic block with event-loop delay, event-loop utilization, CPU core ratio, and a `degraded` flag.
|
||||
|
||||
### `gateway usage-cost`
|
||||
|
||||
|
||||
@@ -49,8 +49,8 @@ Benefits:
|
||||
|
||||
For end-to-end provider checks, prefer `openclaw infer ...` once lower-level
|
||||
provider tests are green. It exercises the shipped CLI, config loading,
|
||||
default-agent resolution, bundled plugin activation, runtime-dependency repair,
|
||||
and the shared capability runtime before the provider request is made.
|
||||
default-agent resolution, bundled plugin activation, and the shared capability
|
||||
runtime before the provider request is made.
|
||||
|
||||
## Command tree
|
||||
|
||||
|
||||
@@ -119,8 +119,8 @@ Gateway token options in non-interactive mode:
|
||||
- With `--install-daemon`, if token mode requires a token and the configured token SecretRef is unresolved, onboarding fails closed with remediation guidance.
|
||||
- With `--install-daemon`, if both `gateway.auth.token` and `gateway.auth.password` are configured and `gateway.auth.mode` is unset, onboarding blocks install until mode is set explicitly.
|
||||
- Local onboarding writes `gateway.mode="local"` into the config. If a later config file is missing `gateway.mode`, treat that as config damage or an incomplete manual edit, not as a valid local-mode shortcut.
|
||||
- Local onboarding materializes newly required bundled plugin runtime dependencies after writing config, before workspace/bootstrap, daemon install, or health checks continue. This is a narrow package-manager repair step, not a full `openclaw doctor` run.
|
||||
- Remote onboarding only writes connection info for the remote Gateway and does not install local bundled plugin dependencies.
|
||||
- Local onboarding installs selected downloadable plugins when the chosen setup path requires them.
|
||||
- Remote onboarding only writes connection info for the remote Gateway and does not install local plugin packages.
|
||||
- `--allow-unconfigured` is a separate gateway runtime escape hatch. It does not mean onboarding may omit `gateway.mode`.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw plugins` (list, install, marketplace, uninstall, enable/disable, deps, doctor)"
|
||||
summary: "CLI reference for `openclaw plugins` (list, install, marketplace, uninstall, enable/disable, doctor)"
|
||||
read_when:
|
||||
- You want to install or manage Gateway plugins or compatible bundles
|
||||
- You want to debug plugin load failures
|
||||
@@ -42,10 +42,6 @@ openclaw plugins disable <id>
|
||||
openclaw plugins registry
|
||||
openclaw plugins registry --refresh
|
||||
openclaw plugins uninstall <id>
|
||||
openclaw plugins deps
|
||||
openclaw plugins deps --repair
|
||||
openclaw plugins deps --prune
|
||||
openclaw plugins deps --json
|
||||
openclaw plugins doctor
|
||||
openclaw plugins update <id-or-npm-spec>
|
||||
openclaw plugins update --all
|
||||
@@ -129,13 +125,13 @@ current OpenClaw or a local checkout until a newer npm package is published.
|
||||
|
||||
Bare specs and `@latest` stay on the stable track. If npm resolves either of those to a prerelease, OpenClaw stops and asks you to opt in explicitly with a prerelease tag such as `@beta`/`@rc` or an exact prerelease version such as `@1.2.3-beta.4`.
|
||||
|
||||
If a bare install spec matches a bundled plugin id (for example `diffs`), OpenClaw installs the bundled plugin directly. To install an npm package with the same name, use an explicit scoped spec (for example `@scope/diffs`).
|
||||
If a bare install spec matches an official plugin id (for example `diffs`), OpenClaw installs the catalog entry directly. To install an npm package with the same name, use an explicit scoped spec (for example `@scope/diffs`).
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Git repositories">
|
||||
Use `git:<repo>` to install directly from a git repository. Supported forms include `git:github.com/owner/repo`, `git:owner/repo`, full `https://`, `ssh://`, `git://`, `file://`, and `git@host:owner/repo.git` clone URLs. Add `@<ref>` or `#<ref>` to check out a branch, tag, or commit before install.
|
||||
|
||||
Git installs clone into a temporary directory, check out the requested ref when present, then use the normal plugin directory installer. That means manifest validation, dangerous-code scanning, runtime dependency staging, and install records behave like local-path installs. Recorded git installs include the source URL/ref plus the resolved commit so `openclaw plugins update` can re-resolve the source later.
|
||||
Git installs clone into a temporary directory, check out the requested ref when present, then use the normal plugin directory installer. That means manifest validation, dangerous-code scanning, package-manager install work, and install records behave like npm installs. Recorded git installs include the source URL/ref plus the resolved commit so `openclaw plugins update` can re-resolve the source later.
|
||||
|
||||
After installing from git, use `openclaw plugins inspect <id> --runtime --json` to verify runtime registrations such as gateway methods and CLI commands. If the plugin registered a CLI root with `api.registerCli`, execute that command directly through the OpenClaw root CLI, for example `openclaw demo-plugin ping`.
|
||||
|
||||
@@ -245,7 +241,7 @@ directory remains inert so normal packaged installs still use compiled dist.
|
||||
|
||||
For runtime hook debugging:
|
||||
|
||||
- `openclaw plugins inspect <id> --runtime --json` shows registered hooks and diagnostics from a module-loaded inspection pass. Runtime inspection never downloads missing bundled runtime dependencies; use `openclaw plugins deps --repair` when repair is needed.
|
||||
- `openclaw plugins inspect <id> --runtime --json` shows registered hooks and diagnostics from a module-loaded inspection pass. Runtime inspection never installs dependencies; use `openclaw doctor --fix` to clean legacy dependency state or install missing configured downloadable plugins.
|
||||
- `openclaw gateway status --deep --require-rpc` confirms the reachable Gateway, service/process hints, config path, and RPC health.
|
||||
- Non-bundled conversation hooks (`llm_input`, `llm_output`, `before_agent_finalize`, `agent_end`) require `plugins.entries.<id>.hooks.allowConversationAccess=true`.
|
||||
|
||||
@@ -267,21 +263,6 @@ Plugin install metadata is machine-managed state, not user config. Installs and
|
||||
|
||||
When OpenClaw sees shipped legacy `plugins.installs` records in config, it moves them into the plugin index and removes the config key; if either write fails, the config records are kept so the install metadata is not lost.
|
||||
|
||||
### Runtime deps
|
||||
|
||||
```bash
|
||||
openclaw plugins deps
|
||||
openclaw plugins deps --repair
|
||||
openclaw plugins deps --prune
|
||||
openclaw plugins deps --json
|
||||
```
|
||||
|
||||
`plugins deps` inspects the packaged runtime dependency stage for OpenClaw-owned bundled plugins selected by plugin config, enabled/configured channels, configured model providers, or bundled manifest defaults. It is not the install/update path for third-party npm or ClawHub plugins.
|
||||
|
||||
Use `--repair` when a packaged install reports missing bundled runtime dependencies during Gateway startup or `plugins doctor`. Repair installs only missing enabled bundled-plugin deps with lifecycle scripts disabled. Use `--prune` to remove stale unknown external runtime-dependency roots left behind by older packaged layouts.
|
||||
|
||||
For the full plan, staging, and repair lifecycle, see [Plugin dependency resolution](/plugins/dependency-resolution).
|
||||
|
||||
### Uninstall
|
||||
|
||||
```bash
|
||||
@@ -336,7 +317,7 @@ openclaw plugins inspect <id> --runtime
|
||||
openclaw plugins inspect <id> --json
|
||||
```
|
||||
|
||||
Inspect shows identity, load status, source, manifest capabilities, policy flags, diagnostics, install metadata, bundle capabilities, and any detected MCP or LSP server support without importing plugin runtime by default. Add `--runtime` to load the plugin module and include registered hooks, tools, commands, services, gateway methods, and HTTP routes. Runtime inspection fails with a repair hint when bundled runtime dependencies are missing; use `openclaw plugins deps --repair` to repair them explicitly.
|
||||
Inspect shows identity, load status, source, manifest capabilities, policy flags, diagnostics, install metadata, bundle capabilities, and any detected MCP or LSP server support without importing plugin runtime by default. Add `--runtime` to load the plugin module and include registered hooks, tools, commands, services, gateway methods, and HTTP routes. Runtime inspection reports missing plugin dependencies directly; installs and repairs stay in `openclaw plugins install`, `openclaw plugins update`, and `openclaw doctor --fix`.
|
||||
|
||||
Plugin-owned CLI commands are installed as root `openclaw` command groups. After `inspect --runtime` shows a command under `cliCommands`, run it as `openclaw <command> ...`; for example a plugin that registers `demo-git` can be verified with `openclaw demo-git ping`.
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ If an exact pinned npm plugin update resolves to an artifact whose integrity dif
|
||||
<Note>
|
||||
Post-update plugin sync failures fail the update result and stop restart follow-up work. Fix the plugin install or update error, then rerun `openclaw update`.
|
||||
|
||||
When the updated Gateway starts, enabled bundled plugin runtime dependencies are staged before plugin activation. Package-manager `update.run` restarts bypass the normal idle deferral and restart cooldown after the package tree has been swapped, so the old process cannot keep lazy-loading removed chunks. Service-manager restarts still drain runtime-dependency staging before closing the Gateway.
|
||||
When the updated Gateway starts, plugin loading is verify-only: startup does not run package managers or mutate dependency trees. Package-manager `update.run` restarts bypass the normal idle deferral and restart cooldown after the package tree has been swapped, so the old process cannot keep lazy-loading removed chunks.
|
||||
|
||||
If pnpm bootstrap still fails, the updater stops early with a package-manager-specific error instead of trying `npm run build` inside the checkout.
|
||||
</Note>
|
||||
|
||||
@@ -33,9 +33,9 @@ For multi-endpoint setups, `provider` can also be a custom
|
||||
`models.providers.<id>` entry, such as `ollama-5080`, when that provider sets
|
||||
`api: "ollama"` or another embedding adapter owner.
|
||||
|
||||
For local embeddings with no API key, set `provider: "local"`. Packaged
|
||||
installs retain the native `node-llama-cpp` runtime in OpenClaw's managed plugin
|
||||
runtime-deps tree; run `openclaw doctor --fix` if that tree needs repair.
|
||||
For local embeddings with no API key, set `provider: "local"`. Source checkouts
|
||||
may still require native build approval: `pnpm approve-builds` then
|
||||
`pnpm rebuild node-llama-cpp`.
|
||||
|
||||
Some OpenAI-compatible embedding endpoints require asymmetric labels such as
|
||||
`input_type: "query"` for searches and `input_type: "document"` or `"passage"`
|
||||
|
||||
@@ -339,10 +339,10 @@ That stages grounded durable candidates into the short-term dreaming store while
|
||||
<Accordion title="7. Sandbox image repair">
|
||||
When sandboxing is enabled, doctor checks Docker images and offers to build or switch to legacy names if the current image is missing.
|
||||
</Accordion>
|
||||
<Accordion title="7b. Bundled plugin runtime deps">
|
||||
Doctor verifies runtime dependencies only for bundled plugins that are active in the current config or enabled by their bundled manifest default, for example `plugins.entries.discord.enabled: true`, legacy `channels.discord.enabled: true`, configured `models.providers.*` / agent model refs, or a default-enabled bundled plugin without provider ownership. If any are missing, doctor reports the packages and installs them in `openclaw doctor --fix` / `openclaw doctor --repair` mode. External plugins still use `openclaw plugins install` / `openclaw plugins update`; doctor does not install dependencies for arbitrary plugin paths.
|
||||
<Accordion title="7b. Plugin install cleanup">
|
||||
Doctor removes legacy OpenClaw-generated plugin dependency staging state in `openclaw doctor --fix` / `openclaw doctor --repair` mode. This covers stale generated dependency roots, old install-stage directories, and package-local debris from earlier bundled-plugin dependency repair code.
|
||||
|
||||
During doctor repair, bundled runtime-dependency npm installs report spinner progress in TTY sessions and periodic line progress in piped/headless output. Gateway startup and config reload enter plugin-plan mode before importing bundled plugin runtime modules; normal runtime imports are verify-only and do not spawn package-manager repair. These installs are scoped to the plugin runtime install root, run with scripts disabled, do not write a package lock, and are guarded by an install-root lock so concurrent CLI or Gateway starts do not mutate the same `node_modules` tree at the same time. Stale legacy locks from killed Docker/container starts are reclaimed when their owner metadata cannot prove a current process incarnation and the lock files are old.
|
||||
Doctor can also reinstall configured downloadable plugins when the config references them but the local plugin registry cannot find them. Gateway startup and config reload do not run package managers; plugin installs remain explicit doctor/install/update work.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="8. Gateway service migrations and cleanup hints">
|
||||
|
||||
@@ -517,7 +517,7 @@ Look for:
|
||||
- `browser.executablePath not found` → configured path is invalid.
|
||||
- `browser.cdpUrl must be http(s) or ws(s)` → the configured CDP URL uses an unsupported scheme such as `file:` or `ftp:`.
|
||||
- `browser.cdpUrl has invalid port` → the configured CDP URL has a bad or out-of-range port.
|
||||
- `Playwright is not available in this gateway build; '<feature>' is unsupported.` → the current gateway install lacks the bundled browser plugin's `playwright-core` runtime dependency; run `openclaw doctor --fix`, then restart the gateway. ARIA snapshots and basic page screenshots can still work, but navigation, AI snapshots, CSS-selector element screenshots, and PDF export stay unavailable.
|
||||
- `Playwright is not available in this gateway build; '<feature>' is unsupported.` → the current gateway install lacks the core browser runtime dependency; reinstall or update OpenClaw, then restart the gateway. ARIA snapshots and basic page screenshots can still work, but navigation, AI snapshots, CSS-selector element screenshots, and PDF export stay unavailable.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Chrome MCP / existing-session signatures">
|
||||
|
||||
@@ -498,8 +498,8 @@ openclaw infer image generate \
|
||||
```
|
||||
|
||||
This covers CLI argument parsing, config/default-agent resolution, bundled
|
||||
plugin activation, on-demand bundled runtime-dependency repair, the shared
|
||||
image-generation runtime, and the live provider request.
|
||||
plugin activation, the shared image-generation runtime, and the live provider
|
||||
request. Plugin dependencies are expected to be present before runtime load.
|
||||
|
||||
## Music generation live
|
||||
|
||||
|
||||
@@ -160,9 +160,9 @@ inside every shard.
|
||||
- `pnpm test:docker:npm-onboard-channel-agent`
|
||||
- Builds an npm tarball from the current checkout, installs it globally in
|
||||
Docker, runs non-interactive OpenAI API-key onboarding, configures Telegram
|
||||
by default, verifies enabling the plugin installs runtime dependencies on
|
||||
demand, runs doctor, and runs one local agent turn against a mocked OpenAI
|
||||
endpoint.
|
||||
by default, verifies the packaged plugin runtime loads without startup
|
||||
dependency repair, runs doctor, and runs one local agent turn against a
|
||||
mocked OpenAI endpoint.
|
||||
- Use `OPENCLAW_NPM_ONBOARD_CHANNEL=discord` to run the same packaged-install
|
||||
lane with Discord.
|
||||
- `pnpm test:docker:session-runtime-context`
|
||||
@@ -227,17 +227,17 @@ gh workflow run package-acceptance.yml --ref main \
|
||||
-f suite_profile=smoke
|
||||
```
|
||||
|
||||
- `pnpm test:docker:bundled-channel-deps`
|
||||
- `pnpm test:docker:plugins`
|
||||
- Packs and installs the current OpenClaw build in Docker, starts the Gateway
|
||||
with OpenAI configured, then enables bundled channel/plugins via config
|
||||
edits.
|
||||
- Verifies setup discovery leaves unconfigured plugin runtime dependencies
|
||||
absent, the first configured Gateway or doctor run installs each bundled
|
||||
plugin's runtime dependencies on demand, and a second restart does not
|
||||
reinstall dependencies that were already activated.
|
||||
- Verifies setup discovery leaves unconfigured downloadable plugins absent,
|
||||
the first configured doctor repair installs each missing downloadable
|
||||
plugin explicitly, and a second restart does not run hidden dependency
|
||||
repair.
|
||||
- Also installs a known older npm baseline, enables Telegram before running
|
||||
`openclaw update --tag <candidate>`, and verifies the candidate's
|
||||
post-update doctor repairs bundled channel runtime dependencies without a
|
||||
post-update doctor cleans legacy plugin dependency debris without a
|
||||
harness-side postinstall repair.
|
||||
- `pnpm test:parallels:npm-update`
|
||||
- Runs the native packaged-install update smoke across Parallels guests. Each
|
||||
@@ -263,9 +263,9 @@ gh workflow run package-acceptance.yml --ref main \
|
||||
- The script writes nested lane logs under `/tmp/openclaw-parallels-npm-update.*`.
|
||||
Inspect `windows-update.log`, `macos-update.log`, or `linux-update.log`
|
||||
before assuming the outer wrapper is hung.
|
||||
- Windows update can spend 10 to 15 minutes in post-update doctor/runtime
|
||||
dependency repair on a cold guest; that is still healthy when the nested
|
||||
npm debug log is advancing.
|
||||
- Windows update can spend 10 to 15 minutes in post-update doctor and package
|
||||
update work on a cold guest; that is still healthy when the nested npm
|
||||
debug log is advancing.
|
||||
- Do not run this aggregate wrapper in parallel with individual Parallels
|
||||
macOS, Windows, or Linux smoke lanes. They share VM state and can collide on
|
||||
snapshot restore, package serving, or guest gateway state.
|
||||
@@ -600,7 +600,7 @@ These Docker runners split into two buckets:
|
||||
`OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=90000`. Override those env vars when you
|
||||
explicitly want the larger exhaustive scan.
|
||||
- `test:docker:all` builds the live Docker image once via `test:docker:live-build`, packs OpenClaw once as an npm tarball through `scripts/package-openclaw-for-docker.mjs`, then builds/reuses two `scripts/e2e/Dockerfile` images. The bare image is only the Node/Git runner for install/update/plugin-dependency lanes; those lanes mount the prebuilt tarball. The functional image installs the same tarball into `/app` for built-app functionality lanes. Docker lane definitions live in `scripts/lib/docker-e2e-scenarios.mjs`; planner logic lives in `scripts/lib/docker-e2e-plan.mjs`; `scripts/test-docker-all.mjs` executes the selected plan. The aggregate uses a weighted local scheduler: `OPENCLAW_DOCKER_ALL_PARALLELISM` controls process slots, while resource caps keep heavy live, npm-install, and multi-service lanes from all starting at once. If a single lane is heavier than the active caps, the scheduler can still start it when the pool is empty and then keeps it running alone until capacity is available again. Defaults are 10 slots, `OPENCLAW_DOCKER_ALL_LIVE_LIMIT=9`, `OPENCLAW_DOCKER_ALL_NPM_LIMIT=10`, and `OPENCLAW_DOCKER_ALL_SERVICE_LIMIT=7`; tune `OPENCLAW_DOCKER_ALL_WEIGHT_LIMIT` or `OPENCLAW_DOCKER_ALL_DOCKER_LIMIT` only when the Docker host has more headroom. The runner performs a Docker preflight by default, removes stale OpenClaw E2E containers, prints status every 30 seconds, stores successful lane timings in `.artifacts/docker-tests/lane-timings.json`, and uses those timings to start longer lanes first on later runs. Use `OPENCLAW_DOCKER_ALL_DRY_RUN=1` to print the weighted lane manifest without building or running Docker, or `node scripts/test-docker-all.mjs --plan-json` to print the CI plan for selected lanes, package/image needs, and credentials.
|
||||
- `Package Acceptance` is the GitHub-native package gate for "does this installable tarball work as a product?" It resolves one candidate package from `source=npm`, `source=ref`, `source=url`, or `source=artifact`, uploads it as `package-under-test`, then runs the reusable Docker E2E lanes against that exact tarball instead of repacking the selected ref. `workflow_ref` selects the trusted workflow/harness scripts, while `package_ref` selects the source commit/branch/tag to pack when `source=ref`; this lets current acceptance logic validate older trusted commits. Profiles are ordered by breadth: `smoke` is quick install/channel/agent plus gateway/config, `package` is the package/update/plugin contract plus the keyless upgrade-survivor fixture, the published-baseline upgrade survivor lane, and the default native replacement for most Parallels package/update coverage, `product` adds MCP channels, cron/subagent cleanup, OpenAI web search, and OpenWebUI, and `full` runs the release-path Docker chunks with OpenWebUI. For `published-upgrade-survivor`, Package Acceptance always uses `package-under-test` as the candidate and `published_upgrade_survivor_baseline` as the fallback published baseline, defaulting to `openclaw@latest`; set `published_upgrade_survivor_baselines=release-history` to shard the lane across a deduped matrix of the latest six stable releases, `2026.4.23`, and the latest stable release before `2026-03-15`. The published lane configures its baseline with a baked `openclaw config set` command recipe, then records recipe steps in the lane summary. Release validation runs a custom package delta (`bundled-channel-deps-compat plugins-offline`) plus Telegram package QA because the release-path Docker chunks already cover the overlapping package/update/plugin lanes. Targeted GitHub Docker rerun commands generated from artifacts include prior package artifact, prepared image inputs, and the published upgrade-survivor baseline list when available, so failed lanes can avoid rebuilding the package and images.
|
||||
- `Package Acceptance` is the GitHub-native package gate for "does this installable tarball work as a product?" It resolves one candidate package from `source=npm`, `source=ref`, `source=url`, or `source=artifact`, uploads it as `package-under-test`, then runs the reusable Docker E2E lanes against that exact tarball instead of repacking the selected ref. `workflow_ref` selects the trusted workflow/harness scripts, while `package_ref` selects the source commit/branch/tag to pack when `source=ref`; this lets current acceptance logic validate older trusted commits. Profiles are ordered by breadth: `smoke` is quick install/channel/agent plus gateway/config, `package` is the package/update/plugin contract plus the keyless upgrade-survivor fixture, the published-baseline upgrade survivor lane, and the default native replacement for most Parallels package/update coverage, `product` adds MCP channels, cron/subagent cleanup, OpenAI web search, and OpenWebUI, and `full` runs the release-path Docker chunks with OpenWebUI. For `published-upgrade-survivor`, Package Acceptance always uses `package-under-test` as the candidate and `published_upgrade_survivor_baseline` as the fallback published baseline, defaulting to `openclaw@latest`; set `published_upgrade_survivor_baselines=release-history` to shard the lane across a deduped matrix of the latest six stable releases, `2026.4.23`, and the latest stable release before `2026-03-15`. The published lane configures its baseline with a baked `openclaw config set` command recipe, then records recipe steps in the lane summary. Release validation runs a custom package delta (`plugins-offline plugin-update`) plus Telegram package QA because the release-path Docker chunks already cover the overlapping package/update/plugin lanes. Targeted GitHub Docker rerun commands generated from artifacts include prior package artifact, prepared image inputs, and the published upgrade-survivor baseline list when available, so failed lanes can avoid rebuilding the package and images.
|
||||
- Build and release checks run `scripts/check-cli-bootstrap-imports.mjs` after tsdown. The guard walks the static built graph from `dist/entry.js` and `dist/cli/run-main.js` and fails if pre-dispatch startup imports package dependencies such as Commander, prompt UI, undici, or logging before command dispatch; it also keeps the bundled gateway run chunk under budget and rejects static imports of known cold gateway paths. Packaged CLI smoke also covers root help, onboard help, doctor help, status, config schema, and a model-list command.
|
||||
- Package Acceptance legacy compatibility is capped at `2026.4.25` (`2026.4.25-beta.*` included). Through that cutoff, the harness tolerates only shipped-package metadata gaps: omitted private QA inventory entries, missing `gateway install --wrapper`, missing patch files in the tarball-derived git fixture, missing persisted `update.channel`, legacy plugin install-record locations, missing marketplace install-record persistence, and config metadata migration during `plugins update`. For packages after `2026.4.25`, those paths are strict failures.
|
||||
- Container smoke runners: `test:docker:openwebui`, `test:docker:onboard`, `test:docker:npm-onboard-channel-agent`, `test:docker:update-channel-switch`, `test:docker:upgrade-survivor`, `test:docker:published-upgrade-survivor`, `test:docker:session-runtime-context`, `test:docker:agents-delete-shared-workspace`, `test:docker:gateway-network`, `test:docker:browser-cdp-snapshot`, `test:docker:mcp-channels`, `test:docker:pi-bundle-mcp-tools`, `test:docker:cron-mcp-cleanup`, `test:docker:plugins`, `test:docker:plugin-update`, and `test:docker:config-reload` boot one or more real containers and verify higher-level integration paths.
|
||||
@@ -615,9 +615,9 @@ The live-model Docker runners also bind-mount only the needed CLI auth homes (or
|
||||
- Observability smoke: `pnpm qa:otel:smoke` is a private QA source-checkout lane. It is intentionally not part of package Docker release lanes because the npm tarball omits QA Lab.
|
||||
- Open WebUI live smoke: `pnpm test:docker:openwebui` (script: `scripts/e2e/openwebui-docker.sh`)
|
||||
- Onboarding wizard (TTY, full scaffolding): `pnpm test:docker:onboard` (script: `scripts/e2e/onboard-docker.sh`)
|
||||
- Npm tarball onboarding/channel/agent smoke: `pnpm test:docker:npm-onboard-channel-agent` installs the packed OpenClaw tarball globally in Docker, configures OpenAI via env-ref onboarding plus Telegram by default, verifies doctor repairs activated plugin runtime deps, and runs one mocked OpenAI agent turn. Reuse a prebuilt tarball with `OPENCLAW_CURRENT_PACKAGE_TGZ=/path/to/openclaw-*.tgz`, skip the host rebuild with `OPENCLAW_NPM_ONBOARD_HOST_BUILD=0`, or switch channel with `OPENCLAW_NPM_ONBOARD_CHANNEL=discord`.
|
||||
- Npm tarball onboarding/channel/agent smoke: `pnpm test:docker:npm-onboard-channel-agent` installs the packed OpenClaw tarball globally in Docker, configures OpenAI via env-ref onboarding plus Telegram by default, runs doctor, and runs one mocked OpenAI agent turn. Reuse a prebuilt tarball with `OPENCLAW_CURRENT_PACKAGE_TGZ=/path/to/openclaw-*.tgz`, skip the host rebuild with `OPENCLAW_NPM_ONBOARD_HOST_BUILD=0`, or switch channel with `OPENCLAW_NPM_ONBOARD_CHANNEL=discord`.
|
||||
- Update channel switch smoke: `pnpm test:docker:update-channel-switch` installs the packed OpenClaw tarball globally in Docker, switches from package `stable` to git `dev`, verifies the persisted channel and plugin post-update work, then switches back to package `stable` and checks update status.
|
||||
- Upgrade survivor smoke: `pnpm test:docker:upgrade-survivor` installs the packed OpenClaw tarball over a dirty old-user fixture with agents, channel config, plugin allowlists, stale plugin runtime-deps state, and existing workspace/session files. It runs package update plus non-interactive doctor without live provider or channel keys, then starts a loopback Gateway and checks config/state preservation plus startup/status budgets.
|
||||
- Upgrade survivor smoke: `pnpm test:docker:upgrade-survivor` installs the packed OpenClaw tarball over a dirty old-user fixture with agents, channel config, plugin allowlists, stale plugin dependency state, and existing workspace/session files. It runs package update plus non-interactive doctor without live provider or channel keys, then starts a loopback Gateway and checks config/state preservation plus startup/status budgets.
|
||||
- Published upgrade survivor smoke: `pnpm test:docker:published-upgrade-survivor` installs `openclaw@latest` by default, seeds realistic existing-user files, configures that baseline with a baked command recipe, validates the resulting config, updates that published install to the candidate tarball, runs non-interactive doctor, writes `.artifacts/upgrade-survivor/summary.json`, then starts a loopback Gateway and checks configured intents, state preservation, startup, `/healthz`, `/readyz`, and RPC status budgets. Override one baseline with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC`, ask the aggregate scheduler to expand exact baselines with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS`, and expand issue-shaped fixtures with `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS` such as `reported-issues`; Package Acceptance exposes those as `published_upgrade_survivor_baseline`, `published_upgrade_survivor_baselines`, and `published_upgrade_survivor_scenarios`.
|
||||
- Session runtime context smoke: `pnpm test:docker:session-runtime-context` verifies hidden runtime context transcript persistence plus doctor repair of affected duplicated prompt-rewrite branches.
|
||||
- Bun global install smoke: `bash scripts/e2e/bun-global-install-smoke.sh` packs the current tree, installs it with `bun install -g` in an isolated home, and verifies `openclaw infer image providers --json` returns bundled image providers instead of hanging. Reuse a prebuilt tarball with `OPENCLAW_BUN_GLOBAL_SMOKE_PACKAGE_TGZ=/path/to/openclaw-*.tgz`, skip the host build with `OPENCLAW_BUN_GLOBAL_SMOKE_HOST_BUILD=0`, or copy `dist/` from a built Docker image with `OPENCLAW_BUN_GLOBAL_SMOKE_DIST_IMAGE=openclaw-dockerfile-smoke:local`.
|
||||
@@ -634,9 +634,7 @@ The live-model Docker runners also bind-mount only the needed CLI auth homes (or
|
||||
Set `OPENCLAW_PLUGINS_E2E_CLAWHUB=0` to skip the ClawHub block, or override the default kitchen-sink package/runtime pair with `OPENCLAW_PLUGINS_E2E_CLAWHUB_SPEC` and `OPENCLAW_PLUGINS_E2E_CLAWHUB_ID`. Without `OPENCLAW_CLAWHUB_URL`/`CLAWHUB_URL`, the test uses a hermetic local ClawHub fixture server.
|
||||
- Plugin update unchanged smoke: `pnpm test:docker:plugin-update` (script: `scripts/e2e/plugin-update-unchanged-docker.sh`)
|
||||
- Config reload metadata smoke: `pnpm test:docker:config-reload` (script: `scripts/e2e/config-reload-source-docker.sh`)
|
||||
- Bundled plugin runtime deps: `pnpm test:docker:bundled-channel-deps` builds a small Docker runner image by default, builds and packs OpenClaw once on the host, then mounts that tarball into each Linux install scenario. Reuse the image with `OPENCLAW_SKIP_DOCKER_BUILD=1`, skip the host rebuild after a fresh local build with `OPENCLAW_BUNDLED_CHANNEL_HOST_BUILD=0`, or point at an existing tarball with `OPENCLAW_CURRENT_PACKAGE_TGZ=/path/to/openclaw-*.tgz`. The full Docker aggregate and release-path bundled-channel chunks pre-pack this tarball once, then shard bundled channel checks into independent lanes, including separate update lanes for Telegram, Discord, Slack, Feishu, memory-lancedb, and ACPX. Release chunks split channel smokes, update targets, and setup/runtime contracts into `bundled-channels-core`, `bundled-channels-update-a`, `bundled-channels-update-b`, and `bundled-channels-contracts`; the aggregate `bundled-channels` chunk remains available for manual reruns. The release workflow also splits provider installer chunks and bundled plugin install/uninstall chunks; legacy `package-update`, `plugins-runtime`, and `plugins-integrations` chunks remain aggregate aliases for manual reruns. Use `OPENCLAW_BUNDLED_CHANNELS=telegram,slack` to narrow the channel matrix when running the bundled lane directly, or `OPENCLAW_BUNDLED_CHANNEL_UPDATE_TARGETS=telegram,acpx` to narrow the update scenario. Per-scenario Docker runs default to `OPENCLAW_BUNDLED_CHANNEL_DOCKER_RUN_TIMEOUT=900s`; the multi-target update scenario defaults to `OPENCLAW_BUNDLED_CHANNEL_UPDATE_DOCKER_RUN_TIMEOUT=2400s`. The lane also verifies that `channels.<id>.enabled=false` and `plugins.entries.<id>.enabled=false` suppress doctor/runtime-dependency repair.
|
||||
- Narrow bundled plugin runtime deps while iterating by disabling unrelated scenarios, for example:
|
||||
`OPENCLAW_BUNDLED_CHANNEL_SCENARIOS=0 OPENCLAW_BUNDLED_CHANNEL_UPDATE_SCENARIO=0 OPENCLAW_BUNDLED_CHANNEL_ROOT_OWNED_SCENARIO=0 OPENCLAW_BUNDLED_CHANNEL_SETUP_ENTRY_SCENARIO=0 pnpm test:docker:bundled-channel-deps`.
|
||||
- Plugins: `pnpm test:docker:plugins` covers install smoke, local ClawHub fixture installs, marketplace updates, npm package dependency installs, and Claude-bundle enable/inspect. `pnpm test:docker:plugin-update` covers unchanged update behavior for installed plugins.
|
||||
|
||||
To prebuild and reuse the shared functional image manually:
|
||||
|
||||
|
||||
@@ -122,19 +122,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/<agentId>/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 |
|
||||
| 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/<agentId>/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 packages | `/home/node/.openclaw/npm`, `/home/node/.openclaw/git` | Host volume mount | Downloadable plugin package roots |
|
||||
| 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
|
||||
|
||||
|
||||
@@ -126,10 +126,9 @@ The setup script accepts these optional environment variables:
|
||||
| ------------------------------------------ | --------------------------------------------------------------- |
|
||||
| `OPENCLAW_IMAGE` | Use a remote image instead of building locally |
|
||||
| `OPENCLAW_DOCKER_APT_PACKAGES` | Install extra apt packages during build (space-separated) |
|
||||
| `OPENCLAW_EXTENSIONS` | Pre-install plugin deps at build time (space-separated names) |
|
||||
| `OPENCLAW_EXTENSIONS` | Include selected bundled plugin helpers at build time |
|
||||
| `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_SKIP_ONBOARDING` | Skip the interactive onboarding step (`1`, `true`, `yes`, `on`) |
|
||||
| `OPENCLAW_DOCKER_SOCKET` | Override Docker socket path |
|
||||
@@ -163,11 +162,8 @@ export OTEL_SERVICE_NAME="openclaw-gateway"
|
||||
```
|
||||
|
||||
The official OpenClaw Docker release image includes the bundled
|
||||
`diagnostics-otel` plugin source. Depending on the image and cache state, the
|
||||
Gateway may still stage plugin-local OpenTelemetry runtime dependencies the
|
||||
first time the plugin is enabled, so allow that first boot to reach the package
|
||||
registry or prewarm the image in your release lane. To enable export, allow and
|
||||
enable the `diagnostics-otel` plugin in config, then set
|
||||
`diagnostics-otel` plugin source. To enable export, allow and enable the
|
||||
`diagnostics-otel` plugin in config, then set
|
||||
`diagnostics.otel.enabled=true` or use the config example in
|
||||
[OpenTelemetry export](/gateway/opentelemetry). Collector auth headers are
|
||||
configured through `diagnostics.otel.headers`, not through Docker environment
|
||||
@@ -273,24 +269,16 @@ That mounted config directory is where OpenClaw keeps:
|
||||
- `agents/<agentId>/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.
|
||||
Installed downloadable plugins store their package state under the mounted
|
||||
OpenClaw home, so plugin install records and package roots survive container
|
||||
replacement. Gateway startup does not generate bundled-plugin dependency trees.
|
||||
|
||||
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`,
|
||||
the `openclaw-plugin-runtime-deps` Docker volume, and rolling file logs under
|
||||
`/tmp/openclaw/`.
|
||||
**Disk growth hotspots:** watch `media/`, session JSONL files,
|
||||
`cron/runs/*.jsonl`, installed plugin package roots, and rolling file logs
|
||||
under `/tmp/openclaw/`.
|
||||
|
||||
### Shell helpers (optional)
|
||||
|
||||
|
||||
@@ -107,37 +107,21 @@ bun add -g openclaw@latest
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Read-only package tree">
|
||||
OpenClaw treats packaged global installs as read-only at runtime, even when the global package directory is writable by the current user. Bundled plugin runtime dependencies are staged into a writable runtime directory instead of mutating the package tree. This keeps `openclaw update` from racing with a running gateway or local agent that is repairing plugin dependencies during the same install.
|
||||
OpenClaw treats packaged global installs as read-only at runtime, even when the global package directory is writable by the current user. Plugin package installs live in OpenClaw-owned npm/git roots under the user config directory, and Gateway startup does not mutate the OpenClaw package tree.
|
||||
|
||||
Some Linux npm setups install global packages under root-owned directories such as `/usr/lib/node_modules/openclaw`. OpenClaw supports that layout through the same external staging path.
|
||||
Some Linux npm setups install global packages under root-owned directories such as `/usr/lib/node_modules/openclaw`. OpenClaw supports that layout because plugin install/update commands write outside that global package directory.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Hardened systemd units">
|
||||
Set a writable stage directory that is included in `ReadWritePaths`:
|
||||
Give OpenClaw write access to its config/state roots so explicit plugin installs, plugin updates, and doctor cleanup can persist their changes:
|
||||
|
||||
```ini
|
||||
Environment=OPENCLAW_PLUGIN_STAGE_DIR=/var/lib/openclaw/plugin-runtime-deps
|
||||
ReadWritePaths=/var/lib/openclaw /home/openclaw/.openclaw /tmp
|
||||
```
|
||||
|
||||
`OPENCLAW_PLUGIN_STAGE_DIR` also accepts a path list. OpenClaw resolves bundled plugin runtime dependencies left-to-right across the listed roots, treats earlier roots as read-only preinstalled layers, and installs or repairs only into the final writable root:
|
||||
|
||||
```ini
|
||||
Environment=OPENCLAW_PLUGIN_STAGE_DIR=/opt/openclaw/plugin-runtime-deps:/var/lib/openclaw/plugin-runtime-deps
|
||||
ReadWritePaths=/var/lib/openclaw /home/openclaw/.openclaw /tmp
|
||||
```
|
||||
|
||||
If `OPENCLAW_PLUGIN_STAGE_DIR` is not set, OpenClaw uses `$STATE_DIRECTORY` when systemd provides it, then falls back to `~/.openclaw/plugin-runtime-deps`. The repair step treats that stage as an OpenClaw-owned local package root and ignores user npm prefix and global settings, so global-install npm config does not redirect bundled plugin dependencies into `~/node_modules` or the global package tree.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Disk-space preflight">
|
||||
Before package updates and bundled runtime-dependency repairs, OpenClaw tries a best-effort disk-space check for the target volume. Low space produces a warning with the checked path, but does not block the update because filesystem quotas, snapshots, and network volumes can change after the check. The actual npm install, copy, and post-install verification remain authoritative.
|
||||
</Accordion>
|
||||
<Accordion title="Bundled plugin runtime dependencies">
|
||||
Packaged installs keep bundled plugin runtime dependencies out of the read-only package tree. On startup and during `openclaw doctor --fix`, OpenClaw repairs runtime dependencies only for bundled plugins that are active in config, active through legacy channel config, or enabled by their bundled manifest default. Persisted channel auth state alone does not trigger Gateway startup runtime-dependency repair.
|
||||
|
||||
Explicit disablement wins. A disabled plugin or channel does not get its runtime dependencies repaired just because it exists in the package. External plugins and custom load paths still use `openclaw plugins install` or `openclaw plugins update`.
|
||||
|
||||
Before package updates and explicit plugin installs, OpenClaw tries a best-effort disk-space check for the target volume. Low space produces a warning with the checked path, but does not block the update because filesystem quotas, snapshots, and network volumes can change after the check. The actual package-manager install and post-install verification remain authoritative.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
|
||||
@@ -118,8 +118,7 @@ loader state when code or installed artifacts are actually loaded, such as:
|
||||
- `PluginLoaderCacheState` and compatible active runtime registries
|
||||
- jiti/module caches and public-surface loader caches used to avoid importing
|
||||
the same runtime surface repeatedly
|
||||
- runtime dependency mirrors and filesystem caches for installed plugin
|
||||
artifacts
|
||||
- filesystem caches for installed plugin artifacts
|
||||
- short-lived per-call maps for path normalization or duplicate resolution
|
||||
|
||||
Those caches are data-plane implementation details. They must not answer
|
||||
|
||||
@@ -258,13 +258,12 @@ dual-format packages from being partially installed as bundles.
|
||||
- Third-party compatible bundles do not get startup `npm install` repair. They
|
||||
should be installed through `openclaw plugins install` and ship everything
|
||||
they need in the installed plugin directory.
|
||||
- OpenClaw-owned packaged bundled plugins have a narrow exception: when one is
|
||||
enabled, Gateway startup can repair missing declared runtime dependencies
|
||||
before import. Operators can inspect or repair that stage with
|
||||
`openclaw plugins deps`.
|
||||
- The release pipeline is still responsible for shipping a complete bundled
|
||||
dependency payload when possible (see the postpublish verification rule in
|
||||
[Releasing](/reference/RELEASING)).
|
||||
- OpenClaw-owned bundled plugins are either shipped lightweight in core or
|
||||
downloadable through the plugin installer. Gateway startup never runs a
|
||||
package manager for them.
|
||||
- `openclaw doctor --fix` removes legacy staged dependency directories and can
|
||||
install configured downloadable plugins that are missing from the local
|
||||
plugin index.
|
||||
|
||||
## Security
|
||||
|
||||
|
||||
@@ -458,11 +458,10 @@ By default, the plugin starts OpenClaw's managed Codex binary locally with:
|
||||
codex app-server --listen stdio://
|
||||
```
|
||||
|
||||
The managed binary is declared as a bundled plugin runtime dependency and staged
|
||||
with the rest of the `codex` plugin dependencies. This keeps the app-server
|
||||
version tied to the bundled plugin instead of whichever separate Codex CLI
|
||||
happens to be installed locally. Set `appServer.command` only when you
|
||||
intentionally want to run a different executable.
|
||||
The managed binary is shipped with the `codex` plugin package. This keeps the
|
||||
app-server version tied to the bundled plugin instead of whichever separate
|
||||
Codex CLI happens to be installed locally. Set `appServer.command` only when
|
||||
you intentionally want to run a different executable.
|
||||
|
||||
By default, OpenClaw starts local Codex harness sessions in YOLO mode:
|
||||
`approvalPolicy: "never"`, `approvalsReviewer: "user"`, and
|
||||
|
||||
@@ -1,214 +1,103 @@
|
||||
---
|
||||
summary: "How OpenClaw plans, stages, and repairs bundled plugin runtime dependencies"
|
||||
summary: "How OpenClaw installs plugin packages and resolves plugin dependencies"
|
||||
read_when:
|
||||
- You are debugging bundled plugin runtime dependency repair
|
||||
- You are debugging plugin package installs
|
||||
- You are changing plugin startup, doctor, or package-manager install behavior
|
||||
- You are maintaining packaged OpenClaw installs or bundled plugin manifests
|
||||
title: "Plugin dependency resolution"
|
||||
sidebarTitle: "Dependencies"
|
||||
---
|
||||
|
||||
OpenClaw does not install every bundled plugin dependency tree at package install
|
||||
time. It first derives an effective plugin plan from config and plugin metadata,
|
||||
then stages runtime dependencies only for bundled OpenClaw-owned plugins that
|
||||
the plan can actually load.
|
||||
# Plugin dependency resolution
|
||||
|
||||
This page covers packaged runtime dependencies for bundled OpenClaw plugins.
|
||||
Third-party plugins and custom plugin paths still use explicit plugin
|
||||
installation commands such as `openclaw plugins install` and
|
||||
`openclaw plugins update`.
|
||||
OpenClaw keeps plugin dependency work at install/update time. Runtime loading
|
||||
does not run package managers, repair dependency trees, or mutate the OpenClaw
|
||||
package directory.
|
||||
|
||||
## Responsibility split
|
||||
|
||||
OpenClaw owns the plan and policy:
|
||||
Plugin packages own their dependency graph:
|
||||
|
||||
- which plugins are active for this config
|
||||
- which dependency roots are writable or read-only
|
||||
- when repair is allowed
|
||||
- which plugin ids are staged for startup
|
||||
- final checks before importing plugin runtime modules
|
||||
- runtime dependencies live in the plugin package `dependencies` or
|
||||
`optionalDependencies`
|
||||
- SDK/core imports are peer or supplied OpenClaw imports
|
||||
- local development plugins bring their own already-installed dependencies
|
||||
- npm and git plugins are installed into OpenClaw-owned package roots
|
||||
|
||||
The package manager owns dependency convergence:
|
||||
OpenClaw owns only the plugin lifecycle:
|
||||
|
||||
- package graph resolution
|
||||
- production, optional, and peer dependency handling
|
||||
- `node_modules` layout
|
||||
- package integrity
|
||||
- lock and install metadata
|
||||
|
||||
In practice, OpenClaw should decide what needs to exist. `pnpm` or `npm` should
|
||||
make the filesystem match that decision.
|
||||
|
||||
OpenClaw also owns the per-install-root coordination lock. Package managers
|
||||
protect their own install transaction, but they do not serialize OpenClaw's
|
||||
manifest writes, isolated-stage copy/rename, final validation, or plugin import
|
||||
against another Gateway, doctor, or CLI process touching the same runtime
|
||||
dependency root.
|
||||
|
||||
## Effective plugin plan
|
||||
|
||||
The effective plugin plan is derived from config plus discovered plugin
|
||||
metadata. These inputs can activate bundled plugin runtime dependencies:
|
||||
|
||||
- `plugins.entries.<id>.enabled`
|
||||
- `plugins.allow`, `plugins.deny`, and `plugins.enabled`
|
||||
- legacy channel config such as `channels.telegram.enabled`
|
||||
- configured providers, models, or CLI backend references that require a plugin
|
||||
- bundled manifest defaults such as `enabledByDefault`
|
||||
- the installed plugin index and bundled manifest metadata
|
||||
|
||||
Explicit disablement wins. A disabled plugin, denied plugin id, disabled plugin
|
||||
system, or disabled channel does not trigger runtime dependency repair. Persisted
|
||||
auth state alone also does not activate a bundled channel or provider.
|
||||
|
||||
The plugin plan is the stable input. The generated dependency materialization is
|
||||
an output of that plan.
|
||||
|
||||
## Startup flow
|
||||
|
||||
Gateway startup parses config and builds the startup plugin lookup table before
|
||||
plugin runtime modules are loaded. Startup then stages runtime dependencies only
|
||||
for the `startupPluginIds` selected by that plan.
|
||||
|
||||
For packaged installs, dependency staging is allowed before plugin import. After
|
||||
staging, the runtime loader imports startup plugins with install repair disabled;
|
||||
at that point missing dependency materialization is treated as a load failure,
|
||||
not another repair loop.
|
||||
|
||||
When startup dependency staging is deferred behind the HTTP bind, Gateway
|
||||
readiness stays blocked on the `plugin-runtime-deps` reason until the selected
|
||||
startup plugin dependencies are materialized and the startup plugin runtime has
|
||||
loaded.
|
||||
|
||||
## When repair runs
|
||||
|
||||
Runtime dependency repair should run when one of these is true:
|
||||
|
||||
- the effective plugin plan changed and adds bundled plugins that need runtime
|
||||
dependencies
|
||||
- the generated dependency manifest no longer matches the effective plan
|
||||
- expected installed package sentinels are missing or incomplete
|
||||
- `openclaw doctor --fix` or `openclaw plugins deps --repair` was requested
|
||||
|
||||
Runtime dependency repair should not run just because OpenClaw started. A normal
|
||||
startup with an unchanged plan and complete dependency materialization should
|
||||
skip package-manager work.
|
||||
|
||||
Commands that edit config, enable plugins, or repair doctor findings can enter
|
||||
plugin plan mode once, materialize the newly required bundled dependencies, then
|
||||
return to the normal command flow. Local `openclaw onboard` and
|
||||
`openclaw configure` do this automatically after they successfully write config,
|
||||
so the next Gateway run does not discover missing bundled plugin packages after
|
||||
startup has already begun. Remote onboarding/configure stays read-only for local
|
||||
runtime deps.
|
||||
|
||||
## Hot reload rule
|
||||
|
||||
Hot reload paths that can change active plugins must go back through plugin plan
|
||||
mode before loading plugin runtime. The reload should compare the new effective
|
||||
plugin plan with the previous one, stage missing dependencies for newly active
|
||||
bundled plugins, then load or restart the affected runtime.
|
||||
|
||||
If a config reload does not change the effective plugin plan, it should not
|
||||
repair bundled runtime dependencies.
|
||||
|
||||
## Package manager execution
|
||||
|
||||
OpenClaw writes a generated install manifest for the selected bundled runtime
|
||||
dependencies and runs the package manager in the runtime dependency install
|
||||
root. It prefers `pnpm` when available and falls back to the Node-bundled `npm`
|
||||
runner.
|
||||
|
||||
The `pnpm` path uses production dependencies, disables lifecycle scripts, ignores
|
||||
the workspace, and keeps the store inside the install root:
|
||||
|
||||
```bash
|
||||
pnpm install \
|
||||
--prod \
|
||||
--ignore-scripts \
|
||||
--ignore-workspace \
|
||||
--config.frozen-lockfile=false \
|
||||
--config.minimum-release-age=0 \
|
||||
--config.store-dir=<install-root>/.openclaw-pnpm-store \
|
||||
--config.node-linker=hoisted \
|
||||
--config.virtual-store-dir=.pnpm
|
||||
```
|
||||
|
||||
The `npm` fallback uses the safe npm install wrapper with production
|
||||
dependencies, lifecycle scripts disabled, workspace mode disabled, audit
|
||||
disabled, fund output disabled, legacy peer dependency behavior, and package-lock
|
||||
output enabled for the generated install root.
|
||||
|
||||
After install, OpenClaw validates the staged dependency tree before making it
|
||||
visible to the runtime dependency root. Isolated staging is copied into the
|
||||
runtime dependency root and validated again.
|
||||
|
||||
The whole repair/materialization section is guarded by an install-root lock.
|
||||
Current lock owners record PID, process start-time when available, and creation
|
||||
time. Legacy locks without process start-time or creation-time evidence are only
|
||||
reclaimed by filesystem age, so recycled Docker PID 1 locks recover without
|
||||
expiring normal long-running current installs by age alone.
|
||||
- discover the plugin source
|
||||
- install or update the package when explicitly requested
|
||||
- record the install metadata
|
||||
- load the plugin entrypoint
|
||||
- fail with an actionable error when dependencies are missing
|
||||
|
||||
## Install roots
|
||||
|
||||
Packaged installs must not mutate read-only package directories. OpenClaw can
|
||||
read dependency roots from packaged layers, but writes generated runtime
|
||||
dependencies to a writable stage such as:
|
||||
OpenClaw uses stable per-source roots:
|
||||
|
||||
- `OPENCLAW_PLUGIN_STAGE_DIR`
|
||||
- `$STATE_DIRECTORY`
|
||||
- `~/.openclaw/plugin-runtime-deps`
|
||||
- `/var/lib/openclaw/plugin-runtime-deps` in container-style installs
|
||||
- npm packages install under `~/.openclaw/npm`
|
||||
- git packages clone under `~/.openclaw/git`
|
||||
- local/path/archive installs are copied or referenced without dependency repair
|
||||
|
||||
The writable root is the final materialization target. Older read-only roots are
|
||||
kept as compatibility layers only when needed.
|
||||
|
||||
When a packaged OpenClaw update changes the versioned writable root but the
|
||||
selected bundled-plugin dependency plan is still satisfied by a previous staged
|
||||
root, repair reuses that previous `node_modules` tree instead of running the
|
||||
package manager again. The new versioned root still gets its own current package
|
||||
runtime mirror, so plugin code comes from the current OpenClaw package while
|
||||
unchanged dependency trees are shared across updates. Reuse skips previous roots
|
||||
with an active OpenClaw runtime-dependency lock, so a new root does not link to a
|
||||
dependency tree that another Gateway, doctor, or CLI process is currently
|
||||
repairing.
|
||||
|
||||
## Doctor and CLI commands
|
||||
|
||||
Use `plugins deps` to inspect or repair bundled plugin runtime dependency
|
||||
materialization:
|
||||
npm installs run in the npm root with:
|
||||
|
||||
```bash
|
||||
openclaw plugins deps
|
||||
openclaw plugins deps --json
|
||||
openclaw plugins deps --repair
|
||||
openclaw plugins deps --prune
|
||||
npm install --prefix ~/.openclaw/npm <spec> --omit=dev --ignore-scripts --no-audit --no-fund
|
||||
```
|
||||
|
||||
Use doctor when the dependency state is part of broader install health:
|
||||
git installs clone or refresh the repository, then run:
|
||||
|
||||
```bash
|
||||
openclaw doctor
|
||||
npm install --omit=dev --ignore-scripts --no-audit --no-fund
|
||||
```
|
||||
|
||||
The installed plugin then loads from that package directory, so package-local
|
||||
`node_modules` resolution works the same way it does for a normal Node package.
|
||||
|
||||
## Local plugins
|
||||
|
||||
Local plugins are treated as developer-controlled directories. OpenClaw does not
|
||||
run `npm install`, `pnpm install`, or dependency repair for them. If a local
|
||||
plugin has dependencies, install them in that plugin before loading it.
|
||||
|
||||
TypeScript local plugins can use the emergency Jiti path. Packaged JavaScript
|
||||
plugins load through native import/require instead of Jiti.
|
||||
|
||||
## Startup and reload
|
||||
|
||||
Gateway startup and config reload never install plugin dependencies. They read
|
||||
the plugin install records, compute the entrypoint, and load it.
|
||||
|
||||
If a dependency is missing at runtime, the plugin fails to load and the error
|
||||
should point the operator to an explicit fix:
|
||||
|
||||
```bash
|
||||
openclaw plugins update <id>
|
||||
openclaw plugins install <source>
|
||||
openclaw doctor --fix
|
||||
```
|
||||
|
||||
`plugins deps` and doctor operate on OpenClaw-owned bundled plugin runtime
|
||||
dependencies selected by the effective plugin plan. They are not third-party
|
||||
plugin install or update commands.
|
||||
`doctor --fix` can clean legacy OpenClaw-generated dependency state and install
|
||||
configured downloadable plugins that are missing from the local install records.
|
||||
It does not repair dependencies for an already-installed local plugin.
|
||||
|
||||
## Troubleshooting
|
||||
## Bundled plugins
|
||||
|
||||
If a packaged install reports missing bundled runtime dependencies:
|
||||
Lightweight and core-critical bundled plugins are shipped as part of OpenClaw.
|
||||
They should either have no heavy runtime dependency tree or be moved out to a
|
||||
downloadable package on ClawHub/npm.
|
||||
|
||||
1. Run `openclaw plugins deps --json` to inspect the selected plan and missing
|
||||
packages.
|
||||
2. Run `openclaw plugins deps --repair` or `openclaw doctor --fix` to repair the
|
||||
writable dependency stage.
|
||||
3. If the install root is read-only, set `OPENCLAW_PLUGIN_STAGE_DIR` to a
|
||||
writable path and rerun repair.
|
||||
4. Restart Gateway after repair if the missing dependency blocked startup plugin
|
||||
loading.
|
||||
Bundled plugin manifests must not request dependency staging. Large or optional
|
||||
plugin functionality should be packaged as a normal plugin and installed through
|
||||
the same npm/git/ClawHub path as third-party plugins.
|
||||
|
||||
In source checkouts, the workspace install usually provides bundled plugin
|
||||
dependencies. Run `pnpm install` for source dependency repair instead of using
|
||||
packaged runtime dependency repair as the first step.
|
||||
## Legacy cleanup
|
||||
|
||||
Older OpenClaw versions generated bundled-plugin dependency roots at startup or
|
||||
during doctor repair. Current doctor cleanup removes those stale directories and
|
||||
symlinks when `--fix` is used, including old `plugin-runtime-deps` roots,
|
||||
`.openclaw-runtime-deps*` manifests, generated plugin `node_modules`, install
|
||||
stage directories, and package-local pnpm stores.
|
||||
|
||||
These paths are legacy debris only. New installs should not create them.
|
||||
|
||||
@@ -294,9 +294,9 @@ supports `${ENV_VAR}` expansion:
|
||||
## Runtime dependencies
|
||||
|
||||
`memory-lancedb` depends on the native `@lancedb/lancedb` package. Packaged
|
||||
OpenClaw installs first try the bundled runtime dependency and can repair the
|
||||
plugin runtime dependency under OpenClaw state when the bundled import is not
|
||||
available.
|
||||
OpenClaw treats that package as part of the plugin package. Gateway startup
|
||||
does not repair plugin dependencies; if the dependency is missing, reinstall or
|
||||
update the plugin package and restart the Gateway.
|
||||
|
||||
If an older install logs a missing `dist/package.json` or missing
|
||||
`@lancedb/lancedb` error during plugin load, upgrade OpenClaw and restart the
|
||||
|
||||
@@ -355,8 +355,8 @@ Facade-loaded bundled plugin public surfaces (`api.ts`, `runtime-api.ts`,
|
||||
active runtime config snapshot when OpenClaw is already running. If no runtime
|
||||
snapshot exists yet, they fall back to the resolved config file on disk.
|
||||
Packaged bundled plugin facades should be loaded through OpenClaw's plugin
|
||||
facade loaders; direct imports from `dist/extensions/...` bypass staged runtime
|
||||
dependency mirrors that packaged installs use for plugin-owned dependencies.
|
||||
facade loaders; direct imports from `dist/extensions/...` bypass the manifest
|
||||
and runtime sidecar checks that packaged installs use for plugin-owned code.
|
||||
|
||||
Provider plugins can expose a narrow plugin-local contract barrel when a
|
||||
helper is intentionally provider-specific and does not belong in a generic SDK
|
||||
|
||||
@@ -513,14 +513,14 @@ openclaw plugins install <package-name>
|
||||
```
|
||||
|
||||
<Info>
|
||||
For npm-sourced installs, `openclaw plugins install` runs project-local `npm install --ignore-scripts` (no lifecycle scripts), ignoring inherited global npm install settings. Keep plugin dependency trees pure JS/TS and avoid packages that require `postinstall` builds.
|
||||
For npm-sourced installs, `openclaw plugins install` installs the package under `~/.openclaw/npm` with lifecycle scripts disabled. Keep plugin dependency trees pure JS/TS and avoid packages that require `postinstall` builds.
|
||||
</Info>
|
||||
|
||||
<Note>
|
||||
Bundled OpenClaw-owned plugins are the only startup repair exception: when a packaged install sees one enabled by plugin config, legacy channel config, or its bundled default-enabled manifest, startup installs that plugin's missing runtime dependencies before import. Operators can inspect or repair that stage with `openclaw plugins deps`. Third-party plugins should not rely on startup installs; keep using the explicit plugin installer.
|
||||
Gateway startup does not install plugin dependencies. npm/git/ClawHub install flows own dependency convergence; local plugins must already have their dependencies installed.
|
||||
</Note>
|
||||
|
||||
Bundled package-level runtime deps are explicit metadata, not inferred from built JavaScript at gateway startup. If a shared OpenClaw root dependency must be available inside the external bundled-plugin runtime mirror, declare it in `openclaw.bundle.mirroredRootRuntimeDependencies` in the root package manifest.
|
||||
Bundled package metadata is explicit, not inferred from built JavaScript at gateway startup. Runtime dependencies belong in the plugin package that owns them; packaged OpenClaw startup never repairs or mirrors plugin dependencies.
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -211,11 +211,10 @@ Validation` or from the `main`/release workflow ref so workflow logic and
|
||||
- npm release preflight fails closed unless the tarball includes both
|
||||
`dist/control-ui/index.html` and a non-empty `dist/control-ui/assets/` payload
|
||||
so we do not ship an empty browser dashboard again
|
||||
- Post-publish verification also checks that the published registry install
|
||||
contains non-empty bundled plugin runtime deps under the root `dist/*`
|
||||
layout. A release that ships with missing or empty bundled plugin
|
||||
dependency payloads fails the postpublish verifier and cannot be promoted
|
||||
to `latest`.
|
||||
- Post-publish verification also checks that published plugin entrypoints and
|
||||
package metadata are present in the installed registry layout. A release that
|
||||
ships missing plugin runtime payloads fails the postpublish verifier and
|
||||
cannot be promoted to `latest`.
|
||||
- `pnpm test:install:smoke` also enforces the npm pack `unpackedSize` budget on
|
||||
the candidate update tarball, so installer e2e catches accidental pack bloat
|
||||
before the release publish path
|
||||
@@ -370,13 +369,8 @@ Release Docker coverage includes:
|
||||
`plugins-runtime-install-a`, `plugins-runtime-install-b`,
|
||||
`plugins-runtime-install-c`, `plugins-runtime-install-d`,
|
||||
`plugins-runtime-install-e`, `plugins-runtime-install-f`,
|
||||
`plugins-runtime-install-g`, `plugins-runtime-install-h`,
|
||||
`bundled-channels-core`, `bundled-channels-update-a`,
|
||||
`bundled-channels-update-discord`, `bundled-channels-update-b`, and
|
||||
`bundled-channels-contracts`
|
||||
`plugins-runtime-install-g`, and `plugins-runtime-install-h`
|
||||
- OpenWebUI coverage inside the `plugins-runtime-services` chunk when requested
|
||||
- split bundled-channel dependency lanes across channel-smoke, update-target,
|
||||
and setup/runtime contract chunks instead of one large bundled-channel job
|
||||
- split bundled plugin install/uninstall lanes
|
||||
`bundled-plugin-install-uninstall-0` through
|
||||
`bundled-plugin-install-uninstall-23`
|
||||
@@ -430,11 +424,11 @@ Supported candidate sources:
|
||||
|
||||
`OpenClaw Release Checks` runs Package Acceptance with `source=ref`,
|
||||
`package_ref=<release-ref>`, `suite_profile=custom`,
|
||||
`docker_lanes=bundled-channel-deps-compat plugins-offline`, and
|
||||
`telegram_mode=mock-openai`. The release-path Docker chunks cover the
|
||||
overlapping install, update, and plugin-update lanes; Package Acceptance keeps
|
||||
artifact-native bundled-channel compat, offline plugin fixtures, and Telegram
|
||||
package QA against the same resolved tarball. It is the GitHub-native
|
||||
`docker_lanes=plugins-offline plugin-update`, and `telegram_mode=mock-openai`.
|
||||
The release-path Docker chunks cover the overlapping install, update, and
|
||||
plugin-update lanes; Package Acceptance keeps offline plugin fixtures, plugin
|
||||
update, and Telegram package QA against the same resolved tarball. It is the
|
||||
GitHub-native
|
||||
replacement for most of the package/update coverage that previously required
|
||||
Parallels. Cross-OS release checks still matter for OS-specific onboarding,
|
||||
installer, and platform behavior, but package/update product validation should
|
||||
|
||||
@@ -53,11 +53,11 @@ or Docker-facing stages need it.
|
||||
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Release target | **Job:** `Resolve target ref`<br />**Backing workflow:** none<br />**Tests:** selected ref, optional expected SHA, profile, rerun group, and focused live suite filter.<br />**Rerun:** `rerun_group=release-checks`. |
|
||||
| Package artifact | **Job:** `Prepare release package artifact`<br />**Backing workflow:** none<br />**Tests:** packs or resolves one candidate tarball and uploads `release-package-under-test` for downstream package-facing checks.<br />**Rerun:** the affected package, cross-OS, or live/E2E group. |
|
||||
| Install smoke | **Job:** `Run install smoke`<br />**Backing workflow:** `Install Smoke`<br />**Tests:** full install path with root Dockerfile smoke image reuse, QR package install, root and gateway Docker smokes, installer Docker tests, Bun global install image-provider smoke, and fast bundled-plugin Docker E2E.<br />**Rerun:** `rerun_group=install-smoke`. |
|
||||
| Install smoke | **Job:** `Run install smoke`<br />**Backing workflow:** `Install Smoke`<br />**Tests:** full install path with root Dockerfile smoke image reuse, QR package install, root and gateway Docker smokes, installer Docker tests, Bun global install image-provider smoke, and fast bundled-plugin install/uninstall E2E.<br />**Rerun:** `rerun_group=install-smoke`. |
|
||||
| Cross-OS | **Job:** `cross_os_release_checks`<br />**Backing workflow:** `OpenClaw Cross-OS Release Checks (Reusable)`<br />**Tests:** fresh and upgrade lanes on Linux, Windows, and macOS for the selected provider and mode, using the candidate tarball plus a baseline package.<br />**Rerun:** `rerun_group=cross-os`. |
|
||||
| Repo and live E2E | **Job:** `Run repo/live E2E validation`<br />**Backing workflow:** `OpenClaw Live And E2E Checks (Reusable)`<br />**Tests:** repository E2E, live cache, OpenAI websocket streaming, native live provider and plugin shards, and Docker-backed live model/backend/gateway harnesses selected by `release_profile`.<br />**Rerun:** `rerun_group=live-e2e`, optionally with `live_suite_filter`. |
|
||||
| Docker release path | **Job:** `Run Docker release-path validation`<br />**Backing workflow:** `OpenClaw Live And E2E Checks (Reusable)`<br />**Tests:** release-path Docker chunks against the shared package artifact.<br />**Rerun:** `rerun_group=live-e2e`. |
|
||||
| Package Acceptance | **Job:** `Run package acceptance`<br />**Backing workflow:** `Package Acceptance`<br />**Tests:** artifact-native bundled-channel dependency compatibility, offline plugin package fixtures, and mock-OpenAI Telegram package acceptance against the same tarball.<br />**Rerun:** `rerun_group=package`. |
|
||||
| Package Acceptance | **Job:** `Run package acceptance`<br />**Backing workflow:** `Package Acceptance`<br />**Tests:** offline plugin package fixtures, plugin update, and mock-OpenAI Telegram package acceptance against the same tarball.<br />**Rerun:** `rerun_group=package`. |
|
||||
| QA parity | **Job:** `Run QA Lab parity lane` and `Run QA Lab parity report`<br />**Backing workflow:** direct jobs<br />**Tests:** candidate and baseline agentic parity packs, then the parity report.<br />**Rerun:** `rerun_group=qa-parity` or `rerun_group=qa`. |
|
||||
| QA live Matrix | **Job:** `Run QA Lab live Matrix lane`<br />**Backing workflow:** direct job<br />**Tests:** fast live Matrix QA profile in the `qa-live-shared` environment.<br />**Rerun:** `rerun_group=qa-live` or `rerun_group=qa`. |
|
||||
| QA live Telegram | **Job:** `Run QA Lab live Telegram lane`<br />**Backing workflow:** direct job<br />**Tests:** live Telegram QA with Convex CI credential leases.<br />**Rerun:** `rerun_group=qa-live` or `rerun_group=qa`. |
|
||||
@@ -68,18 +68,15 @@ or Docker-facing stages need it.
|
||||
The Docker release-path stage runs these chunks when `live_suite_filter` is
|
||||
empty:
|
||||
|
||||
| Chunk | Coverage |
|
||||
| ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| `core` | Core Docker release-path smoke lanes. |
|
||||
| `package-update-openai` | OpenAI package install and update behavior. |
|
||||
| `package-update-anthropic` | Anthropic package install and update behavior. |
|
||||
| `package-update-core` | Provider-neutral package and update behavior. |
|
||||
| `plugins-runtime-plugins` | Plugin runtime lanes that exercise plugin behavior. |
|
||||
| `plugins-runtime-services` | Service-backed plugin runtime lanes; includes OpenWebUI when requested. |
|
||||
| `plugins-runtime-install-a` through `plugins-runtime-install-h` | Plugin install/runtime batches split for parallel release validation. |
|
||||
| `bundled-channels-core` | Bundled channel Docker behavior. |
|
||||
| `bundled-channels-update-a`, `bundled-channels-update-discord`, `bundled-channels-update-b` | Bundled channel update behavior. |
|
||||
| `bundled-channels-contracts` | Bundled channel contract checks in the Docker release path. |
|
||||
| Chunk | Coverage |
|
||||
| --------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| `core` | Core Docker release-path smoke lanes. |
|
||||
| `package-update-openai` | OpenAI package install and update behavior. |
|
||||
| `package-update-anthropic` | Anthropic package install and update behavior. |
|
||||
| `package-update-core` | Provider-neutral package and update behavior. |
|
||||
| `plugins-runtime-plugins` | Plugin runtime lanes that exercise plugin behavior. |
|
||||
| `plugins-runtime-services` | Service-backed plugin runtime lanes; includes OpenWebUI when requested. |
|
||||
| `plugins-runtime-install-a` through `plugins-runtime-install-h` | Plugin install/runtime batches split for parallel release validation. |
|
||||
|
||||
Use targeted `docker_lanes=<lane[,lane]>` on the reusable live/E2E workflow when
|
||||
only one Docker lane failed. The release artifacts include per-lane rerun
|
||||
|
||||
@@ -284,7 +284,7 @@ For custom OpenAI-compatible endpoints or overriding provider defaults:
|
||||
| `local.modelCacheDir` | `string` | node-llama-cpp default | Cache dir for downloaded models |
|
||||
| `local.contextSize` | `number \| "auto"` | `4096` | Context window size for the embedding context. 4096 covers typical chunks (128–512 tokens) while bounding non-weight VRAM. Lower to 1024–2048 on constrained hosts. `"auto"` uses the model's trained maximum — not recommended for 8B+ models (Qwen3-Embedding-8B: 40 960 tokens → ~32 GB VRAM vs ~8.8 GB at 4096). |
|
||||
|
||||
Default model: `embeddinggemma-300m-qat-Q8_0.gguf` (~0.6 GB, auto-downloaded). Packaged installs repair the native `node-llama-cpp` runtime through managed plugin runtime deps when `provider: "local"` is configured. Source checkouts still require native build approval: `pnpm approve-builds` then `pnpm rebuild node-llama-cpp`.
|
||||
Default model: `embeddinggemma-300m-qat-Q8_0.gguf` (~0.6 GB, auto-downloaded). Source checkouts still require native build approval: `pnpm approve-builds` then `pnpm rebuild node-llama-cpp`.
|
||||
|
||||
Use the standalone CLI to verify the same provider path the Gateway uses:
|
||||
|
||||
|
||||
@@ -42,8 +42,8 @@ title: "Tests"
|
||||
- CLI backend live Docker probes can be run as focused lanes, for example `pnpm test:docker:live-cli-backend:codex`, `pnpm test:docker:live-cli-backend:codex:resume`, or `pnpm test:docker:live-cli-backend:codex:mcp`. Claude and Gemini have matching `:resume` and `:mcp` aliases.
|
||||
- `pnpm test:docker:openwebui`: Starts Dockerized OpenClaw + Open WebUI, signs in through Open WebUI, checks `/api/models`, then runs a real proxied chat through `/api/chat/completions`. Requires a usable live model key (for example OpenAI in `~/.profile`), pulls an external Open WebUI image, and is not expected to be CI-stable like the normal unit/e2e suites.
|
||||
- `pnpm test:docker:mcp-channels`: Starts a seeded Gateway container and a second client container that spawns `openclaw mcp serve`, then verifies routed conversation discovery, transcript reads, attachment metadata, live event queue behavior, outbound send routing, and Claude-style channel + permission notifications over the real stdio bridge. The Claude notification assertion reads the raw stdio MCP frames directly so the smoke reflects what the bridge actually emits.
|
||||
- `pnpm test:docker:upgrade-survivor`: Installs the packed OpenClaw tarball over a dirty old-user fixture, runs package update plus non-interactive doctor without live provider or channel keys, then starts a loopback Gateway and checks that agents, channel config, plugin allowlists, workspace/session files, stale plugin runtime-deps state, startup, and RPC status survive.
|
||||
- `pnpm test:docker:published-upgrade-survivor`: Installs `openclaw@latest` by default, seeds realistic existing-user files without live provider or channel keys, configures that baseline with a baked `openclaw config set` command recipe, updates that published install to the packed OpenClaw tarball, runs non-interactive doctor, writes `.artifacts/upgrade-survivor/summary.json`, then starts a loopback Gateway and checks that configured intents, workspace/session files, stale plugin config/runtime-deps state, startup, `/healthz`, `/readyz`, and RPC status survive or repair cleanly. Override one baseline with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC`, expand an exact matrix with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS`, or add scenario fixtures with `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS=reported-issues`; Package Acceptance exposes those as `published_upgrade_survivor_baseline`, `published_upgrade_survivor_baselines`, and `published_upgrade_survivor_scenarios`.
|
||||
- `pnpm test:docker:upgrade-survivor`: Installs the packed OpenClaw tarball over a dirty old-user fixture, runs package update plus non-interactive doctor without live provider or channel keys, then starts a loopback Gateway and checks that agents, channel config, plugin allowlists, workspace/session files, stale legacy plugin dependency state, startup, and RPC status survive.
|
||||
- `pnpm test:docker:published-upgrade-survivor`: Installs `openclaw@latest` by default, seeds realistic existing-user files without live provider or channel keys, configures that baseline with a baked `openclaw config set` command recipe, updates that published install to the packed OpenClaw tarball, runs non-interactive doctor, writes `.artifacts/upgrade-survivor/summary.json`, then starts a loopback Gateway and checks that configured intents, workspace/session files, stale plugin config and legacy dependency state, startup, `/healthz`, `/readyz`, and RPC status survive or repair cleanly. Override one baseline with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC`, expand an exact matrix with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS`, or add scenario fixtures with `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS=reported-issues`; Package Acceptance exposes those as `published_upgrade_survivor_baseline`, `published_upgrade_survivor_baselines`, and `published_upgrade_survivor_scenarios`.
|
||||
|
||||
## Local PR gate
|
||||
|
||||
|
||||
@@ -809,8 +809,8 @@ permission modes, see
|
||||
| `ACP is disabled by policy (acp.enabled=false)` | ACP globally disabled. | Set `acp.enabled=true`. |
|
||||
| `ACP dispatch is disabled by policy (acp.dispatch.enabled=false)` | Automatic dispatch from normal thread messages disabled. | Set `acp.dispatch.enabled=true` to resume automatic thread routing; explicit `sessions_spawn({ runtime: "acp" })` calls still work. |
|
||||
| `ACP agent "<id>" is not allowed by policy` | Agent not in allowlist. | Use allowed `agentId` or update `acp.allowedAgents`. |
|
||||
| `/acp doctor` reports backend not ready right after startup | Plugin dependency probe or self-repair is still running. | Wait briefly and rerun `/acp doctor`; if it stays unhealthy, inspect the backend install error and plugin allow/deny policy. |
|
||||
| Harness command not found | Adapter CLI is not installed, staged plugin deps are missing, or first-run `npx` fetch failed for a non-Codex adapter. | Run `/acp doctor`, repair plugin dependencies, install/prewarm the adapter on the Gateway host, or configure the acpx agent command explicitly. |
|
||||
| `/acp doctor` reports backend not ready right after startup | Backend plugin is missing, disabled, blocked by allow/deny policy, or its configured executable is unavailable. | Install/enable the backend plugin, rerun `/acp doctor`, and inspect the backend install or policy error if it stays unhealthy. |
|
||||
| Harness command not found | Adapter CLI is not installed, the external plugin is missing, or first-run `npx` fetch failed for a non-Codex adapter. | Run `/acp doctor`, install/prewarm the adapter on the Gateway host, or configure the acpx agent command explicitly. |
|
||||
| Model-not-found from the harness | Model id is valid for another provider/harness but not this ACP target. | Use a model listed by that harness, configure the model in the harness, or omit the override. |
|
||||
| Vendor auth error from the harness | OpenClaw is healthy, but the target CLI/provider is not logged in. | Log in or provide the required provider key on the Gateway host environment. |
|
||||
| `Unable to resolve session target: ...` | Bad key/id/label token. | Run `/acp sessions`, copy exact key/label, retry. |
|
||||
|
||||
@@ -96,10 +96,10 @@ What still needs Playwright:
|
||||
Element screenshots also reject `--full-page`; the route returns `fullPage is
|
||||
not supported for element screenshots`.
|
||||
|
||||
If you see `Playwright is not available in this gateway build`, repair the
|
||||
bundled browser plugin runtime dependencies so `playwright-core` is installed,
|
||||
then restart the gateway. For packaged installs, run `openclaw doctor --fix`.
|
||||
For Docker, also install the Chromium browser binaries as shown below.
|
||||
If you see `Playwright is not available in this gateway build`, the packaged
|
||||
Gateway is missing the core browser runtime dependency. Reinstall or update
|
||||
OpenClaw, then restart the gateway. For Docker, also install the Chromium
|
||||
browser binaries as shown below.
|
||||
|
||||
#### Docker Playwright install
|
||||
|
||||
|
||||
@@ -26,6 +26,11 @@ When enabled, the plugin prepends concise usage guidance into system-prompt spac
|
||||
## Quick start
|
||||
|
||||
<Steps>
|
||||
<Step title="Install the plugin">
|
||||
```bash
|
||||
openclaw plugins install diffs
|
||||
```
|
||||
</Step>
|
||||
<Step title="Enable the plugin">
|
||||
```json5
|
||||
{
|
||||
|
||||
@@ -96,22 +96,15 @@ Gateway startup skips plugin discovery/load work and `openclaw doctor` preserves
|
||||
the disabled plugin config instead of auto-removing it. Re-enable plugins before
|
||||
running doctor cleanup if you want stale plugin ids removed.
|
||||
|
||||
Packaged OpenClaw installs do not eagerly install every bundled plugin's
|
||||
runtime dependency tree. When a bundled OpenClaw-owned plugin is active from
|
||||
plugin config, legacy channel config, or a default-enabled manifest, startup
|
||||
repairs only that plugin's declared runtime dependencies before importing it.
|
||||
Persisted channel auth state alone does not activate a bundled channel for
|
||||
Gateway startup runtime-dependency repair.
|
||||
Explicit disablement still wins: `plugins.entries.<id>.enabled: false`,
|
||||
`plugins.deny`, `plugins.enabled: false`, and `channels.<id>.enabled: false`
|
||||
prevent automatic bundled runtime-dependency repair for that plugin/channel.
|
||||
A non-empty `plugins.allow` also bounds default-enabled bundled runtime-dependency
|
||||
repair; explicit bundled channel enablement (`channels.<id>.enabled: true`) can
|
||||
still repair that channel's plugin dependencies.
|
||||
External plugins and custom load paths must still be installed through
|
||||
`openclaw plugins install`.
|
||||
See [Plugin dependency resolution](/plugins/dependency-resolution) for the full
|
||||
planning and staging lifecycle.
|
||||
Plugin dependency installation happens only during explicit install/update or
|
||||
doctor repair flows. Gateway startup, config reload, and runtime inspection do
|
||||
not run package managers or repair dependency trees. Local plugins must already
|
||||
have their dependencies installed, while npm, git, and ClawHub plugins are
|
||||
installed under OpenClaw's managed plugin roots with package-local
|
||||
dependencies. External plugins and custom load paths must still be installed
|
||||
through `openclaw plugins install`.
|
||||
See [Plugin dependency resolution](/plugins/dependency-resolution) for the
|
||||
install-time lifecycle.
|
||||
|
||||
## Plugin types
|
||||
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
],
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ describe("prepareAcpxCodexAuthConfig", () => {
|
||||
});
|
||||
|
||||
const wrapper = await fs.readFile(generated.wrapperPath, "utf8");
|
||||
expect(wrapper).toContain('"@agentclientprotocol/claude-agent-acp@0.31.1"');
|
||||
expect(wrapper).toContain('"@agentclientprotocol/claude-agent-acp@0.31.4"');
|
||||
expect(wrapper).toContain('"--", "claude-agent-acp"');
|
||||
expect(wrapper).not.toContain("@agentclientprotocol/claude-agent-acp@^0.31.0");
|
||||
expect(wrapper).not.toContain("@agentclientprotocol/claude-agent-acp@0.31.0");
|
||||
@@ -379,7 +379,7 @@ describe("prepareAcpxCodexAuthConfig", () => {
|
||||
rawConfig: {
|
||||
agents: {
|
||||
claude: {
|
||||
command: "npx -y @agentclientprotocol/claude-agent-acp@0.31.1 --permission-mode bypass",
|
||||
command: "npx -y @agentclientprotocol/claude-agent-acp@0.31.4 --permission-mode bypass",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -425,7 +425,7 @@ describe("prepareAcpxCodexAuthConfig", () => {
|
||||
const root = await makeTempDir();
|
||||
const stateDir = path.join(root, "state");
|
||||
const command =
|
||||
"node ./custom-claude-wrapper.mjs @agentclientprotocol/claude-agent-acp@0.31.1 --flag";
|
||||
"node ./custom-claude-wrapper.mjs @agentclientprotocol/claude-agent-acp@0.31.4 --flag";
|
||||
const pluginConfig = resolveAcpxPluginConfig({
|
||||
rawConfig: {
|
||||
agents: {
|
||||
|
||||
@@ -7,7 +7,7 @@ const CODEX_ACP_PACKAGE = "@zed-industries/codex-acp";
|
||||
const CODEX_ACP_PACKAGE_RANGE = "^0.12.0";
|
||||
const CODEX_ACP_BIN = "codex-acp";
|
||||
const CLAUDE_ACP_PACKAGE = "@agentclientprotocol/claude-agent-acp";
|
||||
const CLAUDE_ACP_PACKAGE_VERSION = "0.31.1";
|
||||
const CLAUDE_ACP_PACKAGE_VERSION = "0.31.4";
|
||||
const CLAUDE_ACP_BIN = "claude-agent-acp";
|
||||
const RUN_CONFIGURED_COMMAND_SENTINEL = "--openclaw-run-configured";
|
||||
const requireFromHere = createRequire(import.meta.url);
|
||||
|
||||
@@ -4,15 +4,10 @@ import { describe, expect, it } from "vitest";
|
||||
type AcpxPackageManifest = {
|
||||
dependencies?: Record<string, string>;
|
||||
devDependencies?: Record<string, string>;
|
||||
openclaw?: {
|
||||
bundle?: {
|
||||
stageRuntimeDependencies?: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
describe("acpx package manifest", () => {
|
||||
it("opts into staging bundled runtime dependencies", () => {
|
||||
it("keeps runtime dependencies in the package manifest", () => {
|
||||
const packageJson = JSON.parse(
|
||||
fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
||||
) as AcpxPackageManifest;
|
||||
@@ -21,6 +16,5 @@ describe("acpx package manifest", () => {
|
||||
expect(packageJson.dependencies?.["@zed-industries/codex-acp"]).toBe("0.12.0");
|
||||
expect(packageJson.dependencies?.["@agentclientprotocol/claude-agent-acp"]).toBe("0.31.4");
|
||||
expect(packageJson.devDependencies?.["@agentclientprotocol/claude-agent-acp"]).toBeUndefined();
|
||||
expect(packageJson.openclaw?.bundle?.stageRuntimeDependencies).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,9 +13,6 @@
|
||||
"@openclaw/plugin-sdk": "workspace:*"
|
||||
},
|
||||
"openclaw": {
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
},
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
|
||||
@@ -13,9 +13,6 @@
|
||||
"@openclaw/plugin-sdk": "workspace:*"
|
||||
},
|
||||
"openclaw": {
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
},
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
|
||||
@@ -13,9 +13,6 @@
|
||||
"@openclaw/plugin-sdk": "workspace:*"
|
||||
},
|
||||
"openclaw": {
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
},
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
"@openclaw/plugin-sdk": "workspace:*"
|
||||
},
|
||||
"openclaw": {
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
},
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
|
||||
@@ -12,9 +12,6 @@
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
],
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ export async function requirePwAi(
|
||||
501,
|
||||
[
|
||||
`Playwright is not available in this gateway build; '${feature}' is unsupported.`,
|
||||
"Repair the bundled browser plugin runtime dependencies so playwright-core is installed, then restart the gateway. In Docker, also install Chromium with the bundled playwright-core CLI.",
|
||||
"Reinstall or update OpenClaw so the core browser runtime dependency is present, then restart the gateway. In Docker, also install Chromium with the bundled playwright-core CLI.",
|
||||
"Docs: /tools/browser#playwright-requirement",
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
@@ -16,9 +16,6 @@
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
],
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ async function findManagedCodexAppServerCommandPath(params: {
|
||||
throw new Error(
|
||||
[
|
||||
`Managed Codex app-server binary was not found for ${MANAGED_CODEX_APP_SERVER_PACKAGE}.`,
|
||||
"Run OpenClaw with bundled plugin runtime dependencies enabled, or run pnpm install in a source checkout.",
|
||||
"Reinstall or update OpenClaw, or run pnpm install in a source checkout.",
|
||||
"Set plugins.entries.codex.config.appServer.command or OPENCLAW_CODEX_APP_SERVER_BIN to use a custom Codex binary.",
|
||||
].join(" "),
|
||||
);
|
||||
|
||||
@@ -4,15 +4,10 @@ import { MANAGED_CODEX_APP_SERVER_PACKAGE_VERSION } from "./app-server/version.j
|
||||
|
||||
type CodexPackageManifest = {
|
||||
dependencies?: Record<string, string>;
|
||||
openclaw?: {
|
||||
bundle?: {
|
||||
stageRuntimeDependencies?: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
describe("codex package manifest", () => {
|
||||
it("opts into staging bundled runtime dependencies", () => {
|
||||
it("keeps runtime dependencies in the package manifest", () => {
|
||||
const packageJson = JSON.parse(
|
||||
fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
||||
) as CodexPackageManifest;
|
||||
@@ -21,6 +16,5 @@ describe("codex package manifest", () => {
|
||||
expect(packageJson.dependencies?.["@openai/codex"]).toBe(
|
||||
MANAGED_CODEX_APP_SERVER_PACKAGE_VERSION,
|
||||
);
|
||||
expect(packageJson.openclaw?.bundle?.stageRuntimeDependencies).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "diffs",
|
||||
"activation": {
|
||||
"onStartup": true
|
||||
"onStartup": false
|
||||
},
|
||||
"name": "Diffs",
|
||||
"description": "Read-only diff viewer and file renderer for agents.",
|
||||
|
||||
@@ -17,11 +17,27 @@
|
||||
"@openclaw/plugin-sdk": "workspace:*"
|
||||
},
|
||||
"openclaw": {
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
},
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
],
|
||||
"install": {
|
||||
"npmSpec": "@openclaw/diffs",
|
||||
"localPath": "extensions/diffs",
|
||||
"defaultChoice": "npm",
|
||||
"minHostVersion": ">=2026.4.30"
|
||||
},
|
||||
"compat": {
|
||||
"pluginApi": ">=2026.4.30"
|
||||
},
|
||||
"build": {
|
||||
"openclawVersion": "2026.4.30"
|
||||
},
|
||||
"bundle": {
|
||||
"includeInCore": false
|
||||
},
|
||||
"release": {
|
||||
"publishToClawHub": true,
|
||||
"publishToNpm": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,20 +3,14 @@ import { describe, expect, it } from "vitest";
|
||||
|
||||
type DiffsPackageManifest = {
|
||||
dependencies?: Record<string, string>;
|
||||
openclaw?: {
|
||||
bundle?: {
|
||||
stageRuntimeDependencies?: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
describe("diffs package manifest", () => {
|
||||
it("opts into staging bundled runtime dependencies", () => {
|
||||
it("keeps runtime dependencies in the package manifest", () => {
|
||||
const packageJson = JSON.parse(
|
||||
fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
||||
) as DiffsPackageManifest;
|
||||
|
||||
expect(packageJson.dependencies?.["@pierre/diffs"]).toBeDefined();
|
||||
expect(packageJson.openclaw?.bundle?.stageRuntimeDependencies).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -59,9 +59,6 @@
|
||||
"build": {
|
||||
"openclawVersion": "2026.4.25"
|
||||
},
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
},
|
||||
"release": {
|
||||
"publishToClawHub": true,
|
||||
"publishToNpm": true
|
||||
|
||||
@@ -48,9 +48,6 @@
|
||||
"build": {
|
||||
"openclawVersion": "2026.4.25"
|
||||
},
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
},
|
||||
"release": {
|
||||
"publishToClawHub": true,
|
||||
"publishToNpm": true
|
||||
|
||||
@@ -13,9 +13,6 @@
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
],
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,6 @@
|
||||
"@openclaw/plugin-sdk": "workspace:*"
|
||||
},
|
||||
"openclaw": {
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
},
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
|
||||
@@ -22,9 +22,6 @@
|
||||
}
|
||||
},
|
||||
"openclaw": {
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
},
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
],
|
||||
|
||||
@@ -80,9 +80,6 @@
|
||||
"defaultChoice": "npm",
|
||||
"minHostVersion": ">=2026.4.10",
|
||||
"allowInvalidConfigRecovery": true
|
||||
},
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,20 +3,14 @@ import { describe, expect, it } from "vitest";
|
||||
|
||||
type MatrixPackageManifest = {
|
||||
dependencies?: Record<string, string>;
|
||||
openclaw?: {
|
||||
bundle?: {
|
||||
stageRuntimeDependencies?: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
describe("matrix package manifest", () => {
|
||||
it("opts into staging bundled runtime dependencies", () => {
|
||||
it("keeps runtime dependencies in the package manifest", () => {
|
||||
const packageJson = JSON.parse(
|
||||
fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
||||
) as MatrixPackageManifest;
|
||||
|
||||
expect(packageJson.dependencies?.["fake-indexeddb"]).toBeDefined();
|
||||
expect(packageJson.openclaw?.bundle?.stageRuntimeDependencies).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,9 +10,5 @@
|
||||
"devDependencies": {
|
||||
"@openclaw/plugin-sdk": "workspace:*"
|
||||
},
|
||||
"openclaw": {
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
}
|
||||
}
|
||||
"openclaw": {}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,6 @@
|
||||
"contracts": {
|
||||
"memoryEmbeddingProviders": ["local"]
|
||||
},
|
||||
"runtimeDependencies": {
|
||||
"localMemoryEmbedding": ["node-llama-cpp@3.18.1"]
|
||||
},
|
||||
"commandAliases": [
|
||||
{
|
||||
"name": "dreaming",
|
||||
|
||||
@@ -59,9 +59,6 @@
|
||||
"build": {
|
||||
"openclawVersion": "2026.4.25"
|
||||
},
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
},
|
||||
"release": {
|
||||
"publishToClawHub": true,
|
||||
"publishToNpm": true
|
||||
|
||||
@@ -55,9 +55,6 @@
|
||||
"build": {
|
||||
"openclawVersion": "2026.4.25"
|
||||
},
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
},
|
||||
"release": {
|
||||
"publishToClawHub": true,
|
||||
"publishToNpm": true
|
||||
|
||||
@@ -65,7 +65,9 @@ function raiseMinimalReasoningForOpenAINativeWebSearch(payload: Record<string, u
|
||||
reasoning.effort = "low";
|
||||
}
|
||||
|
||||
function patchOpenAINativeWebSearchPayload(payload: unknown): OpenAINativeWebSearchPatchResult {
|
||||
export function patchOpenAINativeWebSearchPayload(
|
||||
payload: unknown,
|
||||
): OpenAINativeWebSearchPatchResult {
|
||||
if (!isRecord(payload)) {
|
||||
return "payload_not_object";
|
||||
}
|
||||
|
||||
@@ -21,11 +21,6 @@ const packageJson = JSON.parse(
|
||||
readFileSync(new URL("./package.json", import.meta.url), "utf8"),
|
||||
) as {
|
||||
dependencies?: Record<string, string>;
|
||||
openclaw?: {
|
||||
bundle?: {
|
||||
stageRuntimeDependencies?: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
function manifestComparableWizardFields(choice: {
|
||||
@@ -64,10 +59,9 @@ function providerWizardByKey() {
|
||||
}
|
||||
|
||||
describe("OpenAI plugin manifest", () => {
|
||||
it("opts into staging bundled runtime dependencies", () => {
|
||||
it("keeps runtime dependencies in the package manifest", () => {
|
||||
expect(packageJson.dependencies?.["@mariozechner/pi-ai"]).toBe("0.71.1");
|
||||
expect(packageJson.dependencies?.ws).toBe("^8.20.0");
|
||||
expect(packageJson.openclaw?.bundle?.stageRuntimeDependencies).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps removed Codex CLI import auth choice as a deprecated browser-login alias", () => {
|
||||
|
||||
@@ -12,9 +12,6 @@
|
||||
"@openclaw/plugin-sdk": "workspace:*"
|
||||
},
|
||||
"openclaw": {
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
},
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
|
||||
@@ -934,8 +934,8 @@ describe("qa bundled plugin dir", () => {
|
||||
).resolves.toBeTruthy();
|
||||
});
|
||||
|
||||
it("skips transient runtime dependency artifacts while staging built bundled plugins", async () => {
|
||||
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-runtime-deps-"));
|
||||
it("skips legacy dependency debris while staging built bundled plugins", async () => {
|
||||
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-legacy-deps-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(repoRoot, { recursive: true, force: true });
|
||||
});
|
||||
@@ -961,7 +961,7 @@ describe("qa bundled plugin dir", () => {
|
||||
"export {};\n",
|
||||
"utf8",
|
||||
);
|
||||
const tempRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-runtime-deps-target-"));
|
||||
const tempRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-legacy-deps-target-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(tempRoot, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"openclawVersion": "2026.4.27"
|
||||
},
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
"includeInCore": false
|
||||
},
|
||||
"release": {
|
||||
"publishToClawHub": true,
|
||||
|
||||
@@ -36,9 +36,6 @@
|
||||
"specifier": "./configured-state",
|
||||
"exportName": "hasSlackConfiguredState"
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,9 +46,6 @@
|
||||
"specifier": "./configured-state",
|
||||
"exportName": "hasTelegramConfiguredState"
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,6 @@ import { describe, expect, it } from "vitest";
|
||||
|
||||
type TokenjuicePackageManifest = {
|
||||
dependencies?: Record<string, string>;
|
||||
openclaw?: {
|
||||
bundle?: {
|
||||
stageRuntimeDependencies?: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
type TokenjuicePluginManifest = {
|
||||
@@ -17,13 +12,12 @@ type TokenjuicePluginManifest = {
|
||||
};
|
||||
|
||||
describe("tokenjuice package manifest", () => {
|
||||
it("opts into staging bundled runtime dependencies", () => {
|
||||
it("keeps runtime dependencies in the package manifest", () => {
|
||||
const packageJson = JSON.parse(
|
||||
fs.readFileSync(new URL("./package.json", import.meta.url), "utf8"),
|
||||
) as TokenjuicePackageManifest;
|
||||
|
||||
expect(packageJson.dependencies?.tokenjuice).toBe("0.7.0");
|
||||
expect(packageJson.openclaw?.bundle?.stageRuntimeDependencies).toBe(true);
|
||||
});
|
||||
|
||||
it("declares runtime-neutral tool result middleware ownership in the manifest contract", () => {
|
||||
|
||||
@@ -12,9 +12,6 @@
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
],
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
"@openclaw/plugin-sdk": "workspace:*"
|
||||
},
|
||||
"openclaw": {
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
},
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
|
||||
@@ -59,9 +59,6 @@
|
||||
"compat": {
|
||||
"pluginApi": ">=2026.4.25"
|
||||
},
|
||||
"bundle": {
|
||||
"stageRuntimeDependencies": true
|
||||
},
|
||||
"build": {
|
||||
"openclawVersion": "2026.4.25"
|
||||
},
|
||||
|
||||
@@ -111,7 +111,7 @@ const config = {
|
||||
workspaces: {
|
||||
".": {
|
||||
entry: rootEntries,
|
||||
ignoreDependencies: ["@openclaw/*", "sqlite-vec"],
|
||||
ignoreDependencies: ["@openclaw/*", "playwright-core", "sqlite-vec"],
|
||||
project: [
|
||||
"src/**/*.ts!",
|
||||
"scripts/**/*.{js,mjs,cjs,ts,mts,cts}!",
|
||||
|
||||
48
package.json
48
package.json
@@ -31,9 +31,6 @@
|
||||
"!dist/.runtime-postbuildstamp",
|
||||
"!dist/**/*.map",
|
||||
"!dist/plugin-sdk/.tsbuildinfo",
|
||||
"!dist/extensions/*/.openclaw-install-stage*/**",
|
||||
"!dist/extensions/*/.openclaw-runtime-deps-*/**",
|
||||
"!dist/extensions/*/.openclaw-runtime-deps-stamp.json",
|
||||
"!dist/extensions/node_modules/**",
|
||||
"!dist/extensions/*/node_modules/**",
|
||||
"!dist/extensions/qa-channel/**",
|
||||
@@ -57,7 +54,6 @@
|
||||
"skills/",
|
||||
"scripts/npm-runner.mjs",
|
||||
"scripts/preinstall-package-manager-warning.mjs",
|
||||
"scripts/lib/bundled-runtime-deps-install.mjs",
|
||||
"scripts/lib/package-dist-imports.mjs",
|
||||
"scripts/postinstall-bundled-plugins.mjs",
|
||||
"scripts/windows-cmd-helpers.mjs"
|
||||
@@ -1446,12 +1442,11 @@
|
||||
"rtt": "node --import tsx scripts/rtt.ts",
|
||||
"runtime-sidecars:check": "node --import tsx scripts/generate-runtime-sidecar-paths-baseline.ts --check",
|
||||
"runtime-sidecars:gen": "node --import tsx scripts/generate-runtime-sidecar-paths-baseline.ts --write",
|
||||
"stage:bundled-plugin-runtime-deps": "node scripts/stage-bundled-plugin-runtime-deps.mjs",
|
||||
"start": "node scripts/run-node.mjs",
|
||||
"test": "node scripts/test-projects.mjs",
|
||||
"test:all": "pnpm lint && pnpm build && pnpm test && pnpm test:e2e && pnpm test:live && pnpm test:docker:all",
|
||||
"test:auth:compat": "node scripts/run-vitest.mjs run --config test/vitest/vitest.gateway.config.ts src/gateway/server.auth.compat-baseline.test.ts src/gateway/client.test.ts src/gateway/reconnect-gating.test.ts src/gateway/protocol/connect-error-details.test.ts",
|
||||
"test:build:bundled-runtime-deps": "node scripts/test-built-bundled-runtime-deps.mjs",
|
||||
"test:build:bundled-runtime-deps": "node -e \"console.log('bundled plugin runtime dependency staging was removed; no-op')\"",
|
||||
"test:build:singleton": "node scripts/test-built-plugin-singleton.mjs",
|
||||
"test:build:status-message-runtime": "node scripts/test-built-status-message-runtime.mjs",
|
||||
"test:bundled": "node scripts/run-vitest.mjs run --config test/vitest/vitest.bundled.config.ts",
|
||||
@@ -1466,8 +1461,7 @@
|
||||
"test:docker:agents-delete-shared-workspace": "bash scripts/e2e/agents-delete-shared-workspace-docker.sh",
|
||||
"test:docker:all": "node scripts/test-docker-all.mjs",
|
||||
"test:docker:browser-cdp-snapshot": "bash scripts/e2e/browser-cdp-snapshot-docker.sh",
|
||||
"test:docker:bundled-channel-deps": "bash scripts/e2e/bundled-channel-runtime-deps-docker.sh",
|
||||
"test:docker:bundled-channel-deps:fast": "OPENCLAW_BUNDLED_CHANNEL_SCENARIOS=0 OPENCLAW_BUNDLED_CHANNEL_UPDATE_SCENARIO=0 OPENCLAW_BUNDLED_CHANNEL_ROOT_OWNED_SCENARIO=0 OPENCLAW_BUNDLED_CHANNEL_SETUP_ENTRY_SCENARIO=1 OPENCLAW_BUNDLED_CHANNEL_DISABLED_CONFIG_SCENARIO=1 OPENCLAW_BUNDLED_CHANNEL_LOAD_FAILURE_SCENARIO=1 bash scripts/e2e/bundled-channel-runtime-deps-docker.sh",
|
||||
"test:docker:bundled-channel-deps:fast": "node -e \"console.log('bundled channel dependency staging was removed; no-op')\"",
|
||||
"test:docker:bundled-plugin-install-uninstall": "bash scripts/e2e/bundled-plugin-install-uninstall-docker.sh",
|
||||
"test:docker:cleanup": "bash scripts/test-cleanup-docker.sh",
|
||||
"test:docker:commitments-safety": "bash scripts/e2e/commitments-safety-docker.sh",
|
||||
@@ -1641,9 +1635,9 @@
|
||||
"jszip": "^3.10.1",
|
||||
"markdown-it": "14.1.1",
|
||||
"openai": "^6.35.0",
|
||||
"playwright-core": "1.59.1",
|
||||
"proxy-agent": "^8.0.1",
|
||||
"qrcode": "1.5.4",
|
||||
"semver": "7.7.4",
|
||||
"sqlite-vec": "0.1.9",
|
||||
"tar": "7.5.13",
|
||||
"tslog": "^4.10.2",
|
||||
@@ -1746,41 +1740,5 @@
|
||||
"@whiskeysockets/baileys@7.0.0-rc.9": "patches/@whiskeysockets__baileys@7.0.0-rc.9.patch",
|
||||
"@agentclientprotocol/claude-agent-acp@0.31.4": "patches/@agentclientprotocol__claude-agent-acp@0.31.4.patch"
|
||||
}
|
||||
},
|
||||
"openclaw": {
|
||||
"bundle": {
|
||||
"mirroredRootRuntimeDependencies": [
|
||||
"@agentclientprotocol/sdk",
|
||||
"@clack/prompts",
|
||||
"@lydell/node-pty",
|
||||
"@mariozechner/pi-ai",
|
||||
"@mariozechner/pi-coding-agent",
|
||||
"@modelcontextprotocol/sdk",
|
||||
"ajv",
|
||||
"chalk",
|
||||
"chokidar",
|
||||
"commander",
|
||||
"croner",
|
||||
"dotenv",
|
||||
"global-agent",
|
||||
"https-proxy-agent",
|
||||
"jiti",
|
||||
"json5",
|
||||
"jszip",
|
||||
"markdown-it",
|
||||
"openai",
|
||||
"qrcode",
|
||||
"semver",
|
||||
"sqlite-vec",
|
||||
"tar",
|
||||
"tslog",
|
||||
"typebox",
|
||||
"undici",
|
||||
"web-push",
|
||||
"ws",
|
||||
"yaml",
|
||||
"zod"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -111,15 +111,15 @@ importers:
|
||||
openai:
|
||||
specifier: ^6.35.0
|
||||
version: 6.35.0(ws@8.20.0)(zod@4.4.1)
|
||||
playwright-core:
|
||||
specifier: 1.59.1
|
||||
version: 1.59.1
|
||||
proxy-agent:
|
||||
specifier: ^8.0.1
|
||||
version: 8.0.1
|
||||
qrcode:
|
||||
specifier: 1.5.4
|
||||
version: 1.5.4
|
||||
semver:
|
||||
specifier: 7.7.4
|
||||
version: 7.7.4
|
||||
sqlite-vec:
|
||||
specifier: 0.1.9
|
||||
version: 0.1.9
|
||||
|
||||
@@ -46,7 +46,7 @@ const CONTROL_UI_I18N_SCOPE_RE =
|
||||
const NATIVE_ONLY_RE =
|
||||
/^(apps\/android\/|apps\/ios\/|apps\/macos\/|apps\/macos-mlx-tts\/|apps\/shared\/|Swabble\/|appcast\.xml$)/;
|
||||
const FAST_INSTALL_SMOKE_SCOPE_RE =
|
||||
/^(Dockerfile$|\.npmrc$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|scripts\/ci-changed-scope\.mjs$|scripts\/postinstall-bundled-plugins\.mjs$|scripts\/e2e\/(?:Dockerfile(?:\.qr-import)?|agents-delete-shared-workspace-docker\.sh|gateway-network-docker\.sh|bundled-channel-runtime-deps-docker\.sh)$|src\/plugins\/bundled-runtime-deps\.ts$|extensions\/[^/]+\/(?:package\.json|openclaw\.plugin\.json)$|\.github\/workflows\/install-smoke\.yml$|\.github\/actions\/setup-node-env\/action\.yml$)/;
|
||||
/^(Dockerfile$|\.npmrc$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|scripts\/ci-changed-scope\.mjs$|scripts\/postinstall-bundled-plugins\.mjs$|scripts\/e2e\/(?:Dockerfile(?:\.qr-import)?|agents-delete-shared-workspace-docker\.sh|gateway-network-docker\.sh)$|extensions\/[^/]+\/(?:package\.json|openclaw\.plugin\.json)$|\.github\/workflows\/install-smoke\.yml$|\.github\/actions\/setup-node-env\/action\.yml$)/;
|
||||
const FULL_INSTALL_SMOKE_SCOPE_RE =
|
||||
/^(Dockerfile$|\.npmrc$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|scripts\/ci-changed-scope\.mjs$|scripts\/install\.sh$|scripts\/test-install-sh-docker\.sh$|scripts\/docker\/|scripts\/e2e\/(?:Dockerfile(?:\.qr-import)?|qr-import-docker\.sh|bun-global-install-smoke\.sh)$|\.github\/workflows\/install-smoke\.yml$|\.github\/actions\/setup-node-env\/action\.yml$)/;
|
||||
const FAST_INSTALL_SMOKE_RUNTIME_SCOPE_RE = /^src\/(?:channels|gateway|plugin-sdk|plugins)\//;
|
||||
|
||||
@@ -192,14 +192,13 @@ 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
|
||||
- Downloadable plugin packages and install records live under the mounted OpenClaw home
|
||||
- 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
|
||||
|
||||
|
||||
@@ -363,8 +363,7 @@ export function copyBundledPluginMetadata(params = {}) {
|
||||
manifest,
|
||||
generatedChannelConfigsByPlugin.get(manifest.id),
|
||||
);
|
||||
// Generated skill assets live under a dedicated dist-owned directory. Runtime
|
||||
// dependency staging owns dist plugin node_modules; do not remove it here.
|
||||
// Generated skill assets live under a dedicated dist-owned directory.
|
||||
removePathIfExists(path.join(distPluginDir, GENERATED_BUNDLED_SKILLS_DIR));
|
||||
const copiedSkills = copyDeclaredPluginSkillPaths({
|
||||
manifest: manifestWithGeneratedChannelConfigs,
|
||||
|
||||
@@ -20,7 +20,6 @@ COPY packages ./packages
|
||||
COPY extensions ./extensions
|
||||
COPY patches ./patches
|
||||
COPY scripts/postinstall-bundled-plugins.mjs scripts/preinstall-package-manager-warning.mjs scripts/npm-runner.mjs scripts/windows-cmd-helpers.mjs ./scripts/
|
||||
COPY scripts/lib/bundled-runtime-deps-install.mjs ./scripts/lib/bundled-runtime-deps-install.mjs
|
||||
COPY scripts/lib/package-dist-imports.mjs ./scripts/lib/package-dist-imports.mjs
|
||||
RUN --mount=type=cache,id=openclaw-pnpm-store,target=/root/.local/share/pnpm/store,sharing=locked \
|
||||
corepack enable \
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs bundled plugin runtime-dependency Docker scenarios from a mounted OpenClaw
|
||||
# npm tarball. The default image is a clean runner; each scenario installs the
|
||||
# tarball so package install behavior is what gets tested.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
source "$ROOT_DIR/scripts/e2e/lib/bundled-channel-runtime-deps-runner.sh"
|
||||
source "$ROOT_DIR/scripts/e2e/lib/bundled-channel/channel.sh"
|
||||
source "$ROOT_DIR/scripts/e2e/lib/bundled-channel/root-owned.sh"
|
||||
source "$ROOT_DIR/scripts/e2e/lib/bundled-channel/setup-entry.sh"
|
||||
source "$ROOT_DIR/scripts/e2e/lib/bundled-channel/disabled-config.sh"
|
||||
source "$ROOT_DIR/scripts/e2e/lib/bundled-channel/update.sh"
|
||||
source "$ROOT_DIR/scripts/e2e/lib/bundled-channel/load-failure.sh"
|
||||
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-bundled-channel-deps-e2e" OPENCLAW_BUNDLED_CHANNEL_DEPS_E2E_IMAGE)"
|
||||
UPDATE_BASELINE_VERSION="${OPENCLAW_BUNDLED_CHANNEL_UPDATE_BASELINE_VERSION:-2026.4.20}"
|
||||
DOCKER_TARGET="${OPENCLAW_BUNDLED_CHANNEL_DOCKER_TARGET:-bare}"
|
||||
HOST_BUILD="${OPENCLAW_BUNDLED_CHANNEL_HOST_BUILD:-1}"
|
||||
PACKAGE_TGZ="${OPENCLAW_CURRENT_PACKAGE_TGZ:-}"
|
||||
RUN_CHANNEL_SCENARIOS="${OPENCLAW_BUNDLED_CHANNEL_SCENARIOS:-1}"
|
||||
RUN_UPDATE_SCENARIO="${OPENCLAW_BUNDLED_CHANNEL_UPDATE_SCENARIO:-1}"
|
||||
RUN_ROOT_OWNED_SCENARIO="${OPENCLAW_BUNDLED_CHANNEL_ROOT_OWNED_SCENARIO:-1}"
|
||||
RUN_SETUP_ENTRY_SCENARIO="${OPENCLAW_BUNDLED_CHANNEL_SETUP_ENTRY_SCENARIO:-1}"
|
||||
RUN_LOAD_FAILURE_SCENARIO="${OPENCLAW_BUNDLED_CHANNEL_LOAD_FAILURE_SCENARIO:-1}"
|
||||
RUN_DISABLED_CONFIG_SCENARIO="${OPENCLAW_BUNDLED_CHANNEL_DISABLED_CONFIG_SCENARIO:-1}"
|
||||
CHANNEL_ONLY="${OPENCLAW_BUNDLED_CHANNEL_ONLY:-}"
|
||||
DOCKER_RUN_TIMEOUT="${OPENCLAW_BUNDLED_CHANNEL_DOCKER_RUN_TIMEOUT:-900s}"
|
||||
DOCKER_UPDATE_RUN_TIMEOUT="${OPENCLAW_BUNDLED_CHANNEL_UPDATE_DOCKER_RUN_TIMEOUT:-${OPENCLAW_BUNDLED_CHANNEL_DOCKER_RUN_TIMEOUT:-2400s}}"
|
||||
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" bundled-channel-deps "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "$DOCKER_TARGET"
|
||||
|
||||
prepare_package_tgz() {
|
||||
if [ -n "$PACKAGE_TGZ" ]; then
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz bundled-channel-deps "$PACKAGE_TGZ")"
|
||||
return 0
|
||||
fi
|
||||
if [ "$HOST_BUILD" = "0" ] && [ -z "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}" ]; then
|
||||
echo "OPENCLAW_BUNDLED_CHANNEL_HOST_BUILD=0 requires OPENCLAW_CURRENT_PACKAGE_TGZ" >&2
|
||||
exit 1
|
||||
fi
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz bundled-channel-deps)"
|
||||
}
|
||||
|
||||
prepare_package_tgz
|
||||
docker_e2e_package_mount_args "$PACKAGE_TGZ"
|
||||
docker_e2e_harness_mount_args
|
||||
|
||||
run_bundled_channel_runtime_dep_scenarios
|
||||
@@ -1,83 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Scenario selection for bundled plugin runtime-dependency Docker tests.
|
||||
# The large scenario bodies stay in the owning test script; this helper keeps
|
||||
# env flag parsing and dispatch in one small, reviewable place.
|
||||
|
||||
bundled_channel_state_script_b64() {
|
||||
docker_e2e_test_state_shell_b64 "$1" empty
|
||||
}
|
||||
|
||||
run_bundled_channel_container() {
|
||||
local label="$1"
|
||||
local timeout_value="$2"
|
||||
shift 2
|
||||
run_logged_print "$label" timeout "$timeout_value" docker run --rm \
|
||||
"${DOCKER_E2E_HARNESS_ARGS[@]}" \
|
||||
"$@"
|
||||
}
|
||||
|
||||
run_bundled_channel_container_with_state() {
|
||||
local label="$1"
|
||||
local timeout_value="$2"
|
||||
local state_label="$3"
|
||||
shift 3
|
||||
local state_script_b64
|
||||
state_script_b64="$(bundled_channel_state_script_b64 "$state_label")"
|
||||
run_bundled_channel_container "$label" "$timeout_value" \
|
||||
-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
|
||||
-e "OPENCLAW_TEST_STATE_SCRIPT_B64=$state_script_b64" \
|
||||
"$@"
|
||||
}
|
||||
|
||||
run_bundled_channel_container_with_state_heartbeat() {
|
||||
local label="$1"
|
||||
local heartbeat="$2"
|
||||
local timeout_value="$3"
|
||||
local state_label="$4"
|
||||
shift 4
|
||||
local state_script_b64
|
||||
state_script_b64="$(bundled_channel_state_script_b64 "$state_label")"
|
||||
run_logged_print_heartbeat "$label" "$heartbeat" timeout "$timeout_value" docker run --rm \
|
||||
"${DOCKER_E2E_HARNESS_ARGS[@]}" \
|
||||
-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
|
||||
-e "OPENCLAW_TEST_STATE_SCRIPT_B64=$state_script_b64" \
|
||||
"$@"
|
||||
}
|
||||
|
||||
run_bundled_channel_runtime_dep_scenarios() {
|
||||
if [ "$RUN_CHANNEL_SCENARIOS" != "0" ]; then
|
||||
IFS=',' read -r -a CHANNEL_SCENARIOS <<<"${OPENCLAW_BUNDLED_CHANNELS:-${CHANNEL_ONLY:-telegram,discord,slack,feishu,memory-lancedb}}"
|
||||
for channel_scenario in "${CHANNEL_SCENARIOS[@]}"; do
|
||||
channel_scenario="${channel_scenario//[[:space:]]/}"
|
||||
[ -n "$channel_scenario" ] || continue
|
||||
case "$channel_scenario" in
|
||||
telegram) run_channel_scenario telegram grammy ;;
|
||||
discord) run_channel_scenario discord discord-api-types ;;
|
||||
slack) run_channel_scenario slack @slack/web-api ;;
|
||||
feishu) run_channel_scenario feishu @larksuiteoapi/node-sdk ;;
|
||||
memory-lancedb) run_channel_scenario memory-lancedb @lancedb/lancedb ;;
|
||||
*)
|
||||
echo "Unsupported OPENCLAW_BUNDLED_CHANNELS entry: $channel_scenario" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$RUN_UPDATE_SCENARIO" != "0" ]; then
|
||||
run_update_scenario
|
||||
fi
|
||||
if [ "$RUN_ROOT_OWNED_SCENARIO" != "0" ]; then
|
||||
run_root_owned_global_scenario
|
||||
fi
|
||||
if [ "$RUN_SETUP_ENTRY_SCENARIO" != "0" ]; then
|
||||
run_setup_entry_scenario
|
||||
fi
|
||||
if [ "$RUN_DISABLED_CONFIG_SCENARIO" != "0" ]; then
|
||||
run_disabled_config_scenario
|
||||
fi
|
||||
if [ "$RUN_LOAD_FAILURE_SCENARIO" != "0" ]; then
|
||||
run_load_failure_scenario
|
||||
fi
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import fs from "node:fs";
|
||||
|
||||
const raw = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
const payload = raw.result ?? raw.data ?? raw;
|
||||
const channel = process.argv[3];
|
||||
const dump = () => JSON.stringify(raw, null, 2).slice(0, 4000);
|
||||
|
||||
const hasChannelMeta = Array.isArray(payload.channelMeta)
|
||||
? payload.channelMeta.some((entry) => entry?.id === channel)
|
||||
: Boolean(payload.channelMeta?.[channel]);
|
||||
if (!hasChannelMeta) {
|
||||
throw new Error(`missing channelMeta.${channel}\n${dump()}`);
|
||||
}
|
||||
if (!payload.channels || !payload.channels[channel]) {
|
||||
throw new Error(`missing channels.${channel}\n${dump()}`);
|
||||
}
|
||||
const accounts = payload.channelAccounts?.[channel];
|
||||
if (!Array.isArray(accounts) || accounts.length === 0) {
|
||||
throw new Error(`missing channelAccounts.${channel}\n${dump()}`);
|
||||
}
|
||||
|
||||
console.log(`${channel} channel plugin visible`);
|
||||
@@ -1,44 +0,0 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const stageDir = process.argv[2];
|
||||
const depName = process.argv[3];
|
||||
const manifestName = ".openclaw-runtime-deps.json";
|
||||
const matches = [];
|
||||
|
||||
function visit(dir) {
|
||||
let entries;
|
||||
try {
|
||||
entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
visit(fullPath);
|
||||
continue;
|
||||
}
|
||||
if (entry.name !== manifestName) {
|
||||
continue;
|
||||
}
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(fs.readFileSync(fullPath, "utf8"));
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
const specs = Array.isArray(parsed.specs) ? parsed.specs : [];
|
||||
for (const spec of specs) {
|
||||
if (typeof spec === "string" && spec.startsWith(`${depName}@`)) {
|
||||
matches.push(`${fullPath}: ${spec}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visit(stageDir);
|
||||
if (matches.length > 0) {
|
||||
process.stderr.write(`${matches.join("\n")}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import fs from "node:fs";
|
||||
|
||||
const payload = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
const expectedBefore = process.argv[3];
|
||||
const expectedAfter = process.argv[4];
|
||||
if (payload.status !== "ok") {
|
||||
throw new Error(`expected update status ok, got ${JSON.stringify(payload.status)}`);
|
||||
}
|
||||
if (expectedBefore && (payload.before?.version ?? null) !== expectedBefore) {
|
||||
throw new Error(
|
||||
`expected before.version ${expectedBefore}, got ${JSON.stringify(payload.before?.version)}`,
|
||||
);
|
||||
}
|
||||
if ((payload.after?.version ?? null) !== expectedAfter) {
|
||||
throw new Error(
|
||||
`expected after.version ${expectedAfter}, got ${JSON.stringify(payload.after?.version)}`,
|
||||
);
|
||||
}
|
||||
const steps = Array.isArray(payload.steps) ? payload.steps : [];
|
||||
const doctor = steps.find((step) => step?.name === "openclaw doctor");
|
||||
if (!doctor) {
|
||||
throw new Error("missing openclaw doctor step");
|
||||
}
|
||||
if (Number(doctor.exitCode ?? 1) !== 0) {
|
||||
throw new Error(`openclaw doctor step failed: ${JSON.stringify(doctor)}`);
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Runs one bundled plugin channel runtime-dependency scenario.
|
||||
# Sourced by scripts/e2e/bundled-channel-runtime-deps-docker.sh.
|
||||
|
||||
run_channel_scenario() {
|
||||
local channel="$1"
|
||||
local dep_sentinel="$2"
|
||||
|
||||
echo "Running bundled $channel runtime deps Docker E2E..."
|
||||
run_bundled_channel_container_with_state \
|
||||
"bundled-channel-deps-$channel" \
|
||||
"$DOCKER_RUN_TIMEOUT" \
|
||||
"bundled-channel-deps-$channel" \
|
||||
-e OPENCLAW_CHANNEL_UNDER_TEST="$channel" \
|
||||
-e OPENCLAW_DEP_SENTINEL="$dep_sentinel" \
|
||||
"${DOCKER_E2E_PACKAGE_ARGS[@]}" \
|
||||
-i "$IMAGE_NAME" bash -s <<'EOF'
|
||||
set -euo pipefail
|
||||
|
||||
source scripts/lib/openclaw-e2e-instance.sh
|
||||
source scripts/e2e/lib/bundled-channel/common.sh
|
||||
openclaw_e2e_eval_test_state_from_b64 "${OPENCLAW_TEST_STATE_SCRIPT_B64:?missing OPENCLAW_TEST_STATE_SCRIPT_B64}"
|
||||
export NPM_CONFIG_PREFIX="$HOME/.npm-global"
|
||||
export PATH="$NPM_CONFIG_PREFIX/bin:$PATH"
|
||||
export OPENAI_API_KEY="sk-openclaw-bundled-channel-deps-e2e"
|
||||
export OPENCLAW_NO_ONBOARD=1
|
||||
|
||||
TOKEN="bundled-channel-deps-token"
|
||||
PORT="18789"
|
||||
CHANNEL="${OPENCLAW_CHANNEL_UNDER_TEST:?missing OPENCLAW_CHANNEL_UNDER_TEST}"
|
||||
DEP_SENTINEL="${OPENCLAW_DEP_SENTINEL:?missing OPENCLAW_DEP_SENTINEL}"
|
||||
gateway_pid=""
|
||||
|
||||
terminate_gateways() {
|
||||
openclaw_e2e_terminate_gateways "${gateway_pid:-}"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
terminate_gateways
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
bundled_channel_install_package /tmp/openclaw-install.log
|
||||
|
||||
command -v openclaw >/dev/null
|
||||
package_root="$(openclaw_e2e_package_root)"
|
||||
openclaw_e2e_assert_package_extensions "$package_root" telegram discord slack feishu memory-lancedb
|
||||
|
||||
if [ -d "$package_root/dist/extensions/$CHANNEL/node_modules" ]; then
|
||||
echo "$CHANNEL runtime deps should not be preinstalled in package" >&2
|
||||
find "$package_root/dist/extensions/$CHANNEL/node_modules" -maxdepth 2 -type f | head -20 >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
start_gateway() {
|
||||
local log_file="$1"
|
||||
local skip_sidecars="${2:-0}"
|
||||
: >"$log_file"
|
||||
if [ "$skip_sidecars" = "1" ]; then
|
||||
OPENCLAW_SKIP_CHANNELS=1 OPENCLAW_SKIP_PROVIDERS=1 \
|
||||
openclaw gateway --port "$PORT" --bind loopback --allow-unconfigured >"$log_file" 2>&1 &
|
||||
else
|
||||
openclaw gateway --port "$PORT" --bind loopback --allow-unconfigured >"$log_file" 2>&1 &
|
||||
fi
|
||||
gateway_pid="$!"
|
||||
|
||||
# Cold bundled dependency staging can exceed 60s under 10-way Docker aggregate load.
|
||||
for _ in $(seq 1 1200); do
|
||||
if grep -Eq "listening on ws://|\\[gateway\\] http server listening|\\[gateway\\] ready( \\(|$)" "$log_file"; then
|
||||
return 0
|
||||
fi
|
||||
if ! kill -0 "$gateway_pid" 2>/dev/null; then
|
||||
echo "gateway exited unexpectedly" >&2
|
||||
cat "$log_file" >&2
|
||||
exit 1
|
||||
fi
|
||||
sleep 0.25
|
||||
done
|
||||
|
||||
echo "timed out waiting for gateway" >&2
|
||||
cat "$log_file" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
stop_gateway() {
|
||||
terminate_gateways
|
||||
gateway_pid=""
|
||||
}
|
||||
|
||||
wait_for_gateway_health() {
|
||||
local log_file="${1:-}"
|
||||
if [ -n "${gateway_pid:-}" ] && kill -0 "$gateway_pid" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
echo "gateway process exited after ready marker" >&2
|
||||
if [ -n "$log_file" ]; then
|
||||
cat "$log_file" >&2
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
parse_channel_status_json() {
|
||||
local out="$1"
|
||||
local channel="$2"
|
||||
node scripts/e2e/lib/bundled-channel/assert-channel-status.mjs "$out" "$channel"
|
||||
}
|
||||
|
||||
assert_channel_status() {
|
||||
local channel="$1"
|
||||
if [ "$channel" = "memory-lancedb" ]; then
|
||||
echo "memory-lancedb plugin activation verified by dependency sentinel"
|
||||
return 0
|
||||
fi
|
||||
local out="/tmp/openclaw-channel-status-$channel.json"
|
||||
local err="/tmp/openclaw-channel-status-$channel.err"
|
||||
local parse_err="/tmp/openclaw-channel-status-$channel.parse.err"
|
||||
local parse_out="/tmp/openclaw-channel-status-$channel.parse.out"
|
||||
for _ in $(seq 1 30); do
|
||||
if openclaw gateway call channels.status \
|
||||
--url "ws://127.0.0.1:$PORT" \
|
||||
--token "$TOKEN" \
|
||||
--timeout 10000 \
|
||||
--json \
|
||||
--params '{"probe":false}' >"$out" 2>"$err"; then
|
||||
if parse_channel_status_json "$out" "$channel" >"$parse_out" 2>"$parse_err"; then
|
||||
cat "$parse_out"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
if grep -Eq "\\[gateway\\] ready \\(.*\\b$channel\\b" /tmp/openclaw-"$channel"-*.log 2>/dev/null; then
|
||||
echo "$channel channel plugin visible in gateway ready log"
|
||||
return 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
if [ ! -s "$out" ]; then
|
||||
cat "$err" >&2 || true
|
||||
else
|
||||
cat "$parse_err" >&2 || true
|
||||
cat "$out" >&2 || true
|
||||
fi
|
||||
cat /tmp/openclaw-"$channel"-*.log >&2 2>/dev/null || true
|
||||
return 1
|
||||
}
|
||||
|
||||
assert_installed_once() {
|
||||
local log_file="$1"
|
||||
local channel="$2"
|
||||
local dep_path="$3"
|
||||
local count
|
||||
count="$(grep -Ec "\\[plugins\\] $channel installed bundled runtime deps( in [0-9]+ms)?:" "$log_file" || true)"
|
||||
if [ "$count" -eq 1 ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ "$count" -eq 0 ] && [ -n "$(bundled_channel_find_external_dep_package "$dep_path")" ]; then
|
||||
return 0
|
||||
fi
|
||||
echo "expected one runtime deps install log or staged dependency sentinel for $channel, got $count log lines" >&2
|
||||
cat "$log_file" >&2
|
||||
find "$(bundled_channel_stage_root)" -maxdepth 12 -type f | sort | head -120 >&2 || true
|
||||
exit 1
|
||||
}
|
||||
|
||||
assert_not_installed() {
|
||||
local log_file="$1"
|
||||
local channel="$2"
|
||||
if grep -Eq "\\[plugins\\] $channel installed bundled runtime deps( in [0-9]+ms)?:" "$log_file"; then
|
||||
echo "expected no runtime deps reinstall for $channel" >&2
|
||||
cat "$log_file" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_dep_sentinel() {
|
||||
local channel="$1"
|
||||
local dep_path="$2"
|
||||
bundled_channel_assert_dep_available "$channel" "$dep_path" "$package_root"
|
||||
}
|
||||
|
||||
assert_no_dep_sentinel() {
|
||||
local channel="$1"
|
||||
local dep_path="$2"
|
||||
bundled_channel_assert_no_dep_available "$channel" "$dep_path" "$package_root"
|
||||
}
|
||||
|
||||
assert_no_install_stage() {
|
||||
local channel="$1"
|
||||
local stage="$package_root/dist/extensions/$channel/.openclaw-install-stage"
|
||||
if [ -e "$stage" ]; then
|
||||
echo "install stage should be cleaned after activation for $channel" >&2
|
||||
find "$stage" -maxdepth 4 -type f | sort | head -80 >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "Starting baseline gateway with OpenAI configured..."
|
||||
bundled_channel_write_config baseline
|
||||
start_gateway "/tmp/openclaw-$CHANNEL-baseline.log" 1
|
||||
wait_for_gateway_health "/tmp/openclaw-$CHANNEL-baseline.log"
|
||||
stop_gateway
|
||||
assert_no_dep_sentinel "$CHANNEL" "$DEP_SENTINEL"
|
||||
|
||||
echo "Enabling $CHANNEL by config edit, then restarting gateway..."
|
||||
bundled_channel_write_config "$CHANNEL"
|
||||
start_gateway "/tmp/openclaw-$CHANNEL-first.log"
|
||||
wait_for_gateway_health "/tmp/openclaw-$CHANNEL-first.log"
|
||||
assert_installed_once "/tmp/openclaw-$CHANNEL-first.log" "$CHANNEL" "$DEP_SENTINEL"
|
||||
assert_dep_sentinel "$CHANNEL" "$DEP_SENTINEL"
|
||||
assert_no_install_stage "$CHANNEL"
|
||||
assert_channel_status "$CHANNEL"
|
||||
stop_gateway
|
||||
|
||||
echo "Restarting gateway again; $CHANNEL deps must stay installed..."
|
||||
start_gateway "/tmp/openclaw-$CHANNEL-second.log"
|
||||
wait_for_gateway_health "/tmp/openclaw-$CHANNEL-second.log"
|
||||
assert_not_installed "/tmp/openclaw-$CHANNEL-second.log" "$CHANNEL"
|
||||
assert_no_install_stage "$CHANNEL"
|
||||
assert_channel_status "$CHANNEL"
|
||||
stop_gateway
|
||||
|
||||
echo "bundled $CHANNEL runtime deps Docker E2E passed"
|
||||
EOF
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Container-side helpers shared by bundled channel Docker E2E scenarios.
|
||||
# These functions assume the OpenClaw package is installed globally inside the
|
||||
# test container and the scenario has exported HOME/OPENAI_API_KEY as needed.
|
||||
|
||||
bundled_channel_package_root() {
|
||||
printf "%s/openclaw" "$(npm root -g)"
|
||||
}
|
||||
|
||||
bundled_channel_stage_root() {
|
||||
printf "%s/.openclaw/plugin-runtime-deps" "$HOME"
|
||||
}
|
||||
|
||||
bundled_channel_stage_dir() {
|
||||
printf "%s" "${OPENCLAW_PLUGIN_STAGE_DIR:-$(bundled_channel_stage_root)}"
|
||||
}
|
||||
|
||||
bundled_channel_install_package() {
|
||||
openclaw_e2e_install_package "$@"
|
||||
}
|
||||
|
||||
bundled_channel_find_external_dep_package() {
|
||||
local dep_path="$1"
|
||||
find "$(bundled_channel_stage_root)" -maxdepth 12 -path "*/node_modules/$dep_path/package.json" -type f -print -quit 2>/dev/null || true
|
||||
}
|
||||
|
||||
bundled_channel_find_staged_dep_package() {
|
||||
local dep_path="$1"
|
||||
find "$(bundled_channel_stage_dir)" -maxdepth 12 -path "*/node_modules/$dep_path/package.json" -type f -print -quit 2>/dev/null || true
|
||||
}
|
||||
|
||||
bundled_channel_dump_stage_dir() {
|
||||
find "$(bundled_channel_stage_dir)" -maxdepth 12 -type f | sort | head -160 >&2 || true
|
||||
}
|
||||
|
||||
bundled_channel_assert_no_package_dep_available() {
|
||||
local channel="$1"
|
||||
local dep_path="$2"
|
||||
local root="${3:-$(bundled_channel_package_root)}"
|
||||
for candidate in \
|
||||
"$root/dist/extensions/$channel/node_modules/$dep_path/package.json" \
|
||||
"$root/dist/extensions/node_modules/$dep_path/package.json" \
|
||||
"$root/node_modules/$dep_path/package.json"; do
|
||||
if [ -f "$candidate" ]; then
|
||||
echo "packaged install should not mutate package tree for $channel: $candidate" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
if [ -f "$HOME/node_modules/$dep_path/package.json" ]; then
|
||||
echo "bundled runtime deps should not use HOME npm project for $channel: $HOME/node_modules/$dep_path/package.json" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
bundled_channel_assert_dep_available() {
|
||||
local channel="$1"
|
||||
local dep_path="$2"
|
||||
local root="${3:-$(bundled_channel_package_root)}"
|
||||
if [ -n "$(bundled_channel_find_external_dep_package "$dep_path")" ]; then
|
||||
bundled_channel_assert_no_package_dep_available "$channel" "$dep_path" "$root"
|
||||
return 0
|
||||
fi
|
||||
echo "missing dependency sentinel for $channel: $dep_path" >&2
|
||||
find "$root/dist/extensions/$channel" -maxdepth 3 -type f | sort | head -80 >&2 || true
|
||||
find "$root/node_modules" -maxdepth 3 -path "*/$dep_path/package.json" -type f -print >&2 || true
|
||||
find "$(bundled_channel_stage_root)" -maxdepth 12 -type f | sort | head -120 >&2 || true
|
||||
exit 1
|
||||
}
|
||||
|
||||
bundled_channel_assert_no_dep_available() {
|
||||
local channel="$1"
|
||||
local dep_path="$2"
|
||||
local root="${3:-$(bundled_channel_package_root)}"
|
||||
bundled_channel_assert_no_package_dep_available "$channel" "$dep_path" "$root"
|
||||
if [ -n "$(bundled_channel_find_external_dep_package "$dep_path")" ]; then
|
||||
echo "dependency sentinel should be absent before repair for $channel: $dep_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
bundled_channel_assert_no_staged_dep() {
|
||||
local channel="$1"
|
||||
local dep_path="$2"
|
||||
local message="${3:-$channel unexpectedly staged $dep_path}"
|
||||
if [ -n "$(bundled_channel_find_staged_dep_package "$dep_path")" ]; then
|
||||
echo "$message" >&2
|
||||
bundled_channel_dump_stage_dir
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
bundled_channel_assert_staged_dep() {
|
||||
local channel="$1"
|
||||
local dep_path="$2"
|
||||
local log_file="${3:-}"
|
||||
if [ -n "$(bundled_channel_find_staged_dep_package "$dep_path")" ]; then
|
||||
return 0
|
||||
fi
|
||||
echo "missing external staged dependency sentinel for $channel: $dep_path" >&2
|
||||
if [ -n "$log_file" ]; then
|
||||
cat "$log_file" >&2 || true
|
||||
fi
|
||||
bundled_channel_dump_stage_dir
|
||||
exit 1
|
||||
}
|
||||
|
||||
bundled_channel_assert_no_staged_manifest_spec() {
|
||||
local channel="$1"
|
||||
local dep_path="$2"
|
||||
local log_file="${3:-}"
|
||||
if ! node scripts/e2e/lib/bundled-channel/assert-no-staged-manifest-spec.mjs "$(bundled_channel_stage_dir)" "$dep_path"; then
|
||||
echo "$channel unexpectedly selected $dep_path for external runtime deps" >&2
|
||||
if [ -n "$log_file" ]; then
|
||||
cat "$log_file" >&2 || true
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
bundled_channel_remove_runtime_dep() {
|
||||
local channel="$1"
|
||||
local dep_path="$2"
|
||||
local root="${3:-$(bundled_channel_package_root)}"
|
||||
rm -rf "$root/dist/extensions/$channel/node_modules"
|
||||
rm -rf "$root/dist/extensions/node_modules/$dep_path"
|
||||
rm -rf "$root/node_modules/$dep_path"
|
||||
rm -rf "$(bundled_channel_stage_root)"
|
||||
}
|
||||
|
||||
bundled_channel_write_config() {
|
||||
local mode="$1"
|
||||
node scripts/e2e/lib/bundled-channel/write-config.mjs \
|
||||
"$mode" \
|
||||
"${TOKEN:-bundled-channel-config-token}" \
|
||||
"${PORT:-18789}"
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Runs disabled-config runtime-dependency isolation scenarios.
|
||||
# Sourced by scripts/e2e/bundled-channel-runtime-deps-docker.sh.
|
||||
|
||||
run_disabled_config_scenario() {
|
||||
echo "Running bundled channel disabled-config runtime deps Docker E2E..."
|
||||
run_bundled_channel_container_with_state \
|
||||
bundled-channel-disabled-config \
|
||||
"$DOCKER_RUN_TIMEOUT" \
|
||||
bundled-channel-disabled-config \
|
||||
"${DOCKER_E2E_PACKAGE_ARGS[@]}" \
|
||||
-i "$IMAGE_NAME" bash -s <<'EOF'
|
||||
set -euo pipefail
|
||||
|
||||
source scripts/lib/openclaw-e2e-instance.sh
|
||||
source scripts/e2e/lib/bundled-channel/common.sh
|
||||
openclaw_e2e_eval_test_state_from_b64 "${OPENCLAW_TEST_STATE_SCRIPT_B64:?missing OPENCLAW_TEST_STATE_SCRIPT_B64}"
|
||||
export NPM_CONFIG_PREFIX="$HOME/.npm-global"
|
||||
export PATH="$NPM_CONFIG_PREFIX/bin:$PATH"
|
||||
export OPENCLAW_NO_ONBOARD=1
|
||||
export OPENCLAW_PLUGIN_STAGE_DIR="$HOME/.openclaw/plugin-runtime-deps"
|
||||
mkdir -p "$OPENCLAW_PLUGIN_STAGE_DIR"
|
||||
|
||||
assert_dep_absent_everywhere() {
|
||||
local channel="$1"
|
||||
local dep_path="$2"
|
||||
local root="$3"
|
||||
bundled_channel_assert_no_package_dep_available "$channel" "$dep_path" "$root"
|
||||
bundled_channel_assert_no_staged_manifest_spec "$channel" "$dep_path" /tmp/openclaw-disabled-config-doctor.log
|
||||
}
|
||||
|
||||
bundled_channel_install_package /tmp/openclaw-disabled-config-install.log
|
||||
|
||||
root="$(bundled_channel_package_root)"
|
||||
test -d "$root/dist/extensions/telegram"
|
||||
test -d "$root/dist/extensions/discord"
|
||||
test -d "$root/dist/extensions/slack"
|
||||
rm -rf "$root/dist/extensions/telegram/node_modules"
|
||||
rm -rf "$root/dist/extensions/discord/node_modules"
|
||||
rm -rf "$root/dist/extensions/slack/node_modules"
|
||||
|
||||
bundled_channel_write_config disabled-config
|
||||
|
||||
if ! openclaw doctor --non-interactive >/tmp/openclaw-disabled-config-doctor.log 2>&1; then
|
||||
echo "doctor failed for disabled-config runtime deps smoke" >&2
|
||||
cat /tmp/openclaw-disabled-config-doctor.log >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
assert_dep_absent_everywhere telegram grammy "$root"
|
||||
assert_dep_absent_everywhere slack @slack/web-api "$root"
|
||||
assert_dep_absent_everywhere discord discord-api-types "$root"
|
||||
|
||||
if grep -Eq "(grammy|@slack/web-api|discord-api-types)" /tmp/openclaw-disabled-config-doctor.log; then
|
||||
echo "doctor installed runtime deps for an explicitly disabled channel/plugin" >&2
|
||||
cat /tmp/openclaw-disabled-config-doctor.log >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "bundled channel disabled-config runtime deps Docker E2E passed"
|
||||
EOF
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import { readdir } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
const root = process.argv[2] || process.env.OPENCLAW_PACKAGE_ROOT;
|
||||
if (!root) {
|
||||
throw new Error("missing package root");
|
||||
}
|
||||
|
||||
const distDir = path.join(root, "dist");
|
||||
const onboardChannelFiles = (await readdir(distDir))
|
||||
.filter((entry) => /^onboard-channels-.*\.js$/.test(entry))
|
||||
.toSorted();
|
||||
let setupChannels;
|
||||
for (const entry of onboardChannelFiles) {
|
||||
const module = await import(pathToFileURL(path.join(distDir, entry)));
|
||||
if (typeof module.setupChannels === "function") {
|
||||
setupChannels = module.setupChannels;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!setupChannels) {
|
||||
throw new Error(
|
||||
`could not find packaged setupChannels export in ${JSON.stringify(onboardChannelFiles)}`,
|
||||
);
|
||||
}
|
||||
|
||||
let channelSelectCount = 0;
|
||||
const notes = [];
|
||||
const prompter = {
|
||||
intro: async () => {},
|
||||
outro: async () => {},
|
||||
note: async (body, title) => {
|
||||
notes.push({ title, body });
|
||||
},
|
||||
confirm: async ({ message, initialValue }) => {
|
||||
if (message === "Link WhatsApp now (QR)?") {
|
||||
return false;
|
||||
}
|
||||
return initialValue ?? true;
|
||||
},
|
||||
select: async ({ message, options }) => {
|
||||
if (message === "Select a channel") {
|
||||
channelSelectCount += 1;
|
||||
return channelSelectCount === 1 ? "whatsapp" : "__done__";
|
||||
}
|
||||
if (message === "Install WhatsApp plugin?") {
|
||||
if (!options?.some((option) => option.value === "local")) {
|
||||
throw new Error(`missing bundled local install option: ${JSON.stringify(options)}`);
|
||||
}
|
||||
return "local";
|
||||
}
|
||||
if (message === "WhatsApp phone setup") {
|
||||
return "separate";
|
||||
}
|
||||
if (message === "WhatsApp DM policy") {
|
||||
return "disabled";
|
||||
}
|
||||
throw new Error(`unexpected select prompt: ${message}`);
|
||||
},
|
||||
multiselect: async ({ message }) => {
|
||||
throw new Error(`unexpected multiselect prompt: ${message}`);
|
||||
},
|
||||
text: async ({ message }) => {
|
||||
throw new Error(`unexpected text prompt: ${message}`);
|
||||
},
|
||||
};
|
||||
const runtime = {
|
||||
log: (message) => console.log(message),
|
||||
error: (message) => console.error(message),
|
||||
};
|
||||
|
||||
const result = await setupChannels({ plugins: { enabled: true } }, runtime, prompter, {
|
||||
deferStatusUntilSelection: true,
|
||||
skipConfirm: true,
|
||||
skipStatusNote: true,
|
||||
skipDmPolicyPrompt: true,
|
||||
initialSelection: ["whatsapp"],
|
||||
});
|
||||
|
||||
if (!result.channels?.whatsapp) {
|
||||
throw new Error(`WhatsApp setup did not write channel config: ${JSON.stringify(result)}`);
|
||||
}
|
||||
console.log("packaged guided WhatsApp setup completed");
|
||||
@@ -1,34 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Runs load-failure isolation scenarios.
|
||||
# Sourced by scripts/e2e/bundled-channel-runtime-deps-docker.sh.
|
||||
|
||||
run_load_failure_scenario() {
|
||||
echo "Running bundled channel load-failure isolation Docker E2E..."
|
||||
run_bundled_channel_container_with_state \
|
||||
bundled-channel-load-failure \
|
||||
"$DOCKER_RUN_TIMEOUT" \
|
||||
bundled-channel-load-failure \
|
||||
"${DOCKER_E2E_PACKAGE_ARGS[@]}" \
|
||||
-i "$IMAGE_NAME" bash -s <<'EOF'
|
||||
set -euo pipefail
|
||||
|
||||
source scripts/lib/openclaw-e2e-instance.sh
|
||||
source scripts/e2e/lib/bundled-channel/common.sh
|
||||
openclaw_e2e_eval_test_state_from_b64 "${OPENCLAW_TEST_STATE_SCRIPT_B64:?missing OPENCLAW_TEST_STATE_SCRIPT_B64}"
|
||||
export NPM_CONFIG_PREFIX="$HOME/.npm-global"
|
||||
export PATH="$NPM_CONFIG_PREFIX/bin:$PATH"
|
||||
export OPENCLAW_NO_ONBOARD=1
|
||||
|
||||
bundled_channel_install_package /tmp/openclaw-load-failure-install.log
|
||||
|
||||
root="$(bundled_channel_package_root)"
|
||||
plugin_dir="$root/dist/extensions/load-failure-alpha"
|
||||
node scripts/e2e/lib/bundled-channel/write-load-failure-fixture.mjs "$plugin_dir"
|
||||
|
||||
echo "Loading synthetic failing bundled channel through packaged loader..."
|
||||
node scripts/e2e/lib/bundled-channel/loader-probe.mjs load-failure "$root" load-failure-alpha
|
||||
|
||||
echo "bundled channel load-failure isolation Docker E2E passed"
|
||||
EOF
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
function usage() {
|
||||
console.error("Usage: loader-probe.mjs <setup-entries|load-failure> <package-root> [channel...]");
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
function findBundledLoader(root) {
|
||||
const distDir = path.join(root, "dist");
|
||||
const bundledPath = fs
|
||||
.readdirSync(distDir)
|
||||
.filter((entry) => /^bundled-[A-Za-z0-9_-]+\.js$/.test(entry))
|
||||
.map((entry) => path.join(distDir, entry))
|
||||
.find((entry) => fs.readFileSync(entry, "utf8").includes("src/channels/plugins/bundled.ts"));
|
||||
if (!bundledPath) {
|
||||
throw new Error("missing packaged bundled channel loader artifact");
|
||||
}
|
||||
return bundledPath;
|
||||
}
|
||||
|
||||
function namedExport(module, name) {
|
||||
const fn = Object.values(module).find(
|
||||
(value) => typeof value === "function" && value.name === name,
|
||||
);
|
||||
if (typeof fn !== "function") {
|
||||
throw new Error(
|
||||
`missing packaged bundled loader export ${name}; exports=${Object.keys(module).join(",")}`,
|
||||
);
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
async function importBundled(root) {
|
||||
return import(pathToFileURL(findBundledLoader(root)));
|
||||
}
|
||||
|
||||
function loadCounts() {
|
||||
return {
|
||||
plugin: globalThis.__loadFailurePlugin,
|
||||
setup: globalThis.__loadFailureSetup,
|
||||
secrets: globalThis.__loadFailureSecrets,
|
||||
setupSecrets: globalThis.__loadFailureSetupSecrets,
|
||||
};
|
||||
}
|
||||
|
||||
function exerciseLoaders(loaders, id) {
|
||||
for (const [name, fn] of loaders) {
|
||||
try {
|
||||
fn(id);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
if (message.includes("synthetic")) {
|
||||
throw new Error(`bundled export ${name} leaked synthetic load failure: ${message}`, {
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [command, root, ...args] = process.argv.slice(2);
|
||||
if (!command || !root) {
|
||||
usage();
|
||||
}
|
||||
|
||||
if (command === "load-failure") {
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = path.join(root, "dist/extensions");
|
||||
}
|
||||
|
||||
const bundled = await importBundled(root);
|
||||
|
||||
if (command === "setup-entries") {
|
||||
const channels = args.length > 0 ? args : ["feishu", "whatsapp"];
|
||||
const setupPluginLoader = namedExport(bundled, "getBundledChannelSetupPlugin");
|
||||
for (const channel of channels) {
|
||||
const plugin = setupPluginLoader(channel);
|
||||
if (!plugin) {
|
||||
throw new Error(`${channel} setup plugin did not load pre-config`);
|
||||
}
|
||||
if (plugin.id !== channel) {
|
||||
throw new Error(`${channel} setup plugin id mismatch: ${plugin.id}`);
|
||||
}
|
||||
console.log(`${channel} setup plugin loaded pre-config`);
|
||||
}
|
||||
} else if (command === "load-failure") {
|
||||
const id = args[0] || "load-failure-alpha";
|
||||
const loaderNames = [
|
||||
"getBundledChannelPlugin",
|
||||
"getBundledChannelSetupPlugin",
|
||||
"getBundledChannelSecrets",
|
||||
"getBundledChannelSetupSecrets",
|
||||
];
|
||||
const loaders = loaderNames.map((name) => [name, namedExport(bundled, name)]);
|
||||
|
||||
exerciseLoaders(loaders, id);
|
||||
const firstCounts = loadCounts();
|
||||
exerciseLoaders(loaders, id);
|
||||
const secondCounts = loadCounts();
|
||||
for (const key of ["plugin", "setup", "setupSecrets"]) {
|
||||
const first = firstCounts[key];
|
||||
if (!Number.isInteger(first) || first < 1) {
|
||||
throw new Error(`expected ${key} failure to be exercised at least once, got ${first}`);
|
||||
}
|
||||
if (secondCounts[key] !== first) {
|
||||
throw new Error(
|
||||
`expected ${key} failure to be cached after first pass, got ${first} then ${secondCounts[key]}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (firstCounts.secrets !== undefined && secondCounts.secrets !== firstCounts.secrets) {
|
||||
throw new Error(
|
||||
`expected secrets failure to be cached after first pass, got ${firstCounts.secrets} then ${secondCounts.secrets}`,
|
||||
);
|
||||
}
|
||||
console.log("synthetic bundled channel load failures were isolated and cached");
|
||||
} else {
|
||||
usage();
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
|
||||
const raw = execFileSync("tar", ["-xOf", process.argv[2], "package/package.json"], {
|
||||
encoding: "utf8",
|
||||
});
|
||||
process.stdout.write(String(JSON.parse(raw).version));
|
||||
@@ -1,124 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Runs the root-owned global install runtime-dependency scenario.
|
||||
# Sourced by scripts/e2e/bundled-channel-runtime-deps-docker.sh.
|
||||
|
||||
run_root_owned_global_scenario() {
|
||||
echo "Running bundled channel root-owned global install Docker E2E..."
|
||||
run_bundled_channel_container bundled-channel-root-owned "$DOCKER_RUN_TIMEOUT" \
|
||||
--user root \
|
||||
-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
|
||||
"${DOCKER_E2E_PACKAGE_ARGS[@]}" \
|
||||
-i "$IMAGE_NAME" bash -s <<'EOF'
|
||||
set -euo pipefail
|
||||
|
||||
source scripts/lib/openclaw-e2e-instance.sh
|
||||
source scripts/e2e/lib/bundled-channel/common.sh
|
||||
export HOME="/root"
|
||||
export OPENAI_API_KEY="sk-openclaw-bundled-channel-root-owned-e2e"
|
||||
export OPENCLAW_NO_ONBOARD=1
|
||||
export OPENCLAW_PLUGIN_STAGE_DIR="/var/lib/openclaw/plugin-runtime-deps"
|
||||
|
||||
TOKEN="bundled-channel-root-owned-token"
|
||||
PORT="18791"
|
||||
CHANNEL="slack"
|
||||
DEP_SENTINEL="@slack/web-api"
|
||||
gateway_pid=""
|
||||
|
||||
cleanup() {
|
||||
if [ -n "${gateway_pid:-}" ] && kill -0 "$gateway_pid" 2>/dev/null; then
|
||||
kill "$gateway_pid" 2>/dev/null || true
|
||||
wait "$gateway_pid" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
bundled_channel_install_package /tmp/openclaw-root-owned-install.log "mounted OpenClaw package into root-owned global npm"
|
||||
|
||||
root="$(bundled_channel_package_root)"
|
||||
test -d "$root/dist/extensions/$CHANNEL"
|
||||
rm -rf "$root/dist/extensions/$CHANNEL/node_modules"
|
||||
chmod -R a-w "$root"
|
||||
mkdir -p "$OPENCLAW_PLUGIN_STAGE_DIR" /home/appuser/.openclaw
|
||||
chown -R appuser:appuser /home/appuser/.openclaw /var/lib/openclaw
|
||||
|
||||
if runuser -u appuser -- test -w "$root"; then
|
||||
echo "expected package root to be unwritable for appuser" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OPENCLAW_BUNDLED_CHANNEL_CONFIG_PATH=/home/appuser/.openclaw/openclaw.json \
|
||||
OPENCLAW_BUNDLED_CHANNEL_SLACK_BOT_TOKEN=xoxb-bundled-channel-root-owned-token \
|
||||
OPENCLAW_BUNDLED_CHANNEL_SLACK_APP_TOKEN=xapp-bundled-channel-root-owned-token \
|
||||
bundled_channel_write_config slack
|
||||
chown appuser:appuser /home/appuser/.openclaw/openclaw.json
|
||||
|
||||
start_gateway() {
|
||||
local log_file="$1"
|
||||
: >"$log_file"
|
||||
chown appuser:appuser "$log_file"
|
||||
runuser -u appuser -- env \
|
||||
HOME=/home/appuser \
|
||||
OPENAI_API_KEY="$OPENAI_API_KEY" \
|
||||
OPENCLAW_NO_ONBOARD=1 \
|
||||
OPENCLAW_PLUGIN_STAGE_DIR="$OPENCLAW_PLUGIN_STAGE_DIR" \
|
||||
npm_config_cache=/tmp/openclaw-root-owned-npm-cache \
|
||||
bash -c 'openclaw gateway --port "$1" --bind loopback --allow-unconfigured >"$2" 2>&1' \
|
||||
bash "$PORT" "$log_file" &
|
||||
gateway_pid="$!"
|
||||
|
||||
# Cold bundled dependency staging can exceed 60s under 10-way Docker aggregate load.
|
||||
for _ in $(seq 1 1200); do
|
||||
if grep -Eq "listening on ws://|\\[gateway\\] http server listening|\\[gateway\\] ready( \\(|$)" "$log_file"; then
|
||||
return 0
|
||||
fi
|
||||
if ! kill -0 "$gateway_pid" 2>/dev/null; then
|
||||
echo "gateway exited unexpectedly" >&2
|
||||
cat "$log_file" >&2
|
||||
exit 1
|
||||
fi
|
||||
sleep 0.25
|
||||
done
|
||||
|
||||
echo "timed out waiting for gateway" >&2
|
||||
cat "$log_file" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
wait_for_slack_provider_start() {
|
||||
for _ in $(seq 1 180); do
|
||||
if grep -Eq "\\[slack\\] \\[default\\] starting provider|An API error occurred: invalid_auth|\\[plugins\\] slack installed bundled runtime deps|\\[gateway\\] ready \\(.*\\bslack\\b" /tmp/openclaw-root-owned-gateway.log; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
echo "timed out waiting for slack provider startup" >&2
|
||||
cat /tmp/openclaw-root-owned-gateway.log >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
start_gateway /tmp/openclaw-root-owned-gateway.log
|
||||
wait_for_slack_provider_start
|
||||
|
||||
bundled_channel_assert_no_package_dep_available "$CHANNEL" "$DEP_SENTINEL" "$root"
|
||||
bundled_channel_assert_staged_dep "$CHANNEL" "$DEP_SENTINEL" /tmp/openclaw-root-owned-gateway.log
|
||||
if [ -e "$root/dist/extensions/node_modules/openclaw/package.json" ]; then
|
||||
echo "root-owned package tree was mutated with SDK alias" >&2
|
||||
find "$root/dist/extensions/node_modules/openclaw" -maxdepth 4 -type f | sort | head -80 >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
if ! find "$(bundled_channel_stage_dir)" -maxdepth 12 -path "*/dist/extensions/node_modules/openclaw/package.json" -type f | grep -q .; then
|
||||
echo "missing external staged openclaw/plugin-sdk alias" >&2
|
||||
bundled_channel_dump_stage_dir
|
||||
cat /tmp/openclaw-root-owned-gateway.log >&2
|
||||
exit 1
|
||||
fi
|
||||
if grep -Eq "failed to install bundled runtime deps|Cannot find package 'openclaw'|Cannot find module 'openclaw/plugin-sdk'" /tmp/openclaw-root-owned-gateway.log; then
|
||||
echo "root-owned gateway hit bundled runtime dependency errors" >&2
|
||||
cat /tmp/openclaw-root-owned-gateway.log >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "root-owned global install Docker E2E passed"
|
||||
EOF
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Runs setup-entry runtime-dependency installation scenarios.
|
||||
# Sourced by scripts/e2e/bundled-channel-runtime-deps-docker.sh.
|
||||
|
||||
run_setup_entry_scenario() {
|
||||
echo "Running bundled channel setup-entry runtime deps Docker E2E..."
|
||||
run_bundled_channel_container_with_state \
|
||||
bundled-channel-setup-entry \
|
||||
"$DOCKER_RUN_TIMEOUT" \
|
||||
bundled-channel-setup-entry \
|
||||
"${DOCKER_E2E_PACKAGE_ARGS[@]}" \
|
||||
-i "$IMAGE_NAME" bash -s <<'EOF'
|
||||
set -euo pipefail
|
||||
|
||||
source scripts/lib/openclaw-e2e-instance.sh
|
||||
source scripts/e2e/lib/bundled-channel/common.sh
|
||||
openclaw_e2e_eval_test_state_from_b64 "${OPENCLAW_TEST_STATE_SCRIPT_B64:?missing OPENCLAW_TEST_STATE_SCRIPT_B64}"
|
||||
export NPM_CONFIG_PREFIX="$HOME/.npm-global"
|
||||
export PATH="$NPM_CONFIG_PREFIX/bin:$PATH"
|
||||
export OPENCLAW_NO_ONBOARD=1
|
||||
export OPENCLAW_PLUGIN_STAGE_DIR="$HOME/.openclaw/plugin-runtime-deps"
|
||||
mkdir -p "$OPENCLAW_PLUGIN_STAGE_DIR"
|
||||
|
||||
declare -A SETUP_ENTRY_DEP_SENTINELS=(
|
||||
[feishu]="@larksuiteoapi/node-sdk"
|
||||
[whatsapp]="@whiskeysockets/baileys"
|
||||
)
|
||||
|
||||
bundled_channel_install_package /tmp/openclaw-setup-entry-install.log
|
||||
|
||||
root="$(bundled_channel_package_root)"
|
||||
for channel in "${!SETUP_ENTRY_DEP_SENTINELS[@]}"; do
|
||||
dep_sentinel="${SETUP_ENTRY_DEP_SENTINELS[$channel]}"
|
||||
test -d "$root/dist/extensions/$channel"
|
||||
bundled_channel_assert_no_package_dep_available "$channel" "$dep_sentinel" "$root"
|
||||
done
|
||||
|
||||
echo "Probing real bundled setup entries before channel configuration..."
|
||||
node scripts/e2e/lib/bundled-channel/loader-probe.mjs setup-entries "$root" feishu whatsapp
|
||||
|
||||
for channel in "${!SETUP_ENTRY_DEP_SENTINELS[@]}"; do
|
||||
dep_sentinel="${SETUP_ENTRY_DEP_SENTINELS[$channel]}"
|
||||
bundled_channel_assert_no_package_dep_available "$channel" "$dep_sentinel" "$root"
|
||||
bundled_channel_assert_no_staged_dep "$channel" "$dep_sentinel" "setup-entry discovery installed $channel external staged deps before channel configuration"
|
||||
done
|
||||
|
||||
echo "Running packaged guided WhatsApp setup; runtime deps should be staged before finalize..."
|
||||
node scripts/e2e/lib/bundled-channel/guided-whatsapp-setup.mjs "$root"
|
||||
|
||||
bundled_channel_assert_no_package_dep_available whatsapp @whiskeysockets/baileys "$root"
|
||||
bundled_channel_assert_staged_dep whatsapp @whiskeysockets/baileys
|
||||
|
||||
echo "Configuring setup-entry channels; doctor should now install bundled runtime deps externally..."
|
||||
bundled_channel_write_config setup-entry-channels
|
||||
|
||||
openclaw doctor --fix --non-interactive >/tmp/openclaw-setup-entry-doctor.log 2>&1
|
||||
|
||||
for channel in "${!SETUP_ENTRY_DEP_SENTINELS[@]}"; do
|
||||
dep_sentinel="${SETUP_ENTRY_DEP_SENTINELS[$channel]}"
|
||||
bundled_channel_assert_no_package_dep_available "$channel" "$dep_sentinel" "$root"
|
||||
bundled_channel_assert_staged_dep "$channel" "$dep_sentinel" /tmp/openclaw-setup-entry-doctor.log
|
||||
done
|
||||
|
||||
echo "bundled channel setup-entry runtime deps Docker E2E passed"
|
||||
EOF
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user