From 010f7a58a1aaa0efff541a6844c7b3d684a2bacc Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 2 May 2026 08:47:58 +0100 Subject: [PATCH] build(plugins): externalize acpx release packages --- CHANGELOG.md | 2 + docs/gateway/configuration-reference.md | 2 +- docs/gateway/opentelemetry.md | 8 +++- docs/help/testing-live.md | 2 +- docs/install/docker.md | 14 +++---- docs/tools/acp-agents-setup.md | 19 ++++++--- docs/tools/acp-agents.md | 21 ++++++---- extensions/acpx/AGENTS.md | 4 +- extensions/acpx/package.json | 23 ++++++++++- extensions/acpx/skills/acp-router/SKILL.md | 4 +- extensions/diagnostics-otel/package.json | 9 ++++- .../diagnostics-prometheus/package.json | 5 +++ extensions/lobster/package.json | 5 +++ extensions/voice-call/package.json | 2 + package.json | 1 + scripts/lib/plugin-npm-release.ts | 6 +++ test/plugin-npm-release.test.ts | 39 +++++++++++++++++++ 17 files changed, 134 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13b6fbbba8b..b80961bdd53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ Docs: https://docs.openclaw.ai - Tools: add a platform-level tool descriptor planner for descriptor-first visibility, generic availability checks, and executor references. Thanks @shakkernerd. - Docs/Codex: clarify that ChatGPT/Codex subscription setups should use `openai/gpt-*` with `agentRuntime.id: "codex"` for native Codex runtime, while `openai-codex/*` remains the PI OAuth route. Thanks @pashpashpash. - Plugins/source checkout: load bundled plugins from the `extensions/*` pnpm workspace tree in source checkouts, so plugin-local dependencies and edits are used directly while packaged installs keep using the built runtime tree. Thanks @vincentkoc. +- Plugins/beta: externalize ACPX behind the official `@openclaw/acpx` package so packaged installs keep ACP harness adapter binaries out of core until the ACP backend is installed. Thanks @vincentkoc. +- Plugins/beta: externalize diagnostics OpenTelemetry behind the official `@openclaw/diagnostics-otel` package so packaged installs keep the OTEL dependency stack out of core until the plugin is installed. Thanks @vincentkoc. - Plugins/beta: prepare Google Chat, LINE, Matrix, and Mattermost for `2026.5.1-beta.2` npm and ClawHub publishing, and keep publishable plugin dist trees out of the core npm package. Thanks @vincentkoc. - Plugins/beta: prepare BlueBubbles, diagnostics Prometheus, Google Meet, Nextcloud Talk, Nostr, Zalo, and Zalo Personal for `2026.5.1-beta.2` npm and ClawHub publishing. Thanks @vincentkoc. - Plugins/beta: prepare diagnostics OpenTelemetry, Discord, Diffs, Lobster, Memory LanceDB, Microsoft Teams, QQ Bot, Voice Call, and WhatsApp for `2026.5.1-beta.1` npm and ClawHub publishing. Thanks @vincentkoc. diff --git a/docs/gateway/configuration-reference.md b/docs/gateway/configuration-reference.md index c07ef30ff29..d6157dfd626 100644 --- a/docs/gateway/configuration-reference.md +++ b/docs/gateway/configuration-reference.md @@ -1016,7 +1016,7 @@ Notes: - `enabled`: global ACP feature gate (default: `true`; set `false` to hide ACP dispatch and spawn affordances). - `dispatch.enabled`: independent gate for ACP session turn dispatch (default: `true`). Set `false` to keep ACP commands available while blocking execution. - `backend`: default ACP runtime backend id (must match a registered ACP runtime plugin). - If `plugins.allow` is set, include the backend plugin id (for example `acpx`) or the bundled default plugin will not load. + Install the backend plugin first, and if `plugins.allow` is set, include the backend plugin id (for example `acpx`) or the ACP backend will not load. - `defaultAgent`: fallback ACP target agent id when spawns do not specify an explicit target. - `allowedAgents`: allowlist of agent ids permitted for ACP runtime sessions; empty means no additional restriction. - `maxConcurrentSessions`: maximum concurrently active ACP sessions. diff --git a/docs/gateway/opentelemetry.md b/docs/gateway/opentelemetry.md index 03286ac618d..38c19ba01b6 100644 --- a/docs/gateway/opentelemetry.md +++ b/docs/gateway/opentelemetry.md @@ -7,7 +7,7 @@ read_when: - You need the exact metric names, span names, or attribute shapes to build dashboards or alerts --- -OpenClaw exports diagnostics through the bundled `diagnostics-otel` plugin +OpenClaw exports diagnostics through the official `diagnostics-otel` plugin using **OTLP/HTTP (protobuf)**. Any collector or backend that accepts OTLP/HTTP works without code changes. For local file logs and how to read them, see [Logging](/logging). @@ -27,6 +27,12 @@ works without code changes. For local file logs and how to read them, see ## Quick start +For packaged installs, install the plugin first: + +```bash +openclaw plugins install @openclaw/diagnostics-otel +``` + ```json5 { plugins: { diff --git a/docs/help/testing-live.md b/docs/help/testing-live.md index 236a72dfe30..8ffed933d62 100644 --- a/docs/help/testing-live.md +++ b/docs/help/testing-live.md @@ -266,7 +266,7 @@ Docker notes: - The Docker runner lives at `scripts/test-live-acp-bind-docker.sh`. - By default, it runs the ACP bind smoke against the aggregate live CLI agents in sequence: `claude`, `codex`, then `gemini`. - Use `OPENCLAW_LIVE_ACP_BIND_AGENTS=claude`, `OPENCLAW_LIVE_ACP_BIND_AGENTS=codex`, `OPENCLAW_LIVE_ACP_BIND_AGENTS=droid`, `OPENCLAW_LIVE_ACP_BIND_AGENTS=gemini`, or `OPENCLAW_LIVE_ACP_BIND_AGENTS=opencode` to narrow the matrix. -- It sources `~/.profile`, stages the matching CLI auth material into the container, then installs the requested live CLI (`@anthropic-ai/claude-code`, `@openai/codex`, Factory Droid via `https://app.factory.ai/cli`, `@google/gemini-cli`, or `opencode-ai`) if missing. The ACP backend itself is the bundled embedded `acpx/runtime` package from the `acpx` plugin. +- It sources `~/.profile`, stages the matching CLI auth material into the container, then installs the requested live CLI (`@anthropic-ai/claude-code`, `@openai/codex`, Factory Droid via `https://app.factory.ai/cli`, `@google/gemini-cli`, or `opencode-ai`) if missing. The ACP backend itself is the embedded `acpx/runtime` package from the official `acpx` plugin. - The Droid Docker variant stages `~/.factory` for settings, forwards `FACTORY_API_KEY`, and requires that API key because local Factory OAuth/keyring auth is not portable into the container. It uses ACPX's built-in `droid exec --output-format acp` registry entry. - The OpenCode Docker variant is a strict single-agent regression lane. It writes a temporary `OPENCODE_CONFIG_CONTENT` default model from `OPENCLAW_LIVE_ACP_BIND_OPENCODE_MODEL` (default `opencode/kimi-k2.6`) after sourcing `~/.profile`, and `pnpm test:docker:live-acp-bind:opencode` requires a bound assistant transcript instead of accepting the generic post-bind skip. - Direct `acpx` CLI calls are only a manual/workaround path for comparing behavior outside the Gateway. The Docker ACP bind smoke exercises OpenClaw's embedded `acpx` runtime backend. diff --git a/docs/install/docker.md b/docs/install/docker.md index 20e03099e9e..67233aadbe7 100644 --- a/docs/install/docker.md +++ b/docs/install/docker.md @@ -161,13 +161,13 @@ export OTEL_SERVICE_NAME="openclaw-gateway" ./scripts/docker/setup.sh ``` -The official OpenClaw Docker release image includes the bundled -`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 -variables. +Install the official `@openclaw/diagnostics-otel` plugin in packaged Docker +installs before enabling export. Custom source-built images can still include +the local plugin source with `OPENCLAW_EXTENSIONS=diagnostics-otel`. 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 variables. Prometheus metrics use the already-published Gateway port. Enable the `diagnostics-prometheus` plugin, then scrape: diff --git a/docs/tools/acp-agents-setup.md b/docs/tools/acp-agents-setup.md index 317d20b0e48..eeef8e73d12 100644 --- a/docs/tools/acp-agents-setup.md +++ b/docs/tools/acp-agents-setup.md @@ -126,8 +126,15 @@ See [Configuration Reference](/gateway/configuration-reference). ## Plugin setup for acpx backend -Fresh installs ship the bundled `acpx` runtime plugin enabled by default, so ACP -usually works without a manual plugin install step. +Packaged installs use the official `@openclaw/acpx` runtime plugin for ACP. +Install and enable it before using ACP harness sessions: + +```bash +openclaw plugins install @openclaw/acpx +openclaw config set plugins.entries.acpx.enabled true +``` + +Source checkouts can also use the local workspace plugin after `pnpm install`. Start with: @@ -136,10 +143,10 @@ Start with: ``` If you disabled `acpx`, denied it via `plugins.allow` / `plugins.deny`, or want -to switch to a local development checkout, use the explicit plugin path: +to switch back to the packaged plugin, use the explicit package path: ```bash -openclaw plugins install acpx +openclaw plugins install @openclaw/acpx openclaw config set plugins.entries.acpx.enabled true ``` @@ -157,7 +164,7 @@ Then verify backend health: ### acpx command and version configuration -By default, the bundled `acpx` plugin registers the embedded ACP backend without +By default, the `acpx` plugin registers the embedded ACP backend without spawning an ACP agent during Gateway startup. Run `/acp doctor` for an explicit live probe. Set `OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE=1` only when you need the Gateway to probe the configured agent at startup. @@ -243,7 +250,7 @@ What this does: ### Runtime timeout configuration -The bundled `acpx` plugin defaults embedded runtime turns to a 120-second +The `acpx` plugin defaults embedded runtime turns to a 120-second timeout. This gives slower harnesses such as Gemini CLI enough time to complete ACP startup and initialization. Override it if your host needs a different runtime limit: diff --git a/docs/tools/acp-agents.md b/docs/tools/acp-agents.md index 2076dcc098d..af01ba89b7b 100644 --- a/docs/tools/acp-agents.md +++ b/docs/tools/acp-agents.md @@ -39,10 +39,15 @@ directly to existing OpenClaw channel conversations, use ## Does this work out of the box? -Usually yes. Fresh installs ship the bundled `acpx` runtime plugin enabled -by default with a plugin-local pinned `acpx` binary that OpenClaw probes -and self-repairs immediately after the Gateway HTTP listener is live. Run -`/acp doctor` for a readiness check. +Yes, after installing the official ACP runtime plugin: + +```bash +openclaw plugins install @openclaw/acpx +openclaw config set plugins.entries.acpx.enabled true +``` + +Source checkouts can use the local `extensions/acpx` workspace plugin after +`pnpm install`. Run `/acp doctor` for a readiness check. OpenClaw only teaches agents about ACP spawning when ACP is **truly usable**: ACP must be enabled, dispatch must not be disabled, the current @@ -53,8 +58,8 @@ an unavailable backend. - - If `plugins.allow` is set, it is a restrictive plugin inventory and **must** include `acpx`; otherwise the bundled default is intentionally blocked and `/acp doctor` reports the missing allowlist entry. - - The bundled Codex ACP adapter is staged with the `acpx` plugin and launched locally when possible. + - If `plugins.allow` is set, it is a restrictive plugin inventory and **must** include `acpx`; otherwise the installed ACP backend is intentionally blocked and `/acp doctor` reports the missing allowlist entry. + - The Codex ACP adapter is staged with the `acpx` plugin and launched locally when possible. - Other target harness adapters may still be fetched on demand with `npx` the first time you use them. - Vendor auth still has to exist on the host for that harness. - If the host has no npm or network access, first-run adapter fetches fail until caches are pre-warmed or the adapter is installed another way. @@ -86,7 +91,7 @@ should call those tools directly. ## Supported harness targets -With the bundled `acpx` backend, use these harness ids as `/acp spawn ` +With the `acpx` backend, use these harness ids as `/acp spawn ` or `sessions_spawn({ runtime: "acp", agentId: "" })` targets: | Harness id | Typical backend | Notes | @@ -232,7 +237,7 @@ See also [Sub-agents](/tools/subagents). For Claude Code through ACP, the stack is: 1. OpenClaw ACP session control plane. -2. Bundled `acpx` runtime plugin. +2. Official `@openclaw/acpx` runtime plugin. 3. Claude ACP adapter. 4. Claude-side runtime/session machinery. diff --git a/extensions/acpx/AGENTS.md b/extensions/acpx/AGENTS.md index 496ffc43b75..84c291e258f 100644 --- a/extensions/acpx/AGENTS.md +++ b/extensions/acpx/AGENTS.md @@ -4,7 +4,7 @@ This file applies to work under `extensions/acpx/`. ## Purpose -The bundled ACPX extension is a thin OpenClaw wrapper around the published `acpx` package. Keep reusable ACP runtime logic in `openclaw/acpx`, not in this extension. +The ACPX extension is a thin OpenClaw wrapper around the published `acpx` package. Keep reusable ACP runtime logic in `openclaw/acpx`, not in this extension. ## Default Version Policy @@ -30,7 +30,7 @@ Use this flow when OpenClaw needs unreleased ACPX changes before the ACPX versio ## Lockfile Notes - `pnpm-lock.yaml` is the tracked workspace lockfile and must match the ACPX version referenced by `extensions/acpx/package.json`. -- `extensions/acpx/package-lock.json` is useful local install metadata for the bundled plugin package. +- `extensions/acpx/package-lock.json` is useful local install metadata for the plugin package. - If `extensions/acpx/package-lock.json` is gitignored in this repo state, regenerating it is still useful for local verification, but it will not appear in `git status`. ## Local Runtime Validation diff --git a/extensions/acpx/package.json b/extensions/acpx/package.json index 7e60ef7b1d8..e35ba5b5937 100644 --- a/extensions/acpx/package.json +++ b/extensions/acpx/package.json @@ -1,7 +1,11 @@ { "name": "@openclaw/acpx", - "version": "2026.4.25", + "version": "2026.5.1-beta.2", "description": "OpenClaw ACP runtime backend", + "repository": { + "type": "git", + "url": "https://github.com/openclaw/openclaw" + }, "type": "module", "dependencies": { "@agentclientprotocol/claude-agent-acp": "0.31.4", @@ -14,6 +18,21 @@ "openclaw": { "extensions": [ "./index.ts" - ] + ], + "install": { + "npmSpec": "@openclaw/acpx", + "defaultChoice": "npm", + "minHostVersion": ">=2026.4.25" + }, + "compat": { + "pluginApi": ">=2026.4.25" + }, + "build": { + "openclawVersion": "2026.5.1-beta.2" + }, + "release": { + "publishToClawHub": true, + "publishToNpm": true + } } } diff --git a/extensions/acpx/skills/acp-router/SKILL.md b/extensions/acpx/skills/acp-router/SKILL.md index 6d828208876..8ecfcb0d57e 100644 --- a/extensions/acpx/skills/acp-router/SKILL.md +++ b/extensions/acpx/skills/acp-router/SKILL.md @@ -105,7 +105,7 @@ Required behavior when ACP backend is unavailable: 1. Do not immediately ask the user to pick an alternate path. 2. First attempt automatic local repair: - - ensure plugin-local pinned acpx is installed in the bundled ACPX plugin package + - ensure plugin-local pinned acpx is installed in the ACPX plugin package - verify `${ACPX_CMD} --version` 3. After reinstall/repair, restart the gateway and explicitly offer to run that restart for the user. 4. Retry ACP thread spawn once after repair. @@ -231,7 +231,7 @@ If your local Cursor install still exposes ACP as `agent acp`, set that as the ` ### Failure handling - `acpx: command not found`: - - for thread-spawn ACP requests, install plugin-local pinned acpx in the bundled ACPX plugin package immediately + - for thread-spawn ACP requests, install plugin-local pinned acpx in the ACPX plugin package immediately - restart gateway after install and offer to run the restart automatically - then retry once - do not ask for install permission first unless policy explicitly requires it diff --git a/extensions/diagnostics-otel/package.json b/extensions/diagnostics-otel/package.json index d684930357b..68e76cd18ff 100644 --- a/extensions/diagnostics-otel/package.json +++ b/extensions/diagnostics-otel/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/diagnostics-otel", - "version": "2026.5.1-beta.1", + "version": "2026.5.1-beta.2", "description": "OpenClaw diagnostics OpenTelemetry exporter", "repository": { "type": "git", @@ -27,11 +27,16 @@ "extensions": [ "./index.ts" ], + "install": { + "npmSpec": "@openclaw/diagnostics-otel", + "defaultChoice": "npm", + "minHostVersion": ">=2026.4.25" + }, "compat": { "pluginApi": ">=2026.4.25" }, "build": { - "openclawVersion": "2026.5.1-beta.1" + "openclawVersion": "2026.5.1-beta.2" }, "release": { "publishToClawHub": true, diff --git a/extensions/diagnostics-prometheus/package.json b/extensions/diagnostics-prometheus/package.json index 10281caffac..090848811f5 100644 --- a/extensions/diagnostics-prometheus/package.json +++ b/extensions/diagnostics-prometheus/package.json @@ -14,6 +14,11 @@ "extensions": [ "./index.ts" ], + "install": { + "npmSpec": "@openclaw/diagnostics-prometheus", + "defaultChoice": "npm", + "minHostVersion": ">=2026.4.25" + }, "compat": { "pluginApi": ">=2026.4.25" }, diff --git a/extensions/lobster/package.json b/extensions/lobster/package.json index 43f560f4fda..0342f2979cf 100644 --- a/extensions/lobster/package.json +++ b/extensions/lobster/package.json @@ -19,6 +19,11 @@ "extensions": [ "./index.ts" ], + "install": { + "npmSpec": "@openclaw/lobster", + "defaultChoice": "npm", + "minHostVersion": ">=2026.4.25" + }, "compat": { "pluginApi": ">=2026.4.25" }, diff --git a/extensions/voice-call/package.json b/extensions/voice-call/package.json index f95a619005f..a5c58b2d0cb 100644 --- a/extensions/voice-call/package.json +++ b/extensions/voice-call/package.json @@ -29,6 +29,8 @@ "./index.ts" ], "install": { + "npmSpec": "@openclaw/voice-call", + "defaultChoice": "npm", "minHostVersion": ">=2026.4.10" }, "compat": { diff --git a/package.json b/package.json index e85d360837b..09172b31cfc 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "!dist/.runtime-postbuildstamp", "!dist/**/*.map", "!dist/plugin-sdk/.tsbuildinfo", + "!dist/extensions/acpx/**", "!dist/extensions/node_modules/**", "!dist/extensions/*/node_modules/**", "!dist/extensions/bluebubbles/**", diff --git a/scripts/lib/plugin-npm-release.ts b/scripts/lib/plugin-npm-release.ts index 65bac981621..cec24caa965 100644 --- a/scripts/lib/plugin-npm-release.ts +++ b/scripts/lib/plugin-npm-release.ts @@ -19,6 +19,8 @@ export type PluginPackageJson = { openclaw?: { extensions?: string[]; install?: { + defaultChoice?: string; + minHostVersion?: string; npmSpec?: string; }; release?: { @@ -218,6 +220,7 @@ export function collectPublishablePluginPackageErrors( const errors: string[] = []; const packageName = packageJson.name?.trim() ?? ""; const packageVersion = packageJson.version?.trim() ?? ""; + const installNpmSpec = normalizeOptionalString(packageJson.openclaw?.install?.npmSpec); const repositoryUrl = typeof packageJson.repository === "string" ? packageJson.repository.trim() @@ -250,6 +253,9 @@ export function collectPublishablePluginPackageErrors( if (extensions.some((entry) => typeof entry !== "string" || !entry.trim())) { errors.push("openclaw.extensions must contain only non-empty strings."); } + if (!installNpmSpec) { + errors.push("openclaw.install.npmSpec must be a non-empty string for publishable plugins."); + } return errors; } diff --git a/test/plugin-npm-release.test.ts b/test/plugin-npm-release.test.ts index a2db21542e2..62e1ac309a9 100644 --- a/test/plugin-npm-release.test.ts +++ b/test/plugin-npm-release.test.ts @@ -98,6 +98,9 @@ describe("collectPublishablePluginPackageErrors", () => { }, openclaw: { extensions: ["./index.ts"], + install: { + npmSpec: "@openclaw/zalo", + }, release: { publishToNpm: true, }, @@ -118,6 +121,9 @@ describe("collectPublishablePluginPackageErrors", () => { private: true, openclaw: { extensions: [""], + install: { + npmSpec: " ", + }, release: { publishToNpm: true, }, @@ -130,6 +136,7 @@ describe("collectPublishablePluginPackageErrors", () => { `package.json repository.url must be "${OPENCLAW_PLUGIN_NPM_REPOSITORY_URL}" so npm provenance can validate GitHub trusted publishing; found "".`, 'package.json version must match YYYY.M.D, YYYY.M.D-N, or YYYY.M.D-beta.N; found "latest".', "openclaw.extensions must contain only non-empty strings.", + "openclaw.install.npmSpec must be a non-empty string for publishable plugins.", ]); }); @@ -143,6 +150,9 @@ describe("collectPublishablePluginPackageErrors", () => { version: "2026.5.1-beta.1", openclaw: { extensions: ["./index.ts"], + install: { + npmSpec: "@openclaw/twitch", + }, release: { publishToNpm: true, }, @@ -153,6 +163,29 @@ describe("collectPublishablePluginPackageErrors", () => { `package.json repository.url must be "${OPENCLAW_PLUGIN_NPM_REPOSITORY_URL}" so npm provenance can validate GitHub trusted publishing; found "".`, ]); }); + + it("requires npm install metadata for publishable plugins", () => { + expect( + collectPublishablePluginPackageErrors({ + extensionId: "voice-call", + packageDir: bundledPluginRoot("voice-call"), + packageJson: { + name: "@openclaw/voice-call", + version: "2026.5.1-beta.1", + repository: { + type: "git", + url: OPENCLAW_PLUGIN_NPM_REPOSITORY_URL, + }, + openclaw: { + extensions: ["./index.ts"], + release: { + publishToNpm: true, + }, + }, + }, + }), + ).toEqual(["openclaw.install.npmSpec must be a non-empty string for publishable plugins."]); + }); }); describe("collectPublishablePluginPackages", () => { @@ -218,6 +251,9 @@ describe("collectPublishablePluginPackages", () => { }, openclaw: { extensions: ["./index.ts"], + install: { + npmSpec: "@openclaw/demo-plugin", + }, release: { publishToNpm: true, }, @@ -230,6 +266,9 @@ describe("collectPublishablePluginPackages", () => { private: true, openclaw: { extensions: ["./index.ts"], + install: { + npmSpec: "@openclaw/private-plugin", + }, release: { publishToNpm: true, },