build(plugins): externalize acpx release packages

This commit is contained in:
Peter Steinberger
2026-05-02 08:47:58 +01:00
parent 10c8b9085a
commit 010f7a58a1
17 changed files with 134 additions and 32 deletions

View File

@@ -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.

View File

@@ -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.

View File

@@ -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: {

View File

@@ -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.

View File

@@ -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:

View File

@@ -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:

View File

@@ -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.
<AccordionGroup>
<Accordion title="First-run gotchas">
- 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 <id>`
With the `acpx` backend, use these harness ids as `/acp spawn <id>`
or `sessions_spawn({ runtime: "acp", agentId: "<id>" })` 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.

View File

@@ -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

View File

@@ -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
}
}
}

View File

@@ -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

View File

@@ -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,

View File

@@ -14,6 +14,11 @@
"extensions": [
"./index.ts"
],
"install": {
"npmSpec": "@openclaw/diagnostics-prometheus",
"defaultChoice": "npm",
"minHostVersion": ">=2026.4.25"
},
"compat": {
"pluginApi": ">=2026.4.25"
},

View File

@@ -19,6 +19,11 @@
"extensions": [
"./index.ts"
],
"install": {
"npmSpec": "@openclaw/lobster",
"defaultChoice": "npm",
"minHostVersion": ">=2026.4.25"
},
"compat": {
"pluginApi": ">=2026.4.25"
},

View File

@@ -29,6 +29,8 @@
"./index.ts"
],
"install": {
"npmSpec": "@openclaw/voice-call",
"defaultChoice": "npm",
"minHostVersion": ">=2026.4.10"
},
"compat": {

View File

@@ -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/**",

View File

@@ -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;
}

View File

@@ -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 "<missing>".`,
'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 "<missing>".`,
]);
});
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,
},