Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
307
scripts/e2e/lib/bundled-plugin-install-uninstall/probe.mjs
Normal file
307
scripts/e2e/lib/bundled-plugin-install-uninstall/probe.mjs
Normal file
@@ -0,0 +1,307 @@
|
||||
// Probe script for bundled plugin install/uninstall E2E scenarios.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { readPluginInstallRecords } from "../plugin-index-sqlite.mjs";
|
||||
|
||||
const readJson = (file) => JSON.parse(fs.readFileSync(file, "utf8"));
|
||||
const normalizePathForProbe = (value) => String(value ?? "").replace(/\\/g, "/");
|
||||
const bundledRuntimeFragments = (pluginDir) => [
|
||||
`/dist/extensions/${pluginDir}`,
|
||||
`/dist-runtime/extensions/${pluginDir}`,
|
||||
];
|
||||
const bundledRuntimeRootFragments = ["/dist/extensions/", "/dist-runtime/extensions/"];
|
||||
const DEFAULT_PLUGIN_LIST_TIMEOUT_MS = 30_000;
|
||||
const DEFAULT_PLUGIN_LIST_MAX_BUFFER_BYTES = 4 * 1024 * 1024;
|
||||
|
||||
function readIntegerEnv(name, fallback, minimum) {
|
||||
const raw = process.env[name];
|
||||
if (raw == null || raw === "") {
|
||||
return fallback;
|
||||
}
|
||||
const text = raw.trim();
|
||||
if (!/^\d+$/u.test(text)) {
|
||||
throw new Error(`invalid ${name}: ${text}`);
|
||||
}
|
||||
const value = Number(text);
|
||||
if (!Number.isSafeInteger(value) || value < minimum) {
|
||||
throw new Error(`invalid ${name}: ${text}`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function readPositiveIntEnv(name, fallback) {
|
||||
return readIntegerEnv(name, fallback, 1);
|
||||
}
|
||||
|
||||
function readNonNegativeIntEnv(name, fallback) {
|
||||
return readIntegerEnv(name, fallback, 0);
|
||||
}
|
||||
|
||||
function resolveStateDir() {
|
||||
if (process.env.OPENCLAW_STATE_DIR) {
|
||||
return process.env.OPENCLAW_STATE_DIR;
|
||||
}
|
||||
return path.join(process.env.HOME || os.homedir(), ".openclaw");
|
||||
}
|
||||
|
||||
function pathReferencesBundledRuntime(value, pluginDir) {
|
||||
const normalized = normalizePathForProbe(value);
|
||||
return bundledRuntimeFragments(pluginDir).some((fragment) => normalized.includes(fragment));
|
||||
}
|
||||
|
||||
function pathReferencesPackagedBundledRoot(value) {
|
||||
const normalized = normalizePathForProbe(value);
|
||||
return bundledRuntimeRootFragments.some((fragment) => normalized.includes(fragment));
|
||||
}
|
||||
|
||||
function pathsEqualForProbe(actual, expected) {
|
||||
return normalizePathForProbe(actual) === normalizePathForProbe(expected);
|
||||
}
|
||||
|
||||
function resolveOpenClawEntry() {
|
||||
if (process.env.OPENCLAW_ENTRY) {
|
||||
return process.env.OPENCLAW_ENTRY;
|
||||
}
|
||||
for (const entry of ["dist/index.mjs", "dist/index.js"]) {
|
||||
if (fs.existsSync(entry)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
throw new Error("Missing OPENCLAW_ENTRY and dist/index.(m)js");
|
||||
}
|
||||
|
||||
function readPluginsList() {
|
||||
const entry = resolveOpenClawEntry();
|
||||
const timeoutMs = readPositiveIntEnv(
|
||||
"OPENCLAW_BUNDLED_PLUGIN_LIST_TIMEOUT_MS",
|
||||
DEFAULT_PLUGIN_LIST_TIMEOUT_MS,
|
||||
);
|
||||
const result = spawnSync(process.execPath, [entry, "plugins", "list", "--json"], {
|
||||
cwd: process.cwd(),
|
||||
encoding: "utf8",
|
||||
env: process.env,
|
||||
maxBuffer: readPositiveIntEnv(
|
||||
"OPENCLAW_BUNDLED_PLUGIN_LIST_MAX_BUFFER_BYTES",
|
||||
DEFAULT_PLUGIN_LIST_MAX_BUFFER_BYTES,
|
||||
),
|
||||
killSignal: "SIGKILL",
|
||||
timeout: timeoutMs,
|
||||
});
|
||||
if (result.error) {
|
||||
const timedOut = result.error.code === "ETIMEDOUT";
|
||||
throw new Error(
|
||||
timedOut
|
||||
? `Timed out listing packaged bundled plugins after ${timeoutMs}ms`
|
||||
: `Unable to list packaged bundled plugins: ${result.error.message}`,
|
||||
);
|
||||
}
|
||||
if (result.status !== 0) {
|
||||
throw new Error(
|
||||
`Unable to list packaged bundled plugins: ${result.stderr || result.stdout || `exit ${result.status}`}`,
|
||||
);
|
||||
}
|
||||
const payload = parsePluginListOutput(result.stdout);
|
||||
return Array.isArray(payload.plugins) ? payload.plugins : [];
|
||||
}
|
||||
|
||||
function parsePluginListOutput(stdout) {
|
||||
const trimmed = stdout.trim();
|
||||
const parsed = parseJsonValue(trimmed);
|
||||
if (parsed.ok) {
|
||||
return parsed.value;
|
||||
}
|
||||
let lastParsed;
|
||||
for (const line of trimmed.split(/\r?\n/u).toReversed()) {
|
||||
if (!line.trimStart().startsWith("{")) {
|
||||
continue;
|
||||
}
|
||||
const candidate = parseJsonValue(line);
|
||||
if (!candidate.ok) {
|
||||
continue;
|
||||
}
|
||||
lastParsed ??= candidate.value;
|
||||
if (Array.isArray(candidate.value?.plugins)) {
|
||||
return candidate.value;
|
||||
}
|
||||
}
|
||||
if (lastParsed !== undefined) {
|
||||
return lastParsed;
|
||||
}
|
||||
throw new Error(`Unable to parse packaged bundled plugin list JSON: ${trimmed}`);
|
||||
}
|
||||
|
||||
function parseJsonValue(text) {
|
||||
try {
|
||||
return { ok: true, value: JSON.parse(text) };
|
||||
} catch {
|
||||
return { ok: false };
|
||||
}
|
||||
}
|
||||
|
||||
function pluginRequiresConfig(pluginDir) {
|
||||
const manifestPath = path.join(pluginDir, "openclaw.plugin.json");
|
||||
if (!fs.existsSync(manifestPath)) {
|
||||
throw new Error(`missing bundled plugin manifest: ${manifestPath}`);
|
||||
}
|
||||
const manifest = readJson(manifestPath);
|
||||
const required = manifest.configSchema?.required;
|
||||
return Array.isArray(required) && required.some((value) => typeof value === "string");
|
||||
}
|
||||
|
||||
async function loadPackagedBundledEntries() {
|
||||
return readPluginsList()
|
||||
.filter((plugin) => plugin?.origin === "bundled")
|
||||
.map((plugin) => {
|
||||
const id = typeof plugin.id === "string" ? plugin.id.trim() : "";
|
||||
const rootDir = typeof plugin.rootDir === "string" ? plugin.rootDir.trim() : "";
|
||||
const source = typeof plugin.source === "string" ? plugin.source.trim() : "";
|
||||
const pluginDir = rootDir || (source ? path.dirname(source) : "");
|
||||
if (!id || !pluginDir || !pathReferencesPackagedBundledRoot(pluginDir)) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
id,
|
||||
dir: path.basename(pluginDir),
|
||||
rootDir: pluginDir,
|
||||
requiresConfig: pluginRequiresConfig(pluginDir),
|
||||
};
|
||||
})
|
||||
.filter(Boolean)
|
||||
.toSorted((a, b) => a.id.localeCompare(b.id));
|
||||
}
|
||||
|
||||
async function loadManifestEntries() {
|
||||
const explicit = (process.env.OPENCLAW_BUNDLED_PLUGIN_SWEEP_IDS || "")
|
||||
.split(/[,\s]+/u)
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean);
|
||||
const manifestEntries = await loadPackagedBundledEntries();
|
||||
|
||||
if (explicit.length === 0) {
|
||||
return manifestEntries;
|
||||
}
|
||||
const available = manifestEntries.map((entry) => entry.id).join(", ");
|
||||
return explicit.map((lookup) => {
|
||||
const found = manifestEntries.find((entry) => entry.id === lookup || entry.dir === lookup);
|
||||
if (!found) {
|
||||
throw new Error(
|
||||
`OPENCLAW_BUNDLED_PLUGIN_SWEEP_IDS entry is not an installable bundled plugin in this package: ${lookup}. Available: ${available}`,
|
||||
);
|
||||
}
|
||||
return found;
|
||||
});
|
||||
}
|
||||
|
||||
async function selectedManifestEntries() {
|
||||
const allEntries = await loadManifestEntries();
|
||||
const total = readPositiveIntEnv("OPENCLAW_BUNDLED_PLUGIN_SWEEP_TOTAL", 1);
|
||||
const index = readNonNegativeIntEnv("OPENCLAW_BUNDLED_PLUGIN_SWEEP_INDEX", 0);
|
||||
if (index >= total) {
|
||||
throw new Error(
|
||||
`OPENCLAW_BUNDLED_PLUGIN_SWEEP_INDEX must be in [0, ${total - 1}], got ${process.env.OPENCLAW_BUNDLED_PLUGIN_SWEEP_INDEX}`,
|
||||
);
|
||||
}
|
||||
|
||||
const selected = allEntries.filter((_, candidateIndex) => candidateIndex % total === index);
|
||||
if (selected.length === 0) {
|
||||
throw new Error(`No bundled plugin ids selected for shard ${index}/${total}`);
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
function assertInstalled(pluginId, pluginDir, requiresConfig, selectedPluginRoot = "") {
|
||||
const stateDir = resolveStateDir();
|
||||
const configPath = path.join(stateDir, "openclaw.json");
|
||||
const config = readJson(configPath);
|
||||
const records = readPluginInstallRecords({ stateDir, configPath });
|
||||
const record = records[pluginId];
|
||||
if (!record) {
|
||||
throw new Error(`missing install record for ${pluginId}`);
|
||||
}
|
||||
if (record.source !== "path") {
|
||||
throw new Error(
|
||||
`expected bundled install record source=path for ${pluginId}, got ${record.source}`,
|
||||
);
|
||||
}
|
||||
const sourcePath = typeof record.sourcePath === "string" ? record.sourcePath : "";
|
||||
if (!sourcePath) {
|
||||
throw new Error(`unexpected bundled source path for ${pluginId}: ${record.sourcePath}`);
|
||||
}
|
||||
if (selectedPluginRoot && !pathsEqualForProbe(sourcePath, selectedPluginRoot)) {
|
||||
throw new Error(
|
||||
`bundled source path for ${pluginId} did not match selected root: expected ${selectedPluginRoot}, got ${record.sourcePath}`,
|
||||
);
|
||||
}
|
||||
if (!selectedPluginRoot && !pathReferencesBundledRuntime(sourcePath, pluginDir)) {
|
||||
throw new Error(`unexpected bundled source path for ${pluginId}: ${record.sourcePath}`);
|
||||
}
|
||||
if (selectedPluginRoot && !fs.existsSync(sourcePath)) {
|
||||
throw new Error(`bundled source path for ${pluginId} does not exist: ${record.sourcePath}`);
|
||||
}
|
||||
if (!pathsEqualForProbe(record.installPath, record.sourcePath)) {
|
||||
throw new Error(`bundled install path should equal source path for ${pluginId}`);
|
||||
}
|
||||
const paths = config.plugins?.load?.paths || [];
|
||||
if (paths.some((entry) => pathReferencesBundledRuntime(entry, pluginDir))) {
|
||||
throw new Error(`config load paths should not include bundled install path for ${pluginId}`);
|
||||
}
|
||||
if (requiresConfig && config.plugins?.entries?.[pluginId]?.enabled === true) {
|
||||
throw new Error(
|
||||
`plugin requiring config should not be enabled immediately after install for ${pluginId}`,
|
||||
);
|
||||
}
|
||||
if (!requiresConfig && config.plugins?.entries?.[pluginId]?.enabled !== true) {
|
||||
throw new Error(`config entry is not enabled after install for ${pluginId}`);
|
||||
}
|
||||
const allow = config.plugins?.allow || [];
|
||||
if (Array.isArray(allow) && allow.length > 0 && !allow.includes(pluginId)) {
|
||||
throw new Error(`existing allowlist does not include ${pluginId} after install`);
|
||||
}
|
||||
if ((config.plugins?.deny || []).includes(pluginId)) {
|
||||
throw new Error(`denylist contains ${pluginId} after install`);
|
||||
}
|
||||
}
|
||||
|
||||
function assertUninstalled(pluginId, pluginDir) {
|
||||
const stateDir = resolveStateDir();
|
||||
const configPath = path.join(stateDir, "openclaw.json");
|
||||
const config = fs.existsSync(configPath) ? readJson(configPath) : {};
|
||||
const records = readPluginInstallRecords({ stateDir, configPath });
|
||||
if (records[pluginId]) {
|
||||
throw new Error(`install record still present after uninstall for ${pluginId}`);
|
||||
}
|
||||
const paths = config.plugins?.load?.paths || [];
|
||||
if (paths.some((entry) => pathReferencesBundledRuntime(entry, pluginDir))) {
|
||||
throw new Error(`load path still present after uninstall for ${pluginId}`);
|
||||
}
|
||||
if (config.plugins?.entries?.[pluginId]) {
|
||||
throw new Error(`config entry still present after uninstall for ${pluginId}`);
|
||||
}
|
||||
if ((config.plugins?.allow || []).includes(pluginId)) {
|
||||
throw new Error(`allowlist still contains ${pluginId} after uninstall`);
|
||||
}
|
||||
if ((config.plugins?.deny || []).includes(pluginId)) {
|
||||
throw new Error(`denylist still contains ${pluginId} after uninstall`);
|
||||
}
|
||||
const managedPath = path.join(stateDir, "extensions", pluginId);
|
||||
if (fs.existsSync(managedPath)) {
|
||||
throw new Error(
|
||||
`managed install directory unexpectedly exists for bundled plugin ${pluginId}: ${managedPath}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const [command, pluginId, pluginDir, requiresConfig, selectedPluginRoot] = process.argv.slice(2);
|
||||
if (command === "select") {
|
||||
for (const entry of await selectedManifestEntries()) {
|
||||
console.log(`${entry.id}\t${entry.dir}\t${entry.requiresConfig ? "1" : "0"}\t${entry.rootDir}`);
|
||||
}
|
||||
} else if (command === "assert-installed") {
|
||||
assertInstalled(pluginId, pluginDir, requiresConfig === "1", selectedPluginRoot);
|
||||
} else if (command === "assert-uninstalled") {
|
||||
assertUninstalled(pluginId, pluginDir);
|
||||
} else {
|
||||
throw new Error(`Unknown bundled plugin probe command: ${command || "(missing)"}`);
|
||||
}
|
||||
1559
scripts/e2e/lib/bundled-plugin-install-uninstall/runtime-smoke.mjs
Normal file
1559
scripts/e2e/lib/bundled-plugin-install-uninstall/runtime-smoke.mjs
Normal file
File diff suppressed because it is too large
Load Diff
107
scripts/e2e/lib/bundled-plugin-install-uninstall/sweep.sh
Normal file
107
scripts/e2e/lib/bundled-plugin-install-uninstall/sweep.sh
Normal file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source scripts/lib/openclaw-e2e-instance.sh
|
||||
source scripts/lib/docker-e2e-logs.sh
|
||||
|
||||
if [ -f dist/index.mjs ]; then
|
||||
OPENCLAW_ENTRY="dist/index.mjs"
|
||||
elif [ -f dist/index.js ]; then
|
||||
OPENCLAW_ENTRY="dist/index.js"
|
||||
else
|
||||
echo "Missing dist/index.(m)js (build output):"
|
||||
ls -la dist || true
|
||||
exit 1
|
||||
fi
|
||||
export OPENCLAW_ENTRY
|
||||
|
||||
openclaw_e2e_eval_test_state_from_b64 "${OPENCLAW_TEST_STATE_SCRIPT_B64:?missing OPENCLAW_TEST_STATE_SCRIPT_B64}"
|
||||
|
||||
probe="scripts/e2e/lib/bundled-plugin-install-uninstall/probe.mjs"
|
||||
runtime_smoke="scripts/e2e/lib/bundled-plugin-install-uninstall/runtime-smoke.mjs"
|
||||
node "$probe" select > /tmp/bundled-plugin-sweep-ids
|
||||
sweep_command_timeout="${OPENCLAW_BUNDLED_PLUGIN_SWEEP_COMMAND_TIMEOUT:-300s}"
|
||||
|
||||
now_ms() {
|
||||
node -e 'process.stdout.write(String(Date.now()))'
|
||||
}
|
||||
|
||||
run_logged_sweep_command() {
|
||||
local label="$1"
|
||||
local log_file="$2"
|
||||
shift 2
|
||||
if openclaw_e2e_maybe_timeout "$sweep_command_timeout" "$@" >"$log_file" 2>&1; then
|
||||
return 0
|
||||
else
|
||||
local status=$?
|
||||
docker_e2e_print_log "$log_file"
|
||||
if [ "$status" -eq 124 ]; then
|
||||
echo "Bundled plugin sweep command timed out after $sweep_command_timeout: $label" >&2
|
||||
else
|
||||
echo "Bundled plugin sweep command failed with status $status: $label" >&2
|
||||
fi
|
||||
return "$status"
|
||||
fi
|
||||
}
|
||||
|
||||
lifecycle_trace_enabled() {
|
||||
case "${OPENCLAW_PLUGIN_LIFECYCLE_TRACE:-}" in
|
||||
1 | true | TRUE | yes | YES)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
plugin_entries=()
|
||||
while IFS= read -r plugin_entry; do
|
||||
plugin_entries+=("$plugin_entry")
|
||||
done < /tmp/bundled-plugin-sweep-ids
|
||||
selected_labels=()
|
||||
for plugin_entry in "${plugin_entries[@]}"; do
|
||||
IFS=$'\t' read -r plugin_id plugin_dir _requires_config _plugin_root <<<"$plugin_entry"
|
||||
selected_labels+=("${plugin_id}@${plugin_dir}")
|
||||
done
|
||||
echo "Selected ${#plugin_entries[@]} bundled plugins for shard ${OPENCLAW_BUNDLED_PLUGIN_SWEEP_INDEX:-0}/${OPENCLAW_BUNDLED_PLUGIN_SWEEP_TOTAL:-1}: ${selected_labels[*]}"
|
||||
|
||||
plugin_index=0
|
||||
for plugin_entry in "${plugin_entries[@]}"; do
|
||||
IFS=$'\t' read -r plugin_id plugin_dir requires_config plugin_root <<<"$plugin_entry"
|
||||
install_log="/tmp/openclaw-install-${plugin_index}.log"
|
||||
uninstall_log="/tmp/openclaw-uninstall-${plugin_index}.log"
|
||||
plugin_started_at="$(now_ms)"
|
||||
echo "Installing bundled plugin: $plugin_id ($plugin_dir)"
|
||||
run_logged_sweep_command "install $plugin_id" "$install_log" \
|
||||
node "$OPENCLAW_ENTRY" plugins install "$plugin_id"
|
||||
if lifecycle_trace_enabled; then
|
||||
docker_e2e_print_log "$install_log"
|
||||
fi
|
||||
install_finished_at="$(now_ms)"
|
||||
node "$probe" assert-installed "$plugin_id" "$plugin_dir" "$requires_config" "$plugin_root"
|
||||
installed_asserted_at="$(now_ms)"
|
||||
if [[ "${OPENCLAW_BUNDLED_PLUGIN_RUNTIME_SMOKE:-1}" != "0" ]]; then
|
||||
echo "Running bundled plugin runtime smoke: $plugin_id ($plugin_dir)"
|
||||
node "$runtime_smoke" plugin "$plugin_id" "$plugin_dir" "$requires_config" "$plugin_index" "$plugin_root"
|
||||
node "$runtime_smoke" tts-global-disable "$plugin_id" "$plugin_dir" "$requires_config" "$plugin_index" "$plugin_root" ""
|
||||
if [[ "$plugin_id" == "${OPENCLAW_BUNDLED_PLUGIN_TTS_LIVE_PROVIDER:-openai}" ]]; then
|
||||
node "$runtime_smoke" tts-openai-live "$plugin_id" "$plugin_dir" "$requires_config" "$plugin_index"
|
||||
fi
|
||||
fi
|
||||
runtime_finished_at="$(now_ms)"
|
||||
|
||||
echo "Uninstalling bundled plugin: $plugin_id ($plugin_dir)"
|
||||
run_logged_sweep_command "uninstall $plugin_id" "$uninstall_log" \
|
||||
node "$OPENCLAW_ENTRY" plugins uninstall "$plugin_id" --force
|
||||
if lifecycle_trace_enabled; then
|
||||
docker_e2e_print_log "$uninstall_log"
|
||||
fi
|
||||
uninstall_finished_at="$(now_ms)"
|
||||
node "$probe" assert-uninstalled "$plugin_id" "$plugin_dir"
|
||||
uninstalled_asserted_at="$(now_ms)"
|
||||
echo "Bundled plugin lifecycle timing: $plugin_id install_ms=$((install_finished_at - plugin_started_at)) install_assert_ms=$((installed_asserted_at - install_finished_at)) runtime_ms=$((runtime_finished_at - installed_asserted_at)) uninstall_ms=$((uninstall_finished_at - runtime_finished_at)) uninstall_assert_ms=$((uninstalled_asserted_at - uninstall_finished_at)) total_ms=$((uninstalled_asserted_at - plugin_started_at))"
|
||||
plugin_index=$((plugin_index + 1))
|
||||
done
|
||||
|
||||
echo "bundled plugin install/uninstall sweep passed (${#plugin_entries[@]} plugin(s))"
|
||||
Reference in New Issue
Block a user