mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:20:43 +00:00
fix(bonjour): default LAN discovery on macOS only
Summary: - add manifest-backed platform-specific default enablement for bundled plugins - auto-start Bonjour LAN discovery on macOS hosts only - keep Linux, Windows, and containerized Gateway deployments opt-in while preserving explicit enablement Verification: - pnpm test extensions/bonjour/src/advertiser.test.ts src/plugins/bundled-plugin-metadata.test.ts src/plugins/manifest-registry.test.ts src/plugins/channel-plugin-ids.test.ts - pnpm exec oxfmt --check --threads=1 CHANGELOG.md docs/gateway/bonjour.md docs/gateway/configuration-reference.md docs/gateway/discovery.md docs/gateway/security/index.md docs/plugins/manifest.md extensions/bonjour/openclaw.plugin.json src/plugin-sdk/facade-activation-check.runtime.ts src/plugins/bundled-manifest-contract-plugins.ts src/plugins/bundled-plugin-metadata.test.ts src/plugins/channel-presence-policy.ts src/plugins/default-enablement.ts src/plugins/gateway-startup-plugin-ids.ts src/plugins/installed-plugin-index-record-builder.ts src/plugins/installed-plugin-index-store.ts src/plugins/installed-plugin-index-types.ts src/plugins/installed-plugin-index.ts src/plugins/loader.ts src/plugins/manifest-contract-eligibility.ts src/plugins/manifest-owner-policy.ts src/plugins/manifest-registry-installed.ts src/plugins/manifest-registry.test.ts src/plugins/manifest-registry.ts src/plugins/manifest.ts src/plugins/providers.ts - git diff --check - Testbox: pnpm check:changed via Blacksmith Testbox tbx_01kqqf3f8rbrt8afjtcg0ck7qs Refs #74209
This commit is contained in:
committed by
GitHub
parent
fa98d01aa1
commit
ee6052a169
@@ -40,6 +40,7 @@ Docs: https://docs.openclaw.ai
|
||||
- CLI/message: exit cleanly with a nonzero status when message-command plugin registry loading fails before dispatch, preventing `openclaw-message` children from staying alive after plugin load errors. Fixes #76168.
|
||||
- Gateway/update: recover an installed-but-unloaded macOS LaunchAgent after package updates, rerun Gateway health/version/channel readiness checks, and print restart, reinstall, and rollback guidance before reporting update failure. (#76790) Thanks @jonathanlindsay.
|
||||
- CLI/plugins: explain when a missing plugin command alias belongs to a bundled plugin that is disabled by default, including the `openclaw plugins enable <plugin>` repair command. (#76835)
|
||||
- Gateway/Bonjour: auto-start LAN multicast discovery only on macOS hosts while preserving explicit `openclaw plugins enable bonjour` startup elsewhere, so Linux servers and containers that do not need LAN discovery avoid default mDNS probing and watchdog churn. Refs #74209.
|
||||
- Google Meet: route stateful CLI session commands through the gateway-owned runtime so joined realtime sessions survive after the starting CLI process exits. Fixes #76344. Thanks @coltonharris-wq.
|
||||
- Memory/status: split builtin sqlite-vec store readiness from embedding-provider readiness in `memory status --deep` and `openclaw status`, so local vector-store failures no longer look like provider failures and provider failures no longer hide a healthy local vector store.
|
||||
- CLI/doctor: trust a ready gateway memory probe when CLI-side active memory backend resolution is unavailable, preventing false "No active memory plugin is registered" warnings for healthy runtime setups. Fixes #76792. Thanks @som-686.
|
||||
|
||||
@@ -8,11 +8,12 @@ title: "Bonjour discovery"
|
||||
|
||||
# Bonjour / mDNS discovery
|
||||
|
||||
OpenClaw uses Bonjour (mDNS / DNS‑SD) to discover an active Gateway (WebSocket endpoint).
|
||||
OpenClaw can use Bonjour (mDNS / DNS-SD) to discover an active Gateway (WebSocket endpoint).
|
||||
Multicast `local.` browsing is a **LAN-only convenience**. The bundled `bonjour`
|
||||
plugin owns LAN advertising and is enabled by default. For cross-network discovery,
|
||||
the same beacon can also be published through a configured wide-area DNS-SD domain.
|
||||
Discovery is still best-effort and does **not** replace SSH or Tailnet-based connectivity.
|
||||
plugin owns LAN advertising. It auto-starts on macOS hosts and is opt-in on
|
||||
Linux, Windows, and containerized Gateway deployments. For cross-network discovery, the same
|
||||
beacon can also be published through a configured wide-area DNS-SD domain. Discovery
|
||||
is still best-effort and does **not** replace SSH or Tailnet-based connectivity.
|
||||
|
||||
## Wide-area Bonjour (Unicast DNS-SD) over Tailscale
|
||||
|
||||
@@ -81,8 +82,8 @@ For tailnet‑only setups:
|
||||
## What advertises
|
||||
|
||||
Only the Gateway advertises `_openclaw-gw._tcp`. LAN multicast advertising is
|
||||
provided by the bundled `bonjour` plugin; wide-area DNS-SD publishing remains
|
||||
Gateway-owned.
|
||||
provided by the bundled `bonjour` plugin when the plugin is enabled; wide-area
|
||||
DNS-SD publishing remains Gateway-owned.
|
||||
|
||||
## Service types
|
||||
|
||||
@@ -159,13 +160,30 @@ To capture logs:
|
||||
|
||||
The log includes browser state transitions and result‑set changes.
|
||||
|
||||
## When to enable Bonjour
|
||||
|
||||
Bonjour auto-starts for empty-config Gateway startup on macOS hosts because the
|
||||
local app and nearby iOS/Android nodes commonly rely on same-LAN discovery.
|
||||
|
||||
Enable Bonjour explicitly when same-LAN auto-discovery is useful on Linux,
|
||||
Windows, or another non-macOS host:
|
||||
|
||||
```bash
|
||||
openclaw plugins enable bonjour
|
||||
```
|
||||
|
||||
When enabled, Bonjour uses `discovery.mdns.mode` to decide how much TXT metadata
|
||||
to publish. The default mode is `minimal`; use `full` only when local clients need
|
||||
`cliPath` or `sshPort` hints, and use `off` to suppress LAN multicast without
|
||||
changing plugin enablement.
|
||||
|
||||
## When to disable Bonjour
|
||||
|
||||
Disable Bonjour only when LAN multicast advertising is unavailable or harmful.
|
||||
The common case is a Gateway running behind Docker bridge networking, WSL, or a
|
||||
network policy that drops mDNS multicast. In those environments the Gateway is
|
||||
still reachable through its published URL, SSH, Tailnet, or wide-area DNS-SD,
|
||||
but LAN auto-discovery is not reliable.
|
||||
Leave Bonjour disabled when LAN multicast advertising is unnecessary, unavailable,
|
||||
or harmful. The common cases are non-macOS servers, Docker bridge networking,
|
||||
WSL, or a network policy that drops mDNS multicast. In those environments the
|
||||
Gateway is still reachable through its published URL, SSH, Tailnet, or wide-area
|
||||
DNS-SD, but LAN auto-discovery is not reliable.
|
||||
|
||||
Prefer the existing environment override when the problem is deployment-scoped:
|
||||
|
||||
@@ -177,8 +195,8 @@ That disables LAN multicast advertising without changing plugin configuration.
|
||||
It is safe for Docker images, service files, launch scripts, and one-off
|
||||
debugging because the setting disappears when the environment does.
|
||||
|
||||
Use plugin configuration only when you intentionally want to turn off the
|
||||
bundled LAN discovery plugin for that OpenClaw config:
|
||||
Use plugin configuration when you intentionally want to turn off the bundled LAN
|
||||
discovery plugin for that OpenClaw config:
|
||||
|
||||
```bash
|
||||
openclaw plugins disable bonjour
|
||||
@@ -193,8 +211,8 @@ and the LAN, so advertising from the container rarely makes discovery work.
|
||||
|
||||
Important gotchas:
|
||||
|
||||
- Disabling Bonjour does not stop the Gateway. It only stops LAN multicast
|
||||
advertising.
|
||||
- Bonjour auto-starts on macOS hosts and is opt-in elsewhere. Leaving it
|
||||
disabled does not stop the Gateway; it only skips LAN multicast advertising.
|
||||
- Disabling Bonjour does not change `gateway.bind`; Docker still defaults to
|
||||
`OPENCLAW_GATEWAY_BIND=lan` so the published host port can work.
|
||||
- Disabling Bonjour does not disable wide-area DNS-SD. Use wide-area discovery
|
||||
@@ -226,8 +244,8 @@ If a node no longer auto-discovers the Gateway after Docker setup:
|
||||
- Cross-network clients: Tailnet MagicDNS, Tailnet IP, SSH tunnel, or
|
||||
wide-area DNS-SD
|
||||
|
||||
4. If you deliberately enabled Bonjour in Docker with
|
||||
`OPENCLAW_DISABLE_BONJOUR=0`, test multicast from the host:
|
||||
4. If you deliberately enabled the Bonjour plugin in Docker and forced advertising
|
||||
with `OPENCLAW_DISABLE_BONJOUR=0`, test multicast from the host:
|
||||
|
||||
```bash
|
||||
dns-sd -B _openclaw-gw._tcp local.
|
||||
@@ -261,13 +279,14 @@ sequences (e.g. spaces become `\032`).
|
||||
- This is normal at the protocol level.
|
||||
- UIs should decode for display (iOS uses `BonjourEscapes.decode`).
|
||||
|
||||
## Disabling / configuration
|
||||
## Enabling / disabling / configuration
|
||||
|
||||
- macOS hosts auto-start the bundled LAN discovery plugin by default.
|
||||
- `openclaw plugins enable bonjour` enables the bundled LAN discovery plugin on hosts where it is not default-enabled.
|
||||
- `openclaw plugins disable bonjour` disables LAN multicast advertising by disabling the bundled plugin.
|
||||
- `openclaw plugins enable bonjour` restores the default LAN discovery plugin.
|
||||
- `OPENCLAW_DISABLE_BONJOUR=1` disables LAN multicast advertising without changing plugin config; accepted truthy values are `1`, `true`, `yes`, and `on` (legacy: `OPENCLAW_DISABLE_BONJOUR`).
|
||||
- `OPENCLAW_DISABLE_BONJOUR=0` forces LAN multicast advertising on, including inside detected containers; accepted falsy values are `0`, `false`, `no`, and `off`.
|
||||
- When `OPENCLAW_DISABLE_BONJOUR` is unset, Bonjour advertises on normal hosts and auto-disables inside detected containers.
|
||||
- When the Bonjour plugin is enabled and `OPENCLAW_DISABLE_BONJOUR` is unset, Bonjour advertises on normal hosts and auto-disables inside detected containers.
|
||||
- `gateway.bind` in `~/.openclaw/openclaw.json` controls the Gateway bind mode.
|
||||
- `OPENCLAW_SSH_PORT` overrides the SSH port when `sshPort` is advertised (legacy: `OPENCLAW_SSH_PORT`).
|
||||
- `OPENCLAW_TAILNET_DNS` publishes a MagicDNS hint in TXT when mDNS full mode is enabled (legacy: `OPENCLAW_TAILNET_DNS`).
|
||||
|
||||
@@ -687,8 +687,10 @@ Validation and safety notes:
|
||||
}
|
||||
```
|
||||
|
||||
- `minimal` (default): omit `cliPath` + `sshPort` from TXT records.
|
||||
- `full`: include `cliPath` + `sshPort`.
|
||||
- `minimal` (default when the bundled `bonjour` plugin is enabled): omit `cliPath` + `sshPort` from TXT records.
|
||||
- `full`: include `cliPath` + `sshPort`; LAN multicast advertising still requires the bundled `bonjour` plugin to be enabled.
|
||||
- `off`: suppress LAN multicast advertising without changing plugin enablement.
|
||||
- The bundled `bonjour` plugin auto-starts on macOS hosts and is opt-in on Linux, Windows, and containerized Gateway deployments.
|
||||
- Hostname defaults to the system hostname when it is a valid DNS label, falling back to `openclaw`. Override with `OPENCLAW_MDNS_HOSTNAME`.
|
||||
|
||||
### Wide-area (DNS-SD)
|
||||
|
||||
@@ -54,7 +54,9 @@ same gateway beacon via a configured wide-area DNS-SD domain, so discovery can c
|
||||
|
||||
Target direction:
|
||||
|
||||
- The **gateway** advertises its WS endpoint via Bonjour.
|
||||
- The **gateway** advertises its WS endpoint via Bonjour when the bundled
|
||||
`bonjour` plugin is enabled. The plugin auto-starts on macOS hosts and is
|
||||
opt-in elsewhere.
|
||||
- Clients browse and show a “pick a gateway” list, then store the chosen endpoint.
|
||||
|
||||
Troubleshooting and beacon details: [Bonjour](/gateway/bonjour).
|
||||
@@ -83,12 +85,16 @@ Security notes:
|
||||
- TLS pinning must never allow an advertised `gatewayTlsSha256` to override a previously stored pin.
|
||||
- iOS/Android nodes should require an explicit “trust this fingerprint” confirmation before storing a first-time pin (out-of-band verification) whenever the chosen route is secure/TLS-based.
|
||||
|
||||
Disable/override:
|
||||
Enable/disable/override:
|
||||
|
||||
- `openclaw plugins enable bonjour` enables LAN multicast advertising.
|
||||
- `OPENCLAW_DISABLE_BONJOUR=1` disables advertising.
|
||||
- When `OPENCLAW_DISABLE_BONJOUR` is unset, Bonjour advertises on normal hosts
|
||||
and auto-disables inside detected containers. Use `0` only on host, macvlan,
|
||||
or another mDNS-capable network; use `1` to force-disable.
|
||||
- When the Bonjour plugin is enabled and `OPENCLAW_DISABLE_BONJOUR` is unset,
|
||||
Bonjour advertises on normal hosts and auto-disables inside detected containers.
|
||||
Empty-config macOS Gateway startup enables the plugin automatically; Linux,
|
||||
Windows, and containerized deployments need explicit enablement.
|
||||
Use `0` only on host, macvlan, or another mDNS-capable network; use `1` to
|
||||
force-disable.
|
||||
- `gateway.bind` in `~/.openclaw/openclaw.json` controls the Gateway bind mode.
|
||||
- `OPENCLAW_SSH_PORT` overrides the SSH port advertised when `sshPort` is emitted.
|
||||
- `OPENCLAW_TAILNET_DNS` publishes a `tailnetDns` hint (MagicDNS).
|
||||
|
||||
@@ -796,7 +796,7 @@ setups: SSH + your reverse proxy ports).
|
||||
|
||||
### mDNS/Bonjour discovery
|
||||
|
||||
The Gateway broadcasts its presence via mDNS (`_openclaw-gw._tcp` on port 5353) for local device discovery. In full mode, this includes TXT records that may expose operational details:
|
||||
When the bundled `bonjour` plugin is enabled, the Gateway broadcasts its presence via mDNS (`_openclaw-gw._tcp` on port 5353) for local device discovery. In full mode, this includes TXT records that may expose operational details:
|
||||
|
||||
- `cliPath`: full filesystem path to the CLI binary (reveals username and install location)
|
||||
- `sshPort`: advertises SSH availability on the host
|
||||
@@ -806,7 +806,9 @@ The Gateway broadcasts its presence via mDNS (`_openclaw-gw._tcp` on port 5353)
|
||||
|
||||
**Recommendations:**
|
||||
|
||||
1. **Minimal mode** (default, recommended for exposed gateways): omit sensitive fields from mDNS broadcasts:
|
||||
1. **Keep Bonjour disabled unless LAN discovery is needed.** Bonjour auto-starts on macOS hosts and is opt-in elsewhere; direct Gateway URLs, Tailnet, SSH, or wide-area DNS-SD avoid local multicast.
|
||||
|
||||
2. **Minimal mode** (default when Bonjour is enabled, recommended for exposed gateways): omit sensitive fields from mDNS broadcasts:
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -816,7 +818,7 @@ The Gateway broadcasts its presence via mDNS (`_openclaw-gw._tcp` on port 5353)
|
||||
}
|
||||
```
|
||||
|
||||
2. **Disable entirely** if you don't need local device discovery:
|
||||
3. **Disable mDNS mode** if you want to keep the plugin enabled but suppress local device discovery:
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -826,7 +828,7 @@ The Gateway broadcasts its presence via mDNS (`_openclaw-gw._tcp` on port 5353)
|
||||
}
|
||||
```
|
||||
|
||||
3. **Full mode** (opt-in): include `cliPath` + `sshPort` in TXT records:
|
||||
4. **Full mode** (opt-in): include `cliPath` + `sshPort` in TXT records:
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -836,9 +838,9 @@ The Gateway broadcasts its presence via mDNS (`_openclaw-gw._tcp` on port 5353)
|
||||
}
|
||||
```
|
||||
|
||||
4. **Environment variable** (alternative): set `OPENCLAW_DISABLE_BONJOUR=1` to disable mDNS without config changes.
|
||||
5. **Environment variable** (alternative): set `OPENCLAW_DISABLE_BONJOUR=1` to disable mDNS without config changes.
|
||||
|
||||
In minimal mode, the Gateway still broadcasts enough for device discovery (`role`, `gatewayPort`, `transport`) but omits `cliPath` and `sshPort`. Apps that need CLI path information can fetch it via the authenticated WebSocket connection instead.
|
||||
When Bonjour is enabled in minimal mode, the Gateway broadcasts enough for device discovery (`role`, `gatewayPort`, `transport`) but omits `cliPath` and `sshPort`. Apps that need CLI path information can fetch it via the authenticated WebSocket connection instead.
|
||||
|
||||
### Lock down the Gateway WebSocket (local auth)
|
||||
|
||||
|
||||
@@ -150,6 +150,7 @@ or npm install metadata. Those belong in your plugin code and `package.json`.
|
||||
| `id` | Yes | `string` | Canonical plugin id. This is the id used in `plugins.entries.<id>`. |
|
||||
| `configSchema` | Yes | `object` | Inline JSON Schema for this plugin's config. |
|
||||
| `enabledByDefault` | No | `true` | Marks a bundled plugin as enabled by default. Omit it, or set any non-`true` value, to leave the plugin disabled by default. |
|
||||
| `enabledByDefaultOnPlatforms` | No | `string[]` | Marks a bundled plugin as enabled by default only on the listed Node.js platforms, for example `["darwin"]`. Explicit config still wins. |
|
||||
| `legacyPluginIds` | No | `string[]` | Legacy ids that normalize to this canonical plugin id. |
|
||||
| `autoEnableWhenConfiguredProviders` | No | `string[]` | Provider ids that should auto-enable this plugin when auth, config, or model refs mention them. |
|
||||
| `kind` | No | `"memory"` \| `"context-engine"` | Declares an exclusive plugin kind used by `plugins.slots.*`. |
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"activation": {
|
||||
"onStartup": true
|
||||
},
|
||||
"enabledByDefault": true,
|
||||
"enabledByDefaultOnPlatforms": ["darwin"],
|
||||
"name": "Bonjour Gateway Discovery",
|
||||
"description": "Advertise the local OpenClaw gateway over Bonjour/mDNS.",
|
||||
"configSchema": {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
normalizePluginsConfig,
|
||||
resolveEffectivePluginActivationState,
|
||||
} from "../plugins/config-state.js";
|
||||
import { isPluginEnabledByDefaultForPlatform } from "../plugins/default-enablement.js";
|
||||
import {
|
||||
loadPluginManifestRegistry,
|
||||
type PluginManifestRecord,
|
||||
@@ -31,7 +32,7 @@ const EMPTY_FACADE_BOUNDARY_CONFIG: OpenClawConfig = {};
|
||||
|
||||
export type FacadePluginManifestLike = Pick<
|
||||
PluginManifestRecord,
|
||||
"id" | "origin" | "enabledByDefault" | "rootDir" | "channels"
|
||||
"id" | "origin" | "enabledByDefault" | "enabledByDefaultOnPlatforms" | "rootDir" | "channels"
|
||||
>;
|
||||
|
||||
type FacadeModuleLocation = {
|
||||
@@ -286,7 +287,7 @@ export function evaluateBundledPluginPublicSurfaceAccess(params: {
|
||||
origin: params.manifestRecord.origin,
|
||||
config: params.normalizedPluginsConfig,
|
||||
rootConfig: params.config,
|
||||
enabledByDefault: params.manifestRecord.enabledByDefault,
|
||||
enabledByDefault: isPluginEnabledByDefaultForPlatform(params.manifestRecord),
|
||||
activationSource: params.activationSource,
|
||||
autoEnabledReason: params.autoEnabledReasons[params.manifestRecord.id]?.[0],
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
normalizePluginsConfig,
|
||||
resolveEffectivePluginActivationState,
|
||||
} from "./config-state.js";
|
||||
import { isPluginEnabledByDefaultForPlatform } from "./default-enablement.js";
|
||||
import { loadManifestContractSnapshot } from "./manifest-contract-eligibility.js";
|
||||
import type { PluginManifestContractListKey, PluginManifestRecord } from "./manifest-registry.js";
|
||||
|
||||
@@ -85,7 +86,7 @@ export function resolveEnabledBundledManifestContractPlugins(params: {
|
||||
origin: plugin.origin,
|
||||
config: normalizedPlugins,
|
||||
rootConfig: activation.config,
|
||||
enabledByDefault: plugin.enabledByDefault,
|
||||
enabledByDefault: isPluginEnabledByDefaultForPlatform(plugin),
|
||||
activationSource,
|
||||
}).enabled;
|
||||
});
|
||||
|
||||
@@ -49,7 +49,6 @@ const EXPECTED_BUNDLED_STARTUP_PLUGIN_IDS = [
|
||||
] as const;
|
||||
const EXPECTED_EMPTY_CONFIG_GATEWAY_STARTUP_PLUGIN_IDS = [
|
||||
"acpx",
|
||||
"bonjour",
|
||||
"browser",
|
||||
"device-pair",
|
||||
"file-transfer",
|
||||
@@ -132,6 +131,35 @@ function listRepoBundledPluginManifests() {
|
||||
});
|
||||
}
|
||||
|
||||
function createRepoBundledManifestRegistry(): PluginManifestRegistry {
|
||||
return {
|
||||
plugins: listRepoBundledPluginManifests().map(({ manifest, dirName }) => ({
|
||||
id: manifest.id,
|
||||
name: manifest.name,
|
||||
description: manifest.description,
|
||||
version: manifest.version,
|
||||
enabledByDefault: manifest.enabledByDefault === true ? true : undefined,
|
||||
enabledByDefaultOnPlatforms: manifest.enabledByDefaultOnPlatforms,
|
||||
kind: manifest.kind,
|
||||
channels: manifest.channels ?? [],
|
||||
providers: manifest.providers ?? [],
|
||||
cliBackends: manifest.cliBackends ?? [],
|
||||
syntheticAuthRefs: manifest.syntheticAuthRefs ?? [],
|
||||
nonSecretAuthMarkers: manifest.nonSecretAuthMarkers ?? [],
|
||||
skills: manifest.skills ?? [],
|
||||
origin: "bundled",
|
||||
rootDir: path.join(repoRoot, "extensions", dirName),
|
||||
source: path.join(repoRoot, "extensions", dirName, "index.ts"),
|
||||
manifestPath: path.join(repoRoot, "extensions", dirName, "openclaw.plugin.json"),
|
||||
activation: manifest.activation,
|
||||
setup: manifest.setup,
|
||||
hooks: [],
|
||||
contracts: manifest.contracts,
|
||||
})),
|
||||
diagnostics: [],
|
||||
};
|
||||
}
|
||||
|
||||
function readPackageManifest(pluginDir: string): PackageManifest | undefined {
|
||||
const packagePath = path.join(pluginDir, "package.json");
|
||||
return fs.existsSync(packagePath)
|
||||
@@ -186,6 +214,9 @@ function createInstalledPluginRecordForManifest(
|
||||
origin: record.origin,
|
||||
enabled: record.enabledByDefault === true,
|
||||
...(record.enabledByDefault === true ? { enabledByDefault: true } : {}),
|
||||
...(record.enabledByDefaultOnPlatforms?.length
|
||||
? { enabledByDefaultOnPlatforms: record.enabledByDefaultOnPlatforms }
|
||||
: {}),
|
||||
startup: {
|
||||
sidecar: record.activation?.onStartup === true,
|
||||
memory: hasPluginKind(record, "memory"),
|
||||
@@ -432,31 +463,7 @@ describe("bundled plugin metadata", () => {
|
||||
});
|
||||
|
||||
it("keeps empty-config Gateway startup narrower than declared startup sidecars", () => {
|
||||
const manifestRegistry = {
|
||||
plugins: listRepoBundledPluginManifests().map(({ manifest, dirName }) => ({
|
||||
id: manifest.id,
|
||||
name: manifest.name,
|
||||
description: manifest.description,
|
||||
version: manifest.version,
|
||||
enabledByDefault: manifest.enabledByDefault === true ? true : undefined,
|
||||
kind: manifest.kind,
|
||||
channels: manifest.channels ?? [],
|
||||
providers: manifest.providers ?? [],
|
||||
cliBackends: manifest.cliBackends ?? [],
|
||||
syntheticAuthRefs: manifest.syntheticAuthRefs ?? [],
|
||||
nonSecretAuthMarkers: manifest.nonSecretAuthMarkers ?? [],
|
||||
skills: manifest.skills ?? [],
|
||||
origin: "bundled",
|
||||
rootDir: path.join(repoRoot, "extensions", dirName),
|
||||
source: path.join(repoRoot, "extensions", dirName, "index.ts"),
|
||||
manifestPath: path.join(repoRoot, "extensions", dirName, "openclaw.plugin.json"),
|
||||
activation: manifest.activation,
|
||||
setup: manifest.setup,
|
||||
hooks: [],
|
||||
contracts: manifest.contracts,
|
||||
})),
|
||||
diagnostics: [],
|
||||
} satisfies PluginManifestRegistry;
|
||||
const manifestRegistry = createRepoBundledManifestRegistry();
|
||||
const index = createInstalledPluginIndexForManifests(manifestRegistry);
|
||||
|
||||
expect(
|
||||
@@ -465,10 +472,41 @@ describe("bundled plugin metadata", () => {
|
||||
env: process.env,
|
||||
index,
|
||||
manifestRegistry,
|
||||
platform: "linux",
|
||||
}),
|
||||
).toEqual(EXPECTED_EMPTY_CONFIG_GATEWAY_STARTUP_PLUGIN_IDS);
|
||||
});
|
||||
|
||||
it("auto-starts Bonjour for empty-config macOS Gateway startup", () => {
|
||||
const manifestRegistry = createRepoBundledManifestRegistry();
|
||||
const index = createInstalledPluginIndexForManifests(manifestRegistry);
|
||||
|
||||
expect(
|
||||
resolveGatewayStartupPluginIdsFromRegistry({
|
||||
config: {},
|
||||
env: process.env,
|
||||
index,
|
||||
manifestRegistry,
|
||||
platform: "darwin",
|
||||
}),
|
||||
).toContain("bonjour");
|
||||
});
|
||||
|
||||
it("starts Bonjour when explicitly enabled", () => {
|
||||
const manifestRegistry = createRepoBundledManifestRegistry();
|
||||
const index = createInstalledPluginIndexForManifests(manifestRegistry);
|
||||
|
||||
expect(
|
||||
resolveGatewayStartupPluginIdsFromRegistry({
|
||||
config: { plugins: { entries: { bonjour: { enabled: true } } } },
|
||||
env: process.env,
|
||||
index,
|
||||
manifestRegistry,
|
||||
platform: "linux",
|
||||
}),
|
||||
).toContain("bonjour");
|
||||
});
|
||||
|
||||
it("prefers built generated paths when present and falls back to source paths", () => {
|
||||
const tempRoot = createGeneratedPluginTempRoot("openclaw-bundled-plugin-metadata-");
|
||||
const pluginRoot = path.join(tempRoot, "extensions", "plugin");
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
normalizePluginsConfig,
|
||||
resolveEffectivePluginActivationState,
|
||||
} from "./config-state.js";
|
||||
import { isPluginEnabledByDefaultForPlatform } from "./default-enablement.js";
|
||||
import {
|
||||
hasExplicitManifestOwnerTrust,
|
||||
isActivatedManifestOwner,
|
||||
@@ -280,7 +281,7 @@ function evaluateEffectiveChannelPlugin(params: {
|
||||
origin: params.plugin.origin,
|
||||
config: params.normalizedConfig,
|
||||
rootConfig: params.config,
|
||||
enabledByDefault: params.plugin.enabledByDefault,
|
||||
enabledByDefault: isPluginEnabledByDefaultForPlatform(params.plugin),
|
||||
activationSource: params.activationSource,
|
||||
});
|
||||
return activationState.enabled
|
||||
|
||||
14
src/plugins/default-enablement.ts
Normal file
14
src/plugins/default-enablement.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export type PluginDefaultEnablement = {
|
||||
enabledByDefault?: boolean;
|
||||
enabledByDefaultOnPlatforms?: readonly string[];
|
||||
};
|
||||
|
||||
export function isPluginEnabledByDefaultForPlatform(
|
||||
plugin: PluginDefaultEnablement,
|
||||
platform: NodeJS.Platform = process.platform,
|
||||
): boolean {
|
||||
if (plugin.enabledByDefault === true) {
|
||||
return true;
|
||||
}
|
||||
return plugin.enabledByDefaultOnPlatforms?.includes(platform) === true;
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
|
||||
import { hasExplicitChannelConfig } from "./channel-presence-policy.js";
|
||||
import { collectPluginConfigContractMatches } from "./config-contracts.js";
|
||||
import { resolveEffectivePluginActivationState } from "./config-state.js";
|
||||
import { isPluginEnabledByDefaultForPlatform } from "./default-enablement.js";
|
||||
import {
|
||||
collectConfiguredSpeechProviderIds,
|
||||
normalizeConfiguredSpeechProviderIdForStartup,
|
||||
@@ -218,6 +219,7 @@ function canStartConfiguredSpeechProviderPlugin(params: {
|
||||
rootConfig?: OpenClawConfig;
|
||||
};
|
||||
configuredSpeechProviderIds: ReadonlySet<string>;
|
||||
platform?: NodeJS.Platform;
|
||||
}): boolean {
|
||||
if (
|
||||
!manifestOwnsConfiguredSpeechProvider({
|
||||
@@ -247,7 +249,7 @@ function canStartConfiguredSpeechProviderPlugin(params: {
|
||||
origin: params.plugin.origin,
|
||||
config: params.pluginsConfig,
|
||||
rootConfig: params.config,
|
||||
enabledByDefault: params.plugin.enabledByDefault,
|
||||
enabledByDefault: isPluginEnabledByDefaultForPlatform(params.plugin, params.platform),
|
||||
activationSource: params.activationSource,
|
||||
});
|
||||
return activationState.enabled && activationState.explicitlyEnabled;
|
||||
@@ -315,6 +317,7 @@ function canStartExplicitHookPlugin(params: {
|
||||
rootConfig?: OpenClawConfig;
|
||||
};
|
||||
activationSourcePlugins: NormalizedPluginsConfig;
|
||||
platform?: NodeJS.Platform;
|
||||
}): boolean {
|
||||
const hasHookPolicyIntent = hasExplicitHookPolicyConfig(
|
||||
params.activationSourcePlugins.entries[params.plugin.pluginId],
|
||||
@@ -348,7 +351,7 @@ function canStartExplicitHookPlugin(params: {
|
||||
origin: params.plugin.origin,
|
||||
config: params.pluginsConfig,
|
||||
rootConfig: params.config,
|
||||
enabledByDefault: params.plugin.enabledByDefault,
|
||||
enabledByDefault: isPluginEnabledByDefaultForPlatform(params.plugin, params.platform),
|
||||
activationSource: params.activationSource,
|
||||
});
|
||||
return activationState.enabled && (activationState.explicitlyEnabled || hasHookPolicyIntent);
|
||||
@@ -363,6 +366,7 @@ function canStartConfiguredChannelPlugin(params: {
|
||||
rootConfig?: OpenClawConfig;
|
||||
};
|
||||
manifestLookup: ManifestRegistryLookup;
|
||||
platform?: NodeJS.Platform;
|
||||
}): boolean {
|
||||
if (!params.pluginsConfig.enabled) {
|
||||
return false;
|
||||
@@ -396,7 +400,7 @@ function canStartConfiguredChannelPlugin(params: {
|
||||
origin: params.plugin.origin,
|
||||
config: params.pluginsConfig,
|
||||
rootConfig: params.config,
|
||||
enabledByDefault: params.plugin.enabledByDefault,
|
||||
enabledByDefault: isPluginEnabledByDefaultForPlatform(params.plugin, params.platform),
|
||||
activationSource: params.activationSource,
|
||||
});
|
||||
return activationState.enabled && activationState.explicitlyEnabled;
|
||||
@@ -471,6 +475,7 @@ export function resolveGatewayStartupPluginPlanFromRegistry(params: {
|
||||
env: NodeJS.ProcessEnv;
|
||||
index: PluginRegistrySnapshot;
|
||||
manifestRegistry: PluginManifestRegistry;
|
||||
platform?: NodeJS.Platform;
|
||||
}): GatewayStartupPluginPlan {
|
||||
const channelPluginIds = resolveChannelPluginIdsFromRegistry({
|
||||
manifestRegistry: params.manifestRegistry,
|
||||
@@ -533,6 +538,7 @@ export function resolveGatewayStartupPluginPlanFromRegistry(params: {
|
||||
pluginsConfig,
|
||||
activationSource,
|
||||
manifestLookup,
|
||||
platform: params.platform,
|
||||
});
|
||||
}
|
||||
if (
|
||||
@@ -543,7 +549,7 @@ export function resolveGatewayStartupPluginPlanFromRegistry(params: {
|
||||
origin: plugin.origin,
|
||||
config: pluginsConfig,
|
||||
rootConfig: params.config,
|
||||
enabledByDefault: plugin.enabledByDefault,
|
||||
enabledByDefault: isPluginEnabledByDefaultForPlatform(plugin, params.platform),
|
||||
activationSource,
|
||||
});
|
||||
return activationState.enabled;
|
||||
@@ -567,6 +573,7 @@ export function resolveGatewayStartupPluginPlanFromRegistry(params: {
|
||||
pluginsConfig,
|
||||
activationSource,
|
||||
configuredSpeechProviderIds,
|
||||
platform: params.platform,
|
||||
})
|
||||
) {
|
||||
return true;
|
||||
@@ -579,6 +586,7 @@ export function resolveGatewayStartupPluginPlanFromRegistry(params: {
|
||||
pluginsConfig,
|
||||
activationSource,
|
||||
activationSourcePlugins,
|
||||
platform: params.platform,
|
||||
})
|
||||
) {
|
||||
return true;
|
||||
@@ -599,7 +607,7 @@ export function resolveGatewayStartupPluginPlanFromRegistry(params: {
|
||||
origin: plugin.origin,
|
||||
config: pluginsConfig,
|
||||
rootConfig: params.config,
|
||||
enabledByDefault: plugin.enabledByDefault,
|
||||
enabledByDefault: isPluginEnabledByDefaultForPlatform(plugin, params.platform),
|
||||
activationSource,
|
||||
});
|
||||
if (!activationState.enabled) {
|
||||
@@ -624,6 +632,7 @@ export function resolveGatewayStartupPluginIdsFromRegistry(params: {
|
||||
env: NodeJS.ProcessEnv;
|
||||
index: PluginRegistrySnapshot;
|
||||
manifestRegistry: PluginManifestRegistry;
|
||||
platform?: NodeJS.Platform;
|
||||
}): string[] {
|
||||
return [...resolveGatewayStartupPluginPlanFromRegistry(params).pluginIds];
|
||||
}
|
||||
@@ -635,6 +644,7 @@ export function loadGatewayStartupPluginPlan(params: {
|
||||
env: NodeJS.ProcessEnv;
|
||||
index?: PluginRegistrySnapshot;
|
||||
metadataSnapshot?: PluginMetadataSnapshot;
|
||||
platform?: NodeJS.Platform;
|
||||
}): GatewayStartupPluginPlan {
|
||||
const snapshotConfig = params.activationSourceConfig ?? params.config;
|
||||
const metadataSnapshot =
|
||||
@@ -661,6 +671,7 @@ export function loadGatewayStartupPluginPlan(params: {
|
||||
env: params.env,
|
||||
index: metadataSnapshot.index,
|
||||
manifestRegistry: metadataSnapshot.manifestRegistry,
|
||||
platform: params.platform,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -669,6 +680,7 @@ export function resolveGatewayStartupPluginIds(params: {
|
||||
activationSourceConfig?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env: NodeJS.ProcessEnv;
|
||||
platform?: NodeJS.Platform;
|
||||
}): string[] {
|
||||
return [...loadGatewayStartupPluginPlan(params).pluginIds];
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import path from "node:path";
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import type { PluginCompatCode } from "./compat/registry.js";
|
||||
import { normalizePluginsConfig, resolveEffectiveEnableState } from "./config-state.js";
|
||||
import { isPluginEnabledByDefaultForPlatform } from "./default-enablement.js";
|
||||
import type { PluginCandidate } from "./discovery.js";
|
||||
import type { PluginInstallSourceInfo } from "./install-source-info.js";
|
||||
import { describePluginInstallSource } from "./install-source-info.js";
|
||||
@@ -226,7 +227,7 @@ export function buildInstalledPluginIndexRecords(params: {
|
||||
origin: record.origin,
|
||||
config: normalizedConfig,
|
||||
rootConfig: params.config,
|
||||
enabledByDefault: record.enabledByDefault,
|
||||
enabledByDefault: isPluginEnabledByDefaultForPlatform(record),
|
||||
}).enabled;
|
||||
const indexRecord: InstalledPluginIndexRecord = {
|
||||
pluginId: record.id,
|
||||
@@ -249,6 +250,9 @@ export function buildInstalledPluginIndexRecords(params: {
|
||||
if (record.enabledByDefault === true) {
|
||||
indexRecord.enabledByDefault = true;
|
||||
}
|
||||
if (record.enabledByDefaultOnPlatforms?.length) {
|
||||
indexRecord.enabledByDefaultOnPlatforms = [...record.enabledByDefaultOnPlatforms];
|
||||
}
|
||||
if (record.syntheticAuthRefs?.length) {
|
||||
indexRecord.syntheticAuthRefs = [...record.syntheticAuthRefs];
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { safeParseWithSchema } from "../utils/zod-parse.js";
|
||||
import { resolveCompatibilityHostVersion } from "../version.js";
|
||||
import { normalizePluginsConfig, resolveEffectiveEnableState } from "./config-state.js";
|
||||
import { clearCurrentPluginMetadataSnapshotState } from "./current-plugin-metadata-state.js";
|
||||
import { isPluginEnabledByDefaultForPlatform } from "./default-enablement.js";
|
||||
import { hashJson } from "./installed-plugin-index-hash.js";
|
||||
import { resolveCompatRegistryVersion } from "./installed-plugin-index-policy.js";
|
||||
import {
|
||||
@@ -82,6 +83,7 @@ const InstalledPluginIndexRecordSchema = z.object({
|
||||
origin: z.string(),
|
||||
enabled: z.boolean(),
|
||||
enabledByDefault: z.boolean().optional(),
|
||||
enabledByDefaultOnPlatforms: StringArraySchema.optional(),
|
||||
syntheticAuthRefs: StringArraySchema.optional(),
|
||||
startup: InstalledPluginIndexStartupSchema,
|
||||
compat: z.array(z.string()),
|
||||
@@ -251,7 +253,7 @@ function refreshPersistedPolicyState(
|
||||
origin: plugin.origin,
|
||||
config: normalizedConfig,
|
||||
rootConfig: params.config,
|
||||
enabledByDefault: plugin.enabledByDefault,
|
||||
enabledByDefault: isPluginEnabledByDefaultForPlatform(plugin),
|
||||
}).enabled,
|
||||
})),
|
||||
};
|
||||
|
||||
@@ -101,6 +101,7 @@ export type InstalledPluginIndexRecord = {
|
||||
origin: PluginManifestRecord["origin"];
|
||||
enabled: boolean;
|
||||
enabledByDefault?: boolean;
|
||||
enabledByDefaultOnPlatforms?: readonly string[];
|
||||
syntheticAuthRefs?: readonly string[];
|
||||
startup: InstalledPluginStartupInfo;
|
||||
compat: readonly PluginCompatCode[];
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import { resolveCompatibilityHostVersion } from "../version.js";
|
||||
import { normalizePluginsConfig, resolveEffectivePluginActivationState } from "./config-state.js";
|
||||
import { isPluginEnabledByDefaultForPlatform } from "./default-enablement.js";
|
||||
import { normalizeInstallRecordMap } from "./installed-plugin-index-install-records.js";
|
||||
import {
|
||||
resolveCompatRegistryVersion,
|
||||
@@ -131,7 +132,7 @@ export function isInstalledPluginEnabled(
|
||||
origin: record.origin,
|
||||
config: normalizedConfig,
|
||||
rootConfig: config,
|
||||
enabledByDefault: record.enabledByDefault,
|
||||
enabledByDefault: isPluginEnabledByDefaultForPlatform(record),
|
||||
});
|
||||
return state.enabled && (record.enabled || state.explicitlyEnabled);
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
type PluginActivationConfigSource,
|
||||
type NormalizedPluginsConfig,
|
||||
} from "./config-state.js";
|
||||
import { isPluginEnabledByDefaultForPlatform } from "./default-enablement.js";
|
||||
import { discoverOpenClawPlugins, type PluginCandidate } from "./discovery.js";
|
||||
import { getGlobalHookRunner, initializeGlobalHookRunner } from "./hook-runner-global.js";
|
||||
import { toSafeImportPath } from "./import-specifier.js";
|
||||
@@ -1690,7 +1691,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
origin: candidate.origin,
|
||||
config: normalized,
|
||||
rootConfig: cfg,
|
||||
enabledByDefault: manifestRecord.enabledByDefault,
|
||||
enabledByDefault: isPluginEnabledByDefaultForPlatform(manifestRecord),
|
||||
activationSource,
|
||||
autoEnabledReason: formatAutoEnabledActivationReason(autoEnabledReasons[pluginId]),
|
||||
});
|
||||
@@ -1729,7 +1730,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
origin: candidate.origin,
|
||||
config: normalized,
|
||||
rootConfig: cfg,
|
||||
enabledByDefault: manifestRecord.enabledByDefault,
|
||||
enabledByDefault: isPluginEnabledByDefaultForPlatform(manifestRecord),
|
||||
activationSource,
|
||||
});
|
||||
const entry = normalized.entries[pluginId];
|
||||
@@ -2512,7 +2513,7 @@ export async function loadOpenClawPluginCliRegistry(
|
||||
origin: candidate.origin,
|
||||
config: normalized,
|
||||
rootConfig: cfg,
|
||||
enabledByDefault: manifestRecord.enabledByDefault,
|
||||
enabledByDefault: isPluginEnabledByDefaultForPlatform(manifestRecord),
|
||||
activationSource,
|
||||
autoEnabledReason: formatAutoEnabledActivationReason(autoEnabledReasons[pluginId]),
|
||||
});
|
||||
@@ -2551,7 +2552,7 @@ export async function loadOpenClawPluginCliRegistry(
|
||||
origin: candidate.origin,
|
||||
config: normalized,
|
||||
rootConfig: cfg,
|
||||
enabledByDefault: manifestRecord.enabledByDefault,
|
||||
enabledByDefault: isPluginEnabledByDefaultForPlatform(manifestRecord),
|
||||
activationSource,
|
||||
});
|
||||
const entry = normalized.entries[pluginId];
|
||||
|
||||
@@ -11,7 +11,10 @@ import type {
|
||||
|
||||
export function isManifestPluginAvailableForControlPlane(params: {
|
||||
snapshot: Pick<PluginMetadataSnapshot, "index">;
|
||||
plugin: Pick<PluginManifestRecord, "id" | "origin" | "enabledByDefault">;
|
||||
plugin: Pick<
|
||||
PluginManifestRecord,
|
||||
"id" | "origin" | "enabledByDefault" | "enabledByDefaultOnPlatforms"
|
||||
>;
|
||||
config?: OpenClawConfig;
|
||||
}): boolean {
|
||||
if (params.plugin.origin === "bundled") {
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { normalizePluginsConfig, resolveEffectivePluginActivationState } from "./config-state.js";
|
||||
import { isPluginEnabledByDefaultForPlatform } from "./default-enablement.js";
|
||||
import type { PluginManifestRecord } from "./manifest-registry.js";
|
||||
|
||||
type OwnerPlugin = Pick<PluginManifestRecord, "id" | "origin" | "enabledByDefault">;
|
||||
type OwnerPlugin = Pick<
|
||||
PluginManifestRecord,
|
||||
"id" | "origin" | "enabledByDefault" | "enabledByDefaultOnPlatforms"
|
||||
>;
|
||||
|
||||
type NormalizedPluginsConfig = ReturnType<typeof normalizePluginsConfig>;
|
||||
|
||||
@@ -73,6 +77,6 @@ export function isActivatedManifestOwner(params: {
|
||||
origin: params.plugin.origin,
|
||||
config: params.normalizedConfig,
|
||||
rootConfig: params.rootConfig,
|
||||
enabledByDefault: params.plugin.enabledByDefault,
|
||||
enabledByDefault: isPluginEnabledByDefaultForPlatform(params.plugin),
|
||||
}).activated;
|
||||
}
|
||||
|
||||
@@ -76,6 +76,9 @@ function buildInstalledManifestRegistryIndexKey(index: InstalledPluginIndex) {
|
||||
origin: record.origin,
|
||||
enabled: record.enabled,
|
||||
enabledByDefault: record.enabledByDefault,
|
||||
enabledByDefaultOnPlatforms: record.enabledByDefaultOnPlatforms
|
||||
? [...record.enabledByDefaultOnPlatforms]
|
||||
: undefined,
|
||||
syntheticAuthRefs: record.syntheticAuthRefs,
|
||||
startup: record.startup,
|
||||
compat: record.compat,
|
||||
|
||||
@@ -471,6 +471,7 @@ describe("loadPluginManifestRegistry", () => {
|
||||
writeManifest(dir, {
|
||||
id: "openai",
|
||||
enabledByDefault: true,
|
||||
enabledByDefaultOnPlatforms: ["darwin", "not-a-platform"],
|
||||
providers: ["openai", "openai-codex"],
|
||||
providerAuthEnvVars: {
|
||||
openai: ["OPENAI_API_KEY"],
|
||||
@@ -594,6 +595,7 @@ describe("loadPluginManifestRegistry", () => {
|
||||
"openai-codex": "openai",
|
||||
});
|
||||
expect(registry.plugins[0]?.enabledByDefault).toBe(true);
|
||||
expect(registry.plugins[0]?.enabledByDefaultOnPlatforms).toEqual(["darwin"]);
|
||||
expect(registry.plugins[0]?.providerAuthChoices).toEqual([
|
||||
{
|
||||
provider: "openai",
|
||||
|
||||
@@ -105,6 +105,7 @@ export type PluginManifestRecord = {
|
||||
packageVersion?: string;
|
||||
packageDescription?: string;
|
||||
enabledByDefault?: boolean;
|
||||
enabledByDefaultOnPlatforms?: string[];
|
||||
autoEnableWhenConfiguredProviders?: string[];
|
||||
legacyPluginIds?: string[];
|
||||
format?: PluginFormat;
|
||||
@@ -290,6 +291,7 @@ function buildRecord(params: {
|
||||
packageVersion: params.candidate.packageVersion,
|
||||
packageDescription: params.candidate.packageDescription,
|
||||
enabledByDefault: params.manifest.enabledByDefault === true ? true : undefined,
|
||||
enabledByDefaultOnPlatforms: params.manifest.enabledByDefaultOnPlatforms,
|
||||
autoEnableWhenConfiguredProviders: params.manifest.autoEnableWhenConfiguredProviders,
|
||||
legacyPluginIds: params.manifest.legacyPluginIds,
|
||||
format: params.candidate.format ?? "openclaw",
|
||||
|
||||
@@ -182,6 +182,8 @@ export type PluginManifestActivation = {
|
||||
onCapabilities?: PluginManifestActivationCapability[];
|
||||
};
|
||||
|
||||
export type PluginManifestDefaultPlatform = NodeJS.Platform;
|
||||
|
||||
export type PluginManifestSetupProvider = {
|
||||
/** Provider id surfaced during setup/onboarding. */
|
||||
id: string;
|
||||
@@ -290,6 +292,7 @@ export type PluginManifest = {
|
||||
id: string;
|
||||
configSchema: JsonSchemaObject;
|
||||
enabledByDefault?: boolean;
|
||||
enabledByDefaultOnPlatforms?: PluginManifestDefaultPlatform[];
|
||||
/** Legacy plugin ids that should normalize to this plugin id. */
|
||||
legacyPluginIds?: string[];
|
||||
/** Provider ids that should auto-enable this plugin when referenced in auth/config/models. */
|
||||
@@ -1156,6 +1159,27 @@ function normalizeManifestActivation(value: unknown): PluginManifestActivation |
|
||||
return Object.keys(activation).length > 0 ? activation : undefined;
|
||||
}
|
||||
|
||||
const MANIFEST_DEFAULT_ENABLEMENT_PLATFORMS = new Set<PluginManifestDefaultPlatform>([
|
||||
"aix",
|
||||
"android",
|
||||
"darwin",
|
||||
"freebsd",
|
||||
"haiku",
|
||||
"linux",
|
||||
"openbsd",
|
||||
"sunos",
|
||||
"win32",
|
||||
"cygwin",
|
||||
"netbsd",
|
||||
]);
|
||||
|
||||
function normalizeManifestDefaultPlatforms(value: unknown): PluginManifestDefaultPlatform[] {
|
||||
return normalizeTrimmedStringList(value).filter(
|
||||
(platform): platform is PluginManifestDefaultPlatform =>
|
||||
MANIFEST_DEFAULT_ENABLEMENT_PLATFORMS.has(platform as PluginManifestDefaultPlatform),
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeManifestSetupProviders(
|
||||
value: unknown,
|
||||
): PluginManifestSetupProvider[] | undefined {
|
||||
@@ -1520,6 +1544,9 @@ export function loadPluginManifest(
|
||||
|
||||
const kind = parsePluginKind(raw.kind);
|
||||
const enabledByDefault = raw.enabledByDefault === true;
|
||||
const enabledByDefaultOnPlatforms = normalizeManifestDefaultPlatforms(
|
||||
raw.enabledByDefaultOnPlatforms,
|
||||
);
|
||||
const legacyPluginIds = normalizeTrimmedStringList(raw.legacyPluginIds);
|
||||
const autoEnableWhenConfiguredProviders = normalizeTrimmedStringList(
|
||||
raw.autoEnableWhenConfiguredProviders,
|
||||
@@ -1584,6 +1611,7 @@ export function loadPluginManifest(
|
||||
id,
|
||||
configSchema,
|
||||
...(enabledByDefault ? { enabledByDefault } : {}),
|
||||
...(enabledByDefaultOnPlatforms.length > 0 ? { enabledByDefaultOnPlatforms } : {}),
|
||||
...(legacyPluginIds.length > 0 ? { legacyPluginIds } : {}),
|
||||
...(autoEnableWhenConfiguredProviders.length > 0
|
||||
? { autoEnableWhenConfiguredProviders }
|
||||
|
||||
@@ -2,6 +2,7 @@ import { splitTrailingAuthProfile } from "../agents/model-ref-profile.js";
|
||||
import { normalizeProviderId } from "../agents/provider-id.js";
|
||||
import { withBundledPluginVitestCompat } from "./bundled-compat.js";
|
||||
import { resolveEffectivePluginActivationState } from "./config-state.js";
|
||||
import { isPluginEnabledByDefaultForPlatform } from "./default-enablement.js";
|
||||
import type { PluginLoadOptions } from "./loader.js";
|
||||
import {
|
||||
isActivatedManifestOwner,
|
||||
@@ -106,7 +107,7 @@ function resolveEffectiveRegistryPluginActivation(params: {
|
||||
origin: params.plugin.origin,
|
||||
config: params.normalizedConfig,
|
||||
rootConfig: params.rootConfig,
|
||||
enabledByDefault: params.plugin.enabledByDefault,
|
||||
enabledByDefault: isPluginEnabledByDefaultForPlatform(params.plugin),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -114,7 +115,7 @@ function toManifestOwnerRecord(plugin: PluginRegistryRecord) {
|
||||
return {
|
||||
id: plugin.pluginId,
|
||||
origin: plugin.origin,
|
||||
enabledByDefault: plugin.enabledByDefault,
|
||||
enabledByDefault: isPluginEnabledByDefaultForPlatform(plugin),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user