mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:00:42 +00:00
fix: repair configured plugin installs (#76129)
Summary: - The PR adds a 2026.5.2 doctor repair pass for actively used configured downloadable plugins, prefers ClawHub ... pm fallback, records installed plugin state, extends upgrade-survivor coverage, and updates docs/changelog. - Reproducibility: yes. Static inspection of current main and the PR head gives a high-confidence reproduction ... d-plugin install pass, while the PR tests the new repair-only path, success stamping, and warning behavior. ClawSweeper fixups: - Included follow-up commit: test: cover configured plugin install update path - Included follow-up commit: test: isolate channel option metadata cache - Included follow-up commit: fix: keep configured plugin repair scoped Validation: - ClawSweeper review passed for headd3519ce42c. - Required merge gates passed before the squash merge. Prepared head SHA:d3519ce42cReview: https://github.com/openclaw/openclaw/pull/76129#issuecomment-4364120658 Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
committed by
GitHub
parent
7b6b6401ce
commit
b63d098e8c
@@ -7,6 +7,7 @@ const SCENARIOS = new Set([
|
||||
"feishu-channel",
|
||||
"bootstrap-persona",
|
||||
"plugin-deps-cleanup",
|
||||
"configured-plugin-installs",
|
||||
"tilde-log-path",
|
||||
"versioned-runtime-deps",
|
||||
]);
|
||||
@@ -210,12 +211,27 @@ function assertConfigSurvived() {
|
||||
const pluginAllow = config.plugins?.allow ?? [];
|
||||
assert(pluginAllow.includes("discord"), "discord plugin allow entry missing");
|
||||
assert(pluginAllow.includes("telegram"), "telegram plugin allow entry missing");
|
||||
assert(pluginAllow.includes("whatsapp"), "whatsapp plugin allow entry missing");
|
||||
if (getScenario() === "configured-plugin-installs") {
|
||||
assert(pluginAllow.includes("matrix"), "matrix plugin allow entry missing");
|
||||
} else {
|
||||
assert(pluginAllow.includes("whatsapp"), "whatsapp plugin allow entry missing");
|
||||
}
|
||||
if (hasCoverage(coverage) && acceptsIntent(coverage, "feishu-channel")) {
|
||||
assert(pluginAllow.includes("feishu"), "feishu plugin allow entry missing");
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCoverage(coverage) && acceptsIntent(coverage, "configured-plugin-installs")) {
|
||||
const pluginAllow = config.plugins?.allow ?? [];
|
||||
assert(pluginAllow.includes("discord"), "configured install discord allow entry missing");
|
||||
assert(pluginAllow.includes("telegram"), "configured install telegram allow entry missing");
|
||||
assert(pluginAllow.includes("matrix"), "configured install matrix allow entry missing");
|
||||
assert(
|
||||
config.plugins?.entries?.matrix?.enabled === true,
|
||||
"configured install matrix entry changed",
|
||||
);
|
||||
}
|
||||
|
||||
if (acceptsIntent(coverage, "discord-channel")) {
|
||||
const discord = config.channels?.discord;
|
||||
assert(discord?.enabled === true, "discord enabled flag changed");
|
||||
@@ -243,7 +259,10 @@ function assertConfigSurvived() {
|
||||
);
|
||||
}
|
||||
|
||||
if (acceptsIntent(coverage, "whatsapp-channel")) {
|
||||
if (
|
||||
acceptsIntent(coverage, "whatsapp-channel") &&
|
||||
getScenario() !== "configured-plugin-installs"
|
||||
) {
|
||||
const whatsapp = config.channels?.whatsapp;
|
||||
assert(whatsapp?.enabled === true, "whatsapp enabled flag changed");
|
||||
const whatsappGroup = whatsapp.groups?.["120363000000000000@g.us"];
|
||||
@@ -257,6 +276,17 @@ function assertConfigSurvived() {
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCoverage(coverage) && acceptsIntent(coverage, "configured-plugin-installs")) {
|
||||
const matrix = config.channels?.matrix;
|
||||
assert(matrix?.enabled === true, "matrix enabled flag changed");
|
||||
assert(matrix?.homeserver === "https://matrix.example.invalid", "matrix homeserver changed");
|
||||
assert(matrix?.userId === "@upgrade-survivor:matrix.example.invalid", "matrix userId changed");
|
||||
assert(
|
||||
!config.channels?.whatsapp,
|
||||
"whatsapp channel config should be absent in matrix scenario",
|
||||
);
|
||||
}
|
||||
|
||||
if (hasCoverage(coverage) && acceptsIntent(coverage, "feishu-channel")) {
|
||||
const feishu = config.channels?.feishu;
|
||||
assert(feishu?.enabled === true, "feishu enabled flag changed");
|
||||
@@ -321,6 +351,45 @@ function assertStateSurvived() {
|
||||
}
|
||||
}
|
||||
|
||||
function readInstalledPluginIndex() {
|
||||
const stateDir = requireEnv("OPENCLAW_STATE_DIR");
|
||||
const file = path.join(stateDir, "plugins", "installs.json");
|
||||
assert(fs.existsSync(file), `installed plugin index missing: ${file}`);
|
||||
return readJson(file);
|
||||
}
|
||||
|
||||
function assertConfiguredPluginInstalls() {
|
||||
const coverage = getCoverage();
|
||||
const stage = process.env.OPENCLAW_UPGRADE_SURVIVOR_ASSERT_STAGE || "survival";
|
||||
if (!hasCoverage(coverage) || !acceptsIntent(coverage, "configured-plugin-installs")) {
|
||||
return;
|
||||
}
|
||||
if (stage === "baseline") {
|
||||
return;
|
||||
}
|
||||
const index = readInstalledPluginIndex();
|
||||
const records = index.installRecords ?? {};
|
||||
const matrix = records.matrix;
|
||||
assert(matrix, "configured external matrix plugin install record missing");
|
||||
assert(
|
||||
matrix.source === "clawhub" || matrix.source === "npm",
|
||||
`configured external matrix plugin installed from unexpected source: ${matrix.source}`,
|
||||
);
|
||||
if (matrix.source === "clawhub") {
|
||||
assert(
|
||||
String(matrix.spec ?? "").startsWith("clawhub:@openclaw/matrix"),
|
||||
"configured external matrix plugin ClawHub spec changed",
|
||||
);
|
||||
} else {
|
||||
assert(
|
||||
String(matrix.spec ?? matrix.resolvedSpec ?? "").startsWith("@openclaw/matrix"),
|
||||
"configured external matrix plugin npm spec changed",
|
||||
);
|
||||
}
|
||||
assert(!records.discord, "internal discord plugin should not be installed externally");
|
||||
assert(!records.telegram, "internal telegram plugin should not be installed externally");
|
||||
}
|
||||
|
||||
function assertStatusJson([file]) {
|
||||
const status = readJson(file);
|
||||
assert(status && typeof status === "object", "gateway status JSON was not an object");
|
||||
@@ -334,6 +403,7 @@ if (command === "seed") {
|
||||
assertConfigSurvived();
|
||||
} else if (command === "assert-state") {
|
||||
assertStateSurvived();
|
||||
assertConfiguredPluginInstalls();
|
||||
} else if (command === "assert-status-json") {
|
||||
assertStatusJson(process.argv.slice(3));
|
||||
} else {
|
||||
|
||||
@@ -113,6 +113,28 @@ const scenarioConfigSteps = new Map([
|
||||
},
|
||||
],
|
||||
],
|
||||
[
|
||||
"configured-plugin-installs",
|
||||
[
|
||||
configSetJsonFile(
|
||||
"plugins-configured-installs",
|
||||
"configured-plugin-installs",
|
||||
"plugins",
|
||||
"plugins-configured-installs.json",
|
||||
),
|
||||
{
|
||||
id: "channels-whatsapp-unset",
|
||||
intent: "configured-plugin-installs",
|
||||
argv: ["config", "unset", "channels.whatsapp"],
|
||||
},
|
||||
configSetJsonFile(
|
||||
"channels-matrix",
|
||||
"configured-plugin-installs",
|
||||
"channels.matrix",
|
||||
"channels-matrix.json",
|
||||
),
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
const recipe = [
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"enabled": true,
|
||||
"homeserver": "https://matrix.example.invalid",
|
||||
"userId": "@upgrade-survivor:matrix.example.invalid",
|
||||
"accessToken": {
|
||||
"source": "env",
|
||||
"provider": "default",
|
||||
"id": "MATRIX_ACCESS_TOKEN"
|
||||
},
|
||||
"dm": {
|
||||
"policy": "allowlist",
|
||||
"allowFrom": ["@driver:matrix.example.invalid"]
|
||||
},
|
||||
"groups": {
|
||||
"!upgrade-survivor:matrix.example.invalid": {
|
||||
"enabled": true,
|
||||
"requireMention": true,
|
||||
"tools": {
|
||||
"allow": ["message_send"],
|
||||
"deny": ["exec"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"enabled": true,
|
||||
"allow": ["discord", "telegram", "matrix"],
|
||||
"entries": {
|
||||
"discord": {
|
||||
"enabled": true
|
||||
},
|
||||
"matrix": {
|
||||
"enabled": true
|
||||
},
|
||||
"telegram": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ 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 MATRIX_ACCESS_TOKEN="upgrade-survivor-matrix-token"
|
||||
|
||||
ARTIFACT_ROOT="$(dirname "${OPENCLAW_UPGRADE_SURVIVOR_SUMMARY_JSON:-/tmp/openclaw-upgrade-survivor-artifacts/summary.json}")"
|
||||
mkdir -p "$ARTIFACT_ROOT"
|
||||
@@ -39,6 +40,8 @@ CURRENT_PHASE="setup"
|
||||
FAILURE_PHASE=""
|
||||
FAILURE_MESSAGE=""
|
||||
gateway_pid=""
|
||||
clawhub_fixture_pid=""
|
||||
configured_plugin_installs_clawhub_fixture_owned=""
|
||||
baseline_spec=""
|
||||
baseline_version=""
|
||||
baseline_version_expected="0"
|
||||
@@ -190,6 +193,10 @@ NODE
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [ -n "${clawhub_fixture_pid:-}" ]; then
|
||||
kill "$clawhub_fixture_pid" 2>/dev/null || true
|
||||
wait "$clawhub_fixture_pid" 2>/dev/null || true
|
||||
fi
|
||||
openclaw_e2e_terminate_gateways "${gateway_pid:-}"
|
||||
}
|
||||
|
||||
@@ -276,6 +283,66 @@ plugin_deps_cleanup_plugin_dirs() {
|
||||
"$(package_root)/extensions/$plugin"
|
||||
}
|
||||
|
||||
configured_plugin_installs_enabled() {
|
||||
[ "$SCENARIO" = "configured-plugin-installs" ]
|
||||
}
|
||||
|
||||
start_configured_plugin_installs_clawhub_fixture() {
|
||||
configured_plugin_installs_enabled || return 0
|
||||
configured_plugin_installs_clawhub_fixture_owned=""
|
||||
if [ -n "${OPENCLAW_CLAWHUB_URL:-}" ] || [ -n "${CLAWHUB_URL:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local port_file="$ARTIFACT_ROOT/clawhub-not-found.port"
|
||||
local requests_file="$ARTIFACT_ROOT/clawhub-not-found-requests.jsonl"
|
||||
rm -f "$port_file" "$requests_file"
|
||||
node - "$port_file" "$requests_file" <<'NODE' &
|
||||
const fs = require("node:fs");
|
||||
const http = require("node:http");
|
||||
const portFile = process.argv[2];
|
||||
const requestsFile = process.argv[3];
|
||||
const server = http.createServer((request, response) => {
|
||||
fs.appendFileSync(
|
||||
requestsFile,
|
||||
`${JSON.stringify({ method: request.method, url: request.url, at: new Date().toISOString() })}\n`,
|
||||
);
|
||||
response.writeHead(404, { "content-type": "application/json" });
|
||||
response.end('{"error":"fixture package not found"}\n');
|
||||
});
|
||||
server.listen(0, "127.0.0.1", () => {
|
||||
fs.writeFileSync(portFile, String(server.address().port));
|
||||
});
|
||||
process.on("SIGTERM", () => server.close(() => process.exit(0)));
|
||||
process.on("SIGINT", () => server.close(() => process.exit(0)));
|
||||
NODE
|
||||
clawhub_fixture_pid="$!"
|
||||
for _ in $(seq 1 100); do
|
||||
if [ -s "$port_file" ]; then
|
||||
export OPENCLAW_CLAWHUB_URL="http://127.0.0.1:$(cat "$port_file")"
|
||||
configured_plugin_installs_clawhub_fixture_owned="1"
|
||||
echo "Configured plugin install scenario using ClawHub 404 fixture: $OPENCLAW_CLAWHUB_URL"
|
||||
return 0
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
echo "timed out starting ClawHub 404 fixture" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
assert_configured_plugin_installs_clawhub_attempted() {
|
||||
configured_plugin_installs_enabled || return 0
|
||||
if [ "${configured_plugin_installs_clawhub_fixture_owned:-}" != "1" ]; then
|
||||
return 0
|
||||
fi
|
||||
local requests_file="$ARTIFACT_ROOT/clawhub-not-found-requests.jsonl"
|
||||
if ! grep -q '/api/v1/packages/%40openclaw%2Fmatrix' "$requests_file" 2>/dev/null; then
|
||||
echo "configured plugin install scenario did not attempt ClawHub for @openclaw/matrix" >&2
|
||||
cat "$requests_file" >&2 2>/dev/null || true
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
legacy_plugin_dependency_probe_paths() {
|
||||
local plugin="$1"
|
||||
local plugin_dir
|
||||
@@ -652,7 +719,7 @@ start_gateway() {
|
||||
|
||||
check_gateway_probes() {
|
||||
healthz_seconds="$(probe_gateway_endpoint /healthz live "$HEALTHZ_JSON")"
|
||||
export OPENCLAW_UPGRADE_SURVIVOR_READYZ_ALLOW_FAILING="discord,telegram,whatsapp,feishu"
|
||||
export OPENCLAW_UPGRADE_SURVIVOR_READYZ_ALLOW_FAILING="discord,telegram,whatsapp,feishu,matrix"
|
||||
readyz_seconds="$(probe_gateway_endpoint /readyz ready "$READYZ_JSON")"
|
||||
unset OPENCLAW_UPGRADE_SURVIVOR_READYZ_ALLOW_FAILING
|
||||
}
|
||||
@@ -691,9 +758,11 @@ phase assert-legacy-plugin-dependency-debris assert_legacy_plugin_dependency_deb
|
||||
phase assert-baseline assert_baseline_state
|
||||
phase seed-legacy-runtime-deps-symlink seed_legacy_runtime_deps_symlink
|
||||
phase resolve-candidate resolve_candidate_version
|
||||
phase configured-plugin-installs-clawhub-fixture start_configured_plugin_installs_clawhub_fixture
|
||||
phase update-candidate update_candidate
|
||||
phase assert-legacy-plugin-dependency-debris-before-doctor assert_legacy_plugin_dependency_debris_before_doctor
|
||||
phase doctor run_doctor
|
||||
phase configured-plugin-installs-clawhub-attempted assert_configured_plugin_installs_clawhub_attempted
|
||||
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
|
||||
phase validate-post-doctor-config validate_post_doctor_config
|
||||
|
||||
Reference in New Issue
Block a user