fix(doctor): repair configured missing plugins

Fixes #76872.

Doctor now repairs configured-but-missing official plugins during update/doctor recovery, auto-enables the plugin after a successful repair, and preserves config when the download cannot complete. The plugin auto-enable path also honors disabled web search and only enables configured providers/channels when a manifest declares the matching capability.

Verification:
- git diff --check
- fallback-only Korean i18n check
- focused plugin auto-enable/config/doctor Vitest suite
- Crabbox published upgrade-survivor configured-plugin-installs E2E
- CI green on PR head 67ba8ac002

Co-authored-by: Jack Storment <crazycoder131@gmail.com>
This commit is contained in:
Jack Storment
2026-05-03 23:44:21 +02:00
committed by GitHub
parent 5fa7d3b1a4
commit bdd68a75ea
14 changed files with 620 additions and 15 deletions

View File

@@ -416,6 +416,16 @@ function assertExternalPluginInstall(records, pluginId, packageName) {
}
}
function assertConfiguredPluginAvailable(index, pluginId, packageName) {
const records = index.installRecords ?? {};
const bundled = (index.plugins ?? []).find((plugin) => plugin?.pluginId === pluginId);
if (bundled) {
assert(bundled.enabled !== false, `configured bundled ${pluginId} plugin is disabled`);
return;
}
assertExternalPluginInstall(records, pluginId, packageName);
}
function assertConfiguredPluginInstalls() {
const coverage = getCoverage();
const stage = process.env.OPENCLAW_UPGRADE_SURVIVOR_ASSERT_STAGE || "survival";
@@ -432,7 +442,7 @@ function assertConfiguredPluginInstalls() {
assert(!matrix, "internal matrix plugin should not be installed externally");
assert(bundledMatrix, "configured bundled matrix plugin is missing from the plugin index");
assert(bundledMatrix.enabled !== false, "configured bundled matrix plugin is disabled");
assertExternalPluginInstall(records, "discord", "@openclaw/discord");
assertConfiguredPluginAvailable(index, "brave", "@openclaw/brave-plugin");
assert(!records.telegram, "internal telegram plugin should not be installed externally");
}

View File

@@ -1,7 +1,19 @@
{
"enabled": true,
"allow": ["discord", "telegram", "matrix"],
"allow": ["brave", "discord", "telegram", "matrix"],
"entries": {
"brave": {
"enabled": true,
"config": {
"webSearch": {
"apiKey": {
"source": "env",
"provider": "default",
"id": "BRAVE_API_KEY"
}
}
}
},
"discord": {
"enabled": true
},

View File

@@ -18,6 +18,7 @@ export DISCORD_BOT_TOKEN="upgrade-survivor-discord-token"
export TELEGRAM_BOT_TOKEN="123456:upgrade-survivor-telegram-token"
export FEISHU_APP_SECRET="upgrade-survivor-feishu-secret"
export MATRIX_ACCESS_TOKEN="upgrade-survivor-matrix-token"
export BRAVE_API_KEY="BSA_upgrade_survivor_brave_key"
ARTIFACT_ROOT="$(dirname "${OPENCLAW_UPGRADE_SURVIVOR_SUMMARY_JSON:-/tmp/openclaw-upgrade-survivor-artifacts/summary.json}")"
mkdir -p "$ARTIFACT_ROOT"
@@ -40,6 +41,7 @@ CURRENT_PHASE="setup"
FAILURE_PHASE=""
FAILURE_MESSAGE=""
gateway_pid=""
plugin_registry_pid=""
baseline_spec=""
baseline_version=""
baseline_version_expected="0"
@@ -191,6 +193,9 @@ NODE
}
cleanup() {
if [ -n "${plugin_registry_pid:-}" ]; then
kill "$plugin_registry_pid" >/dev/null 2>&1 || true
fi
openclaw_e2e_terminate_gateways "${gateway_pid:-}"
}
@@ -281,6 +286,92 @@ configured_plugin_installs_enabled() {
[ "$SCENARIO" = "configured-plugin-installs" ]
}
configure_configured_plugin_install_fixture_registry() {
configured_plugin_installs_enabled || return 0
local fixture_root="$ARTIFACT_ROOT/configured-plugin-installs-npm-fixture"
local package_dir="$fixture_root/package"
local tarball="$fixture_root/openclaw-brave-plugin-2026.5.2.tgz"
local port_file="$fixture_root/npm-registry-port"
local log_file="$fixture_root/npm-registry.log"
mkdir -p "$package_dir"
FIXTURE_PACKAGE_DIR="$package_dir" node <<'NODE'
const fs = require("node:fs");
const path = require("node:path");
const root = process.env.FIXTURE_PACKAGE_DIR;
fs.mkdirSync(root, { recursive: true });
fs.writeFileSync(
path.join(root, "package.json"),
`${JSON.stringify(
{
name: "@openclaw/brave-plugin",
version: "2026.5.2",
openclaw: { extensions: ["./index.js"] },
},
null,
2,
)}\n`,
);
fs.writeFileSync(
path.join(root, "openclaw.plugin.json"),
`${JSON.stringify(
{
id: "brave",
activation: { onStartup: false },
providerAuthEnvVars: { brave: ["BRAVE_API_KEY"] },
contracts: { webSearchProviders: ["brave"] },
configSchema: {
type: "object",
additionalProperties: false,
properties: {
webSearch: {
type: "object",
additionalProperties: false,
properties: {
apiKey: { type: ["string", "object"] },
mode: { type: "string", enum: ["web", "llm-context"] },
baseUrl: { type: ["string", "object"] },
},
},
},
},
},
null,
2,
)}\n`,
);
fs.writeFileSync(
path.join(root, "index.js"),
`module.exports = { id: "brave", name: "Brave Fixture", register() {} };\n`,
);
NODE
tar -czf "$tarball" -C "$fixture_root" package
node scripts/e2e/lib/plugins/npm-registry-server.mjs \
"$port_file" \
"@openclaw/brave-plugin" \
"2026.5.2" \
"$tarball" \
>"$log_file" 2>&1 &
plugin_registry_pid="$!"
for _ in $(seq 1 100); do
if [ -s "$port_file" ]; then
export NPM_CONFIG_REGISTRY="http://127.0.0.1:$(cat "$port_file")"
export npm_config_registry="$NPM_CONFIG_REGISTRY"
return 0
fi
if ! kill -0 "$plugin_registry_pid" 2>/dev/null; then
cat "$log_file" >&2 || true
return 1
fi
sleep 0.1
done
cat "$log_file" >&2 || true
echo "Timed out waiting for configured plugin install npm fixture registry." >&2
return 1
}
legacy_plugin_dependency_probe_paths() {
local plugin="$1"
local plugin_dir
@@ -699,6 +790,7 @@ phase seed-legacy-runtime-deps-symlink seed_legacy_runtime_deps_symlink
phase resolve-candidate resolve_candidate_version
phase update-candidate update_candidate
phase assert-legacy-plugin-dependency-debris-before-doctor assert_legacy_plugin_dependency_debris_before_doctor
phase configure-configured-plugin-install-fixture-registry configure_configured_plugin_install_fixture_registry
phase doctor run_doctor
phase assert-legacy-plugin-dependency-debris-cleaned assert_legacy_plugin_dependency_debris_cleaned
phase assert-legacy-runtime-deps-symlink-repaired assert_legacy_runtime_deps_symlink_repaired

View File

@@ -143,13 +143,104 @@ export OPENAI_API_KEY="sk-openclaw-upgrade-survivor"
export DISCORD_BOT_TOKEN="upgrade-survivor-discord-token"
export TELEGRAM_BOT_TOKEN="123456:upgrade-survivor-telegram-token"
export FEISHU_APP_SECRET="upgrade-survivor-feishu-secret"
export BRAVE_API_KEY="BSA_upgrade_survivor_brave_key"
gateway_pid=""
plugin_registry_pid=""
cleanup() {
if [ -n "${plugin_registry_pid:-}" ]; then
kill "$plugin_registry_pid" >/dev/null 2>&1 || true
fi
openclaw_e2e_terminate_gateways "${gateway_pid:-}"
}
trap cleanup EXIT
configure_configured_plugin_install_fixture_registry() {
[ "${OPENCLAW_UPGRADE_SURVIVOR_SCENARIO:-base}" = "configured-plugin-installs" ] || return 0
local fixture_root="$OPENCLAW_UPGRADE_SURVIVOR_ARTIFACT_ROOT/configured-plugin-installs-npm-fixture"
local package_dir="$fixture_root/package"
local tarball="$fixture_root/openclaw-brave-plugin-2026.5.2.tgz"
local port_file="$fixture_root/npm-registry-port"
local log_file="$fixture_root/npm-registry.log"
mkdir -p "$package_dir"
FIXTURE_PACKAGE_DIR="$package_dir" node <<'"'"'NODE'"'"'
const fs = require("node:fs");
const path = require("node:path");
const root = process.env.FIXTURE_PACKAGE_DIR;
fs.mkdirSync(root, { recursive: true });
fs.writeFileSync(
path.join(root, "package.json"),
`${JSON.stringify(
{
name: "@openclaw/brave-plugin",
version: "2026.5.2",
openclaw: { extensions: ["./index.js"] },
},
null,
2,
)}\n`,
);
fs.writeFileSync(
path.join(root, "openclaw.plugin.json"),
`${JSON.stringify(
{
id: "brave",
activation: { onStartup: false },
providerAuthEnvVars: { brave: ["BRAVE_API_KEY"] },
contracts: { webSearchProviders: ["brave"] },
configSchema: {
type: "object",
additionalProperties: false,
properties: {
webSearch: {
type: "object",
additionalProperties: false,
properties: {
apiKey: { type: ["string", "object"] },
mode: { type: "string", enum: ["web", "llm-context"] },
baseUrl: { type: ["string", "object"] },
},
},
},
},
},
null,
2,
)}\n`,
);
fs.writeFileSync(
path.join(root, "index.js"),
`module.exports = { id: "brave", name: "Brave Fixture", register() {} };\n`,
);
NODE
tar -czf "$tarball" -C "$fixture_root" package
node scripts/e2e/lib/plugins/npm-registry-server.mjs \
"$port_file" \
"@openclaw/brave-plugin" \
"2026.5.2" \
"$tarball" \
>"$log_file" 2>&1 &
plugin_registry_pid="$!"
for _ in $(seq 1 100); do
if [ -s "$port_file" ]; then
export NPM_CONFIG_REGISTRY="http://127.0.0.1:$(cat "$port_file")"
export npm_config_registry="$NPM_CONFIG_REGISTRY"
return 0
fi
if ! kill -0 "$plugin_registry_pid" 2>/dev/null; then
cat "$log_file" >&2 || true
return 1
fi
sleep 0.1
done
cat "$log_file" >&2 || true
echo "Timed out waiting for configured plugin install npm fixture registry." >&2
return 1
}
openclaw_e2e_eval_test_state_from_b64 "${OPENCLAW_TEST_STATE_SCRIPT_B64:?missing OPENCLAW_TEST_STATE_SCRIPT_B64}"
node scripts/e2e/lib/upgrade-survivor/assertions.mjs seed
@@ -178,6 +269,7 @@ if [ "$update_status" -ne 0 ]; then
fi
echo "Running non-interactive doctor repair..."
configure_configured_plugin_install_fixture_registry
if ! openclaw doctor --fix --non-interactive >/tmp/openclaw-upgrade-survivor-doctor.log 2>&1; then
echo "openclaw doctor failed" >&2
cat /tmp/openclaw-upgrade-survivor-doctor.log >&2 || true
@@ -220,7 +312,7 @@ node scripts/e2e/lib/upgrade-survivor/probe-gateway.mjs \
--base-url "http://127.0.0.1:$PORT" \
--path /readyz \
--expect ready \
--allow-failing discord,telegram,whatsapp,feishu \
--allow-failing discord,telegram,whatsapp,feishu,matrix \
--out /tmp/openclaw-upgrade-survivor-readyz.json
echo "Checking gateway RPC status..."