diff --git a/CHANGELOG.md b/CHANGELOG.md index 185ec841f85..6394829760b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ 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: 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. - Plugins/beta: prepare Brave, Codex, Feishu, Synology Chat, Tlon, and Twitch for `2026.5.1-beta.1` npm and ClawHub publishing. Thanks @vincentkoc. diff --git a/docs/channels/googlechat.md b/docs/channels/googlechat.md index a862ced39de..d3c0ef95421 100644 --- a/docs/channels/googlechat.md +++ b/docs/channels/googlechat.md @@ -5,7 +5,21 @@ read_when: title: "Google Chat" --- -Status: ready for DMs + spaces via Google Chat API webhooks (HTTP only). +Status: downloadable plugin for DMs + spaces via Google Chat API webhooks (HTTP only). + +## Install + +Install Google Chat before configuring the channel: + +```bash +openclaw plugins install @openclaw/googlechat +``` + +Local checkout (when running from a git repo): + +```bash +openclaw plugins install ./path/to/local/googlechat-plugin +``` ## Quick setup (beginner) diff --git a/docs/channels/index.md b/docs/channels/index.md index 4a9de89f339..0fb5d6fa321 100644 --- a/docs/channels/index.md +++ b/docs/channels/index.md @@ -24,12 +24,12 @@ Text is supported everywhere; media and reactions vary by channel. - [BlueBubbles](/channels/bluebubbles) — **Recommended for iMessage**; uses the BlueBubbles macOS server REST API with full feature support (bundled plugin; edit, unsend, effects, reactions, group management — edit currently broken on macOS 26 Tahoe). - [Discord](/channels/discord) — Discord Bot API + Gateway; supports servers, channels, and DMs. - [Feishu](/channels/feishu) — Feishu/Lark bot via WebSocket (bundled plugin). -- [Google Chat](/channels/googlechat) — Google Chat API app via HTTP webhook. +- [Google Chat](/channels/googlechat) — Google Chat API app via HTTP webhook (downloadable plugin). - [iMessage (legacy)](/channels/imessage) — Legacy macOS integration via imsg CLI (deprecated, use BlueBubbles for new setups). - [IRC](/channels/irc) — Classic IRC servers; channels + DMs with pairing/allowlist controls. -- [LINE](/channels/line) — LINE Messaging API bot (bundled plugin). -- [Matrix](/channels/matrix) — Matrix protocol (bundled plugin). -- [Mattermost](/channels/mattermost) — Bot API + WebSocket; channels, groups, DMs (bundled plugin). +- [LINE](/channels/line) — LINE Messaging API bot (downloadable plugin). +- [Matrix](/channels/matrix) — Matrix protocol (downloadable plugin). +- [Mattermost](/channels/mattermost) — Bot API + WebSocket; channels, groups, DMs (downloadable plugin). - [Microsoft Teams](/channels/msteams) — Bot Framework; enterprise support (bundled plugin). - [Nextcloud Talk](/channels/nextcloud-talk) — Self-hosted chat via Nextcloud Talk (bundled plugin). - [Nostr](/channels/nostr) — Decentralized DMs via NIP-04 (bundled plugin). diff --git a/docs/channels/line.md b/docs/channels/line.md index 40a59594b34..64f775394d3 100644 --- a/docs/channels/line.md +++ b/docs/channels/line.md @@ -11,26 +11,18 @@ LINE connects to OpenClaw via the LINE Messaging API. The plugin runs as a webho receiver on the gateway and uses your channel access token + channel secret for authentication. -Status: bundled plugin. Direct messages, group chats, media, locations, Flex +Status: downloadable plugin. Direct messages, group chats, media, locations, Flex messages, template messages, and quick replies are supported. Reactions and threads are not supported. -## Bundled plugin +## Install -LINE ships as a bundled plugin in current OpenClaw releases, so normal -packaged builds do not need a separate install. - -If you are on an older build or a custom install that excludes LINE, install a -current npm package when one is published: +Install LINE before configuring the channel: ```bash openclaw plugins install @openclaw/line ``` -If npm reports the OpenClaw-owned package as deprecated or missing, use a -current packaged OpenClaw build or a local checkout until the npm package train -catches up. - Local checkout (when running from a git repo): ```bash diff --git a/docs/channels/matrix.md b/docs/channels/matrix.md index 65698fd613d..e366cd0108a 100644 --- a/docs/channels/matrix.md +++ b/docs/channels/matrix.md @@ -6,23 +6,17 @@ read_when: title: "Matrix" --- -Matrix is a bundled channel plugin for OpenClaw. +Matrix is a downloadable channel plugin for OpenClaw. It uses the official `matrix-js-sdk` and supports DMs, rooms, threads, media, reactions, polls, location, and E2EE. -## Bundled plugin +## Install -Current packaged OpenClaw releases ship the Matrix plugin in the box. You do not need to install anything; configuring `channels.matrix.*` (see [Setup](#setup)) is what activates it. - -For older builds or custom installs that exclude Matrix, install a current npm -package when one is published: +Install Matrix before configuring the channel: ```bash openclaw plugins install @openclaw/matrix ``` -If npm reports the OpenClaw-owned package as deprecated, use a current packaged -OpenClaw build or a local checkout until a newer npm package is published. - From a local checkout: ```bash diff --git a/docs/channels/mattermost.md b/docs/channels/mattermost.md index da7e93c0f2f..5a148b7b7ad 100644 --- a/docs/channels/mattermost.md +++ b/docs/channels/mattermost.md @@ -7,15 +7,11 @@ title: "Mattermost" sidebarTitle: "Mattermost" --- -Status: bundled plugin (bot token + WebSocket events). Channels, groups, and DMs are supported. Mattermost is a self-hostable team messaging platform; see the official site at [mattermost.com](https://mattermost.com) for product details and downloads. +Status: downloadable plugin (bot token + WebSocket events). Channels, groups, and DMs are supported. Mattermost is a self-hostable team messaging platform; see the official site at [mattermost.com](https://mattermost.com) for product details and downloads. -## Bundled plugin +## Install - -Mattermost ships as a bundled plugin in current OpenClaw releases, so normal packaged builds do not need a separate install. - - -If you are on an older build or a custom install that excludes Mattermost, install a current npm package when one is published: +Install Mattermost before configuring the channel: @@ -30,10 +26,6 @@ If you are on an older build or a custom install that excludes Mattermost, insta -If npm reports the OpenClaw-owned package as deprecated, use a current packaged -OpenClaw build or the local checkout path until a newer npm package is -published. - Details: [Plugins](/tools/plugin) ## Quick setup diff --git a/extensions/googlechat/package.json b/extensions/googlechat/package.json index c4cea3c72a7..f6980997cca 100644 --- a/extensions/googlechat/package.json +++ b/extensions/googlechat/package.json @@ -1,8 +1,11 @@ { "name": "@openclaw/googlechat", - "version": "2026.4.25", - "private": true, + "version": "2026.5.1-beta.2", "description": "OpenClaw Google Chat channel plugin", + "repository": { + "type": "git", + "url": "https://github.com/openclaw/openclaw" + }, "type": "module", "dependencies": { "gaxios": "7.1.4", @@ -70,6 +73,16 @@ "npmSpec": "@openclaw/googlechat", "defaultChoice": "npm", "minHostVersion": ">=2026.4.10" + }, + "compat": { + "pluginApi": ">=2026.4.25" + }, + "build": { + "openclawVersion": "2026.5.1-beta.2" + }, + "release": { + "publishToClawHub": true, + "publishToNpm": true } } } diff --git a/extensions/line/package.json b/extensions/line/package.json index 0a343b67344..5ddecb2857b 100644 --- a/extensions/line/package.json +++ b/extensions/line/package.json @@ -1,8 +1,11 @@ { "name": "@openclaw/line", - "version": "2026.4.25", - "private": true, + "version": "2026.5.1-beta.2", "description": "OpenClaw LINE channel plugin", + "repository": { + "type": "git", + "url": "https://github.com/openclaw/openclaw" + }, "type": "module", "dependencies": { "@line/bot-sdk": "^11.0.0" @@ -40,6 +43,16 @@ "npmSpec": "@openclaw/line", "defaultChoice": "npm", "minHostVersion": ">=2026.4.10" + }, + "compat": { + "pluginApi": ">=2026.4.25" + }, + "build": { + "openclawVersion": "2026.5.1-beta.2" + }, + "release": { + "publishToClawHub": true, + "publishToNpm": true } } } diff --git a/extensions/matrix/package.json b/extensions/matrix/package.json index 52ff337eec3..51088eb3afb 100644 --- a/extensions/matrix/package.json +++ b/extensions/matrix/package.json @@ -1,7 +1,11 @@ { "name": "@openclaw/matrix", - "version": "2026.4.25", + "version": "2026.5.1-beta.2", "description": "OpenClaw Matrix channel plugin", + "repository": { + "type": "git", + "url": "https://github.com/openclaw/openclaw" + }, "type": "module", "dependencies": { "@matrix-org/matrix-sdk-crypto-nodejs": "^0.5.1", @@ -79,6 +83,16 @@ "defaultChoice": "npm", "minHostVersion": ">=2026.4.10", "allowInvalidConfigRecovery": true + }, + "compat": { + "pluginApi": ">=2026.4.25" + }, + "build": { + "openclawVersion": "2026.5.1-beta.2" + }, + "release": { + "publishToClawHub": true, + "publishToNpm": true } } } diff --git a/extensions/mattermost/package.json b/extensions/mattermost/package.json index dc9ddbfbdf1..2e166a168ea 100644 --- a/extensions/mattermost/package.json +++ b/extensions/mattermost/package.json @@ -1,7 +1,11 @@ { "name": "@openclaw/mattermost", - "version": "2026.4.25", + "version": "2026.5.1-beta.2", "description": "OpenClaw Mattermost channel plugin", + "repository": { + "type": "git", + "url": "https://github.com/openclaw/openclaw" + }, "type": "module", "dependencies": { "ws": "^8.20.0" @@ -36,6 +40,16 @@ "npmSpec": "@openclaw/mattermost", "defaultChoice": "npm", "minHostVersion": ">=2026.4.10" + }, + "compat": { + "pluginApi": ">=2026.4.25" + }, + "build": { + "openclawVersion": "2026.5.1-beta.2" + }, + "release": { + "publishToClawHub": true, + "publishToNpm": true } } } diff --git a/package.json b/package.json index 93dacfeca7a..e85d360837b 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,35 @@ "!dist/plugin-sdk/.tsbuildinfo", "!dist/extensions/node_modules/**", "!dist/extensions/*/node_modules/**", + "!dist/extensions/bluebubbles/**", + "!dist/extensions/brave/**", + "!dist/extensions/codex/**", + "!dist/extensions/diagnostics-otel/**", + "!dist/extensions/diagnostics-prometheus/**", + "!dist/extensions/diffs/**", + "!dist/extensions/discord/**", + "!dist/extensions/feishu/**", + "!dist/extensions/google-meet/**", + "!dist/extensions/googlechat/**", + "!dist/extensions/line/**", + "!dist/extensions/lobster/**", + "!dist/extensions/matrix/**", + "!dist/extensions/mattermost/**", + "!dist/extensions/memory-lancedb/**", + "!dist/extensions/msteams/**", + "!dist/extensions/nextcloud-talk/**", + "!dist/extensions/nostr/**", + "!dist/extensions/qqbot/**", "!dist/extensions/qa-channel/**", "!dist/extensions/qa-lab/**", "!dist/extensions/qa-matrix/**", + "!dist/extensions/synology-chat/**", + "!dist/extensions/tlon/**", + "!dist/extensions/twitch/**", + "!dist/extensions/voice-call/**", + "!dist/extensions/whatsapp/**", + "!dist/extensions/zalo/**", + "!dist/extensions/zalouser/**", "!dist/plugin-sdk/extensions/qa-channel/**", "!dist/plugin-sdk/extensions/qa-lab/**", "!dist/plugin-sdk/qa-channel.*", diff --git a/src/infra/package-dist-inventory.test.ts b/src/infra/package-dist-inventory.test.ts index 0d3788eb357..e810e35fa34 100644 --- a/src/infra/package-dist-inventory.test.ts +++ b/src/infra/package-dist-inventory.test.ts @@ -187,6 +187,69 @@ describe("package dist inventory", () => { ); }); + it("keeps publishable externalized bundled plugin dist trees out of the inventory", async () => { + await withTempDir({ prefix: "openclaw-dist-inventory-externalized-" }, async (packageRoot) => { + const externalizedRuntime = path.join( + packageRoot, + "dist", + "extensions", + "external-chat", + "index.js", + ); + const bundledRuntime = path.join( + packageRoot, + "dist", + "extensions", + "bundled-chat", + "index.js", + ); + const externalizedPackageJson = path.join( + packageRoot, + "extensions", + "external-chat", + "package.json", + ); + const bundledPackageJson = path.join( + packageRoot, + "extensions", + "bundled-chat", + "package.json", + ); + + await fs.mkdir(path.dirname(externalizedRuntime), { recursive: true }); + await fs.mkdir(path.dirname(bundledRuntime), { recursive: true }); + await fs.mkdir(path.dirname(externalizedPackageJson), { recursive: true }); + await fs.mkdir(path.dirname(bundledPackageJson), { recursive: true }); + await fs.writeFile(externalizedRuntime, "export {};\n", "utf8"); + await fs.writeFile(bundledRuntime, "export {};\n", "utf8"); + await fs.writeFile( + externalizedPackageJson, + JSON.stringify({ + name: "@openclaw/external-chat", + openclaw: { + release: { + publishToClawHub: true, + publishToNpm: true, + }, + }, + }), + "utf8", + ); + await fs.writeFile( + bundledPackageJson, + JSON.stringify({ + name: "@openclaw/bundled-chat", + openclaw: {}, + }), + "utf8", + ); + + await expect(writePackageDistInventory(packageRoot)).resolves.toEqual([ + "dist/extensions/bundled-chat/index.js", + ]); + }); + }); + it("reports runtime-created install staging dirs during installed dist verification", async () => { await withTempDir({ prefix: "openclaw-dist-inventory-stage-" }, async (packageRoot) => { const realFile = path.join(packageRoot, "dist", "real-AbC123.js"); diff --git a/src/infra/package-dist-inventory.ts b/src/infra/package-dist-inventory.ts index 91130a3e9b3..ef0c0d3b2e9 100644 --- a/src/infra/package-dist-inventory.ts +++ b/src/infra/package-dist-inventory.ts @@ -39,6 +39,7 @@ const OMITTED_DIST_SUBTREE_PATTERNS = [ new RegExp(`^dist/plugin-sdk/extensions/${LEGACY_QA_LAB_DIR}(?:/|$)`, "u"), ] as const; const INSTALL_STAGE_DEBRIS_DIR_PATTERN = /^\.openclaw-install-stage(?:-[^/]+)?$/iu; +type ExternalizedBundledExtensionIds = ReadonlySet; function normalizeRelativePath(value: string): string { return value.replace(/\\/g, "/"); @@ -74,10 +75,86 @@ export function isLegacyPluginDependencyInstallStagePath(relativePath: string): ); } -function isPackagedDistPath(relativePath: string): boolean { +function isExternalizedBundledExtensionDistPath( + relativePath: string, + externalizedExtensionIds: ExternalizedBundledExtensionIds, +): boolean { + if (externalizedExtensionIds.size === 0) { + return false; + } + const parts = normalizeRelativePath(relativePath).split("/"); + return ( + parts.length >= 3 && + parts[0] === "dist" && + parts[1] === "extensions" && + Boolean(parts[2]) && + externalizedExtensionIds.has(parts[2] ?? "") + ); +} + +function isPublishableExternalizedBundledManifest(value: unknown): boolean { + if (!value || typeof value !== "object") { + return false; + } + const openclaw = (value as { openclaw?: unknown }).openclaw; + if (!openclaw || typeof openclaw !== "object") { + return false; + } + const release = (openclaw as { release?: unknown }).release; + if (!release || typeof release !== "object") { + return false; + } + const typedRelease = release as { publishToClawHub?: unknown; publishToNpm?: unknown }; + return typedRelease.publishToNpm === true || typedRelease.publishToClawHub === true; +} + +async function collectExternalizedBundledExtensionIds( + packageRoot: string, +): Promise { + const extensionsDir = path.join(packageRoot, "extensions"); + let entries: import("node:fs").Dirent[]; + try { + entries = await fs.readdir(extensionsDir, { withFileTypes: true }); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") { + return new Set(); + } + throw error; + } + + const ids = new Set(); + await Promise.all( + entries.map(async (entry) => { + if (!entry.isDirectory()) { + return; + } + const packageJsonPath = path.join(extensionsDir, entry.name, "package.json"); + try { + const parsed = JSON.parse(await fs.readFile(packageJsonPath, "utf8")) as unknown; + if (isPublishableExternalizedBundledManifest(parsed)) { + ids.add(entry.name); + } + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") { + return; + } + throw error; + } + }), + ); + return ids; +} + +function isPackagedDistPath( + relativePath: string, + externalizedExtensionIds: ExternalizedBundledExtensionIds, +): boolean { if (!relativePath.startsWith("dist/")) { return false; } + if (isExternalizedBundledExtensionDistPath(relativePath, externalizedExtensionIds)) { + return false; + } if (isLegacyPluginDependencyDirPath(relativePath)) { return false; } @@ -106,16 +183,24 @@ function isPackagedDistPath(relativePath: string): boolean { return true; } -function isOmittedDistSubtree(relativePath: string): boolean { +function isOmittedDistSubtree( + relativePath: string, + externalizedExtensionIds: ExternalizedBundledExtensionIds, +): boolean { return ( + isExternalizedBundledExtensionDistPath(relativePath, externalizedExtensionIds) || isLegacyPluginDependencyDirPath(relativePath) || OMITTED_DIST_SUBTREE_PATTERNS.some((pattern) => pattern.test(relativePath)) ); } -async function collectRelativeFiles(rootDir: string, baseDir: string): Promise { +async function collectRelativeFiles( + rootDir: string, + baseDir: string, + externalizedExtensionIds: ExternalizedBundledExtensionIds, +): Promise { const rootRelativePath = normalizeRelativePath(path.relative(baseDir, rootDir)); - if (rootRelativePath && isOmittedDistSubtree(rootRelativePath)) { + if (rootRelativePath && isOmittedDistSubtree(rootRelativePath, externalizedExtensionIds)) { return []; } try { @@ -134,10 +219,10 @@ async function collectRelativeFiles(rootDir: string, baseDir: string): Promise { - return await collectRelativeFiles(path.join(packageRoot, "dist"), packageRoot); + const externalizedExtensionIds = await collectExternalizedBundledExtensionIds(packageRoot); + return await collectRelativeFiles( + path.join(packageRoot, "dist"), + packageRoot, + externalizedExtensionIds, + ); } export async function collectLegacyPluginDependencyStagingDebrisPaths( diff --git a/test/plugin-npm-release.test.ts b/test/plugin-npm-release.test.ts index f2e49909fab..a2db21542e2 100644 --- a/test/plugin-npm-release.test.ts +++ b/test/plugin-npm-release.test.ts @@ -1,7 +1,8 @@ -import { mkdirSync } from "node:fs"; +import { mkdirSync, readFileSync } from "node:fs"; import { join } from "node:path"; import { bundledPluginFile, bundledPluginRoot } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it } from "vitest"; +import { collectClawHubPublishablePluginPackages } from "../scripts/lib/plugin-clawhub-release.ts"; import { collectPublishablePluginPackages, collectChangedExtensionIdsFromPaths, @@ -155,6 +156,22 @@ describe("collectPublishablePluginPackageErrors", () => { }); describe("collectPublishablePluginPackages", () => { + it("keeps publishable plugin dist trees out of the core npm package files list", () => { + const rootPackage = JSON.parse(readFileSync("package.json", "utf8")) as { + files?: unknown; + }; + const packageFiles = new Set(Array.isArray(rootPackage.files) ? rootPackage.files : []); + const publishablePlugins = [ + ...collectPublishablePluginPackages(), + ...collectClawHubPublishablePluginPackages(), + ]; + const missingExclusions = Array.from( + new Set(publishablePlugins.map((plugin) => `!dist/extensions/${plugin.extensionId}/**`)), + ).filter((entry) => !packageFiles.has(entry)); + + expect(missingExclusions).toEqual([]); + }); + it("collects publishable npm plugins from extension package manifests", () => { const repoDir = makeTempRepoRoot(tempDirs, "openclaw-plugin-npm-release-"); mkdirSync(join(repoDir, "extensions", "demo-plugin"), { recursive: true });