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

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:
2026-07-05 09:36:54 +00:00
parent 3216769225
commit bedb527145
21108 changed files with 6010766 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
run_plugins_clawhub_scenario() {
if [ "${OPENCLAW_PLUGINS_E2E_CLAWHUB:-1}" = "0" ]; then
echo "Skipping ClawHub plugin install and uninstall (OPENCLAW_PLUGINS_E2E_CLAWHUB=0)."
else
echo "Testing ClawHub plugin install and uninstall..."
CLAWHUB_PLUGIN_SPEC="${OPENCLAW_PLUGINS_E2E_CLAWHUB_SPEC:-clawhub:@openclaw/kitchen-sink}"
CLAWHUB_PLUGIN_ID="${OPENCLAW_PLUGINS_E2E_CLAWHUB_ID:-openclaw-kitchen-sink-fixture}"
export CLAWHUB_PLUGIN_SPEC CLAWHUB_PLUGIN_ID
start_clawhub_fixture_server() {
local fixture_dir="$1"
local server_log="$fixture_dir/clawhub-fixture.log"
local server_port_file="$fixture_dir/clawhub-fixture-port"
local server_pid_file="$fixture_dir/clawhub-fixture-pid"
openclaw_plugins_validate_fixture_log_print_bytes || return $?
node scripts/e2e/lib/clawhub-fixture-server.cjs plugins "$server_port_file" >"$server_log" 2>&1 &
local server_pid="$!"
echo "$server_pid" >"$server_pid_file"
openclaw_plugins_register_fixture_pid_file "$server_pid_file"
for _ in $(seq 1 100); do
if [[ -s "$server_port_file" ]]; then
export OPENCLAW_CLAWHUB_URL="http://127.0.0.1:$(cat "$server_port_file")"
return 0
fi
if ! kill -0 "$server_pid" 2>/dev/null; then
openclaw_plugins_print_fixture_log "$server_log"
return 1
fi
sleep 0.1
done
openclaw_plugins_print_fixture_log "$server_log"
echo "Timed out waiting for ClawHub fixture server." >&2
return 1
}
if [[ "${OPENCLAW_PLUGINS_E2E_LIVE_CLAWHUB:-0}" = "1" ]]; then
export OPENCLAW_CLAWHUB_URL="${OPENCLAW_CLAWHUB_URL:-${CLAWHUB_URL:-https://clawhub.ai}}"
export NPM_CONFIG_REGISTRY="${OPENCLAW_PLUGINS_E2E_LIVE_NPM_REGISTRY:-https://registry.npmjs.org/}"
else
# Keep the release-path smoke hermetic; live ClawHub can rate-limit CI.
if [[ -n "${OPENCLAW_CLAWHUB_URL:-}" || -n "${CLAWHUB_URL:-}" ]]; then
echo "Ignoring ambient ClawHub URL for fixture-mode plugin E2E; set OPENCLAW_PLUGINS_E2E_LIVE_CLAWHUB=1 for live ClawHub."
fi
unset OPENCLAW_CLAWHUB_URL CLAWHUB_URL
clawhub_fixture_dir="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-clawhub-fixture.XXXXXX")"
local fixture_status=0
start_clawhub_fixture_server "$clawhub_fixture_dir" || fixture_status="$?"
if [[ "$fixture_status" -ne 0 ]]; then
return "$fixture_status"
fi
fi
node scripts/e2e/lib/plugins/assertions.mjs clawhub-preflight
run_plugins_openclaw_logged install-clawhub plugins install "$CLAWHUB_PLUGIN_SPEC"
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-clawhub-installed.json" plugins list --json
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-clawhub-inspect.json" plugins inspect "$CLAWHUB_PLUGIN_ID" --json
node scripts/e2e/lib/plugins/assertions.mjs clawhub-installed
openclaw_e2e_maybe_timeout "$OPENCLAW_PLUGINS_CLI_TIMEOUT" node "$OPENCLAW_ENTRY" plugins update "$CLAWHUB_PLUGIN_ID" >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-clawhub-update.log" 2>&1
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-clawhub-updated.json" plugins list --json
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-clawhub-updated-inspect.json" plugins inspect "$CLAWHUB_PLUGIN_ID" --json
node scripts/e2e/lib/plugins/assertions.mjs clawhub-updated
run_plugins_openclaw_logged uninstall-clawhub plugins uninstall "$CLAWHUB_PLUGIN_SPEC" --force
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-clawhub-uninstalled.json" plugins list --json
node scripts/e2e/lib/plugins/assertions.mjs clawhub-removed
fi
}

View File

@@ -0,0 +1,284 @@
OPENCLAW_PLUGINS_FIXTURE_PID_FILES=()
OPENCLAW_PLUGINS_FIXTURE_EXIT_TRAP_INSTALLED=0
OPENCLAW_PLUGINS_FIXTURE_PREVIOUS_EXIT_ACTION=""
openclaw_plugins_read_positive_int_env() {
local name="${1:?missing environment variable name}"
local fallback="${2:?missing fallback value}"
local value="${!name-}"
if [[ -z "${!name+x}" ]]; then
value="$fallback"
fi
if [[ ! "$value" =~ ^[0-9]+$ ]] || (( 10#$value < 1 )); then
echo "invalid $name: $value" >&2
return 2
fi
printf "%s\n" "$((10#$value))"
}
openclaw_plugins_read_nonnegative_decimal_env() {
local name="${1:?missing environment variable name}"
local fallback="${2:?missing fallback value}"
local value="${!name-}"
if [[ -z "${!name+x}" ]]; then
value="$fallback"
fi
if [[ ! "$value" =~ ^[0-9]+([.][0-9]+)?$ ]]; then
echo "invalid $name: $value" >&2
return 2
fi
printf "%s\n" "$value"
}
openclaw_plugins_cleanup_fixture_servers() {
local pid_file
local pid
for pid_file in "${OPENCLAW_PLUGINS_FIXTURE_PID_FILES[@]:-}"; do
[[ -f "$pid_file" ]] || continue
pid="$(cat "$pid_file" 2>/dev/null || true)"
if [[ "$pid" =~ ^[0-9]+$ ]]; then
openclaw_plugins_stop_fixture_process "$pid"
fi
rm -f "$pid_file"
done
}
openclaw_plugins_signal_fixture_process() {
local pid="$1"
local signal="$2"
if kill -0 -- "-$pid" >/dev/null 2>&1; then
kill "-$signal" -- "-$pid" >/dev/null 2>&1 || true
return
fi
kill "-$signal" "$pid" >/dev/null 2>&1 || true
}
openclaw_plugins_fixture_process_alive() {
local pid="$1"
kill -0 "$pid" >/dev/null 2>&1 || kill -0 -- "-$pid" >/dev/null 2>&1
}
openclaw_plugins_stop_fixture_process() {
local pid="$1"
local _
local attempts interval
attempts="$(openclaw_plugins_read_positive_int_env OPENCLAW_PLUGINS_FIXTURE_STOP_ATTEMPTS 40)" || return $?
interval="$(openclaw_plugins_read_nonnegative_decimal_env OPENCLAW_PLUGINS_FIXTURE_STOP_INTERVAL_SECONDS 0.25)" || return $?
if declare -F openclaw_e2e_stop_process >/dev/null 2>&1; then
openclaw_e2e_stop_process "$pid"
return
fi
openclaw_plugins_signal_fixture_process "$pid" TERM
for _ in $(seq 1 "$attempts"); do
! openclaw_plugins_fixture_process_alive "$pid" && { wait "$pid" >/dev/null 2>&1 || true; return; }
sleep "$interval"
done
openclaw_plugins_signal_fixture_process "$pid" KILL
wait "$pid" >/dev/null 2>&1 || true
}
openclaw_plugins_print_fixture_log() {
local log_file="$1"
if declare -F docker_e2e_print_log >/dev/null 2>&1; then
docker_e2e_print_log "$log_file"
return
fi
if [ ! -f "$log_file" ]; then
return
fi
local max_bytes
max_bytes="$(openclaw_plugins_read_positive_int_env OPENCLAW_DOCKER_E2E_LOG_PRINT_BYTES 65536)" || return $?
local log_bytes
log_bytes="$(wc -c <"$log_file" 2>/dev/null || echo 0)"
log_bytes="${log_bytes//[[:space:]]/}"
if ! [[ "$log_bytes" =~ ^[0-9]+$ ]]; then
log_bytes="0"
fi
if [ "$log_bytes" -le "$max_bytes" ]; then
cat "$log_file"
return
fi
echo "--- ${log_file} truncated: showing last ${max_bytes} of ${log_bytes} bytes ---"
tail -c "$max_bytes" "$log_file"
}
openclaw_plugins_validate_fixture_log_print_bytes() {
openclaw_plugins_read_positive_int_env OPENCLAW_DOCKER_E2E_LOG_PRINT_BYTES 65536 >/dev/null
}
openclaw_plugins_register_fixture_pid_file() {
local pid_file="$1"
OPENCLAW_PLUGINS_FIXTURE_PID_FILES+=("$pid_file")
openclaw_plugins_install_fixture_cleanup_trap
}
openclaw_plugins_install_fixture_cleanup_trap() {
if [[ "${OPENCLAW_PLUGINS_FIXTURE_EXIT_TRAP_INSTALLED:-0}" = "1" ]]; then
return
fi
local existing_trap
existing_trap="$(trap -p EXIT || true)"
if [[ -n "$existing_trap" && "$existing_trap" != *openclaw_plugins_fixture_exit_trap* ]]; then
local existing_action="${existing_trap#trap -- }"
existing_action="${existing_action% EXIT}"
eval "OPENCLAW_PLUGINS_FIXTURE_PREVIOUS_EXIT_ACTION=$existing_action"
fi
OPENCLAW_PLUGINS_FIXTURE_EXIT_TRAP_INSTALLED=1
trap openclaw_plugins_fixture_exit_trap EXIT
}
openclaw_plugins_fixture_exit_trap() {
local status="$?"
openclaw_plugins_cleanup_fixture_servers
if [[ -n "${OPENCLAW_PLUGINS_FIXTURE_PREVIOUS_EXIT_ACTION:-}" ]]; then
eval "$OPENCLAW_PLUGINS_FIXTURE_PREVIOUS_EXIT_ACTION"
fi
exit "$status"
}
record_fixture_plugin_trust() {
local plugin_id="$1"
local plugin_root="$2"
local enabled="$3"
node scripts/e2e/lib/plugins/assertions.mjs record-fixture-plugin-trust "$plugin_id" "$plugin_root" "$enabled"
}
write_demo_fixture_plugin() {
local dir="$1"
node scripts/e2e/lib/fixture.mjs plugin-demo "$dir"
}
write_fixture_plugin() {
local dir="$1"
local id="$2"
local version="$3"
local method="$4"
local name="$5"
node scripts/e2e/lib/fixture.mjs plugin "$dir" "$id" "$version" "$method" "$name"
}
write_fixture_plugin_with_cli() {
local dir="$1"
local id="$2"
local version="$3"
local method="$4"
local name="$5"
local cli_root="$6"
local cli_output="$7"
node scripts/e2e/lib/fixture.mjs plugin-cli "$dir" "$id" "$version" "$method" "$name" "$cli_root" "$cli_output"
}
pack_fixture_plugin_with_cli_registry_dependency() {
local pack_dir="$1"
local output_tgz="$2"
local id="$3"
local version="$4"
local method="$5"
local name="$6"
local cli_root="$7"
local cli_output="$8"
mkdir -p "$pack_dir/package"
node scripts/e2e/lib/fixture.mjs plugin-cli-registry-dep "$pack_dir/package" "$id" "$version" "$method" "$name" "$cli_root" "$cli_output"
tar -czf "$output_tgz" -C "$pack_dir" package
}
pack_fake_is_number_package() {
local pack_dir="$1"
local output_tgz="$2"
mkdir -p "$pack_dir/package"
node scripts/e2e/lib/fixture.mjs fake-is-number-package "$pack_dir/package"
tar -czf "$output_tgz" -C "$pack_dir" package
}
write_fixture_plugin_with_vendored_dependency() {
local dir="$1"
local id="$2"
local version="$3"
local method="$4"
local name="$5"
node scripts/e2e/lib/fixture.mjs plugin-vendored-dep "$dir" "$id" "$version" "$method" "$name"
}
pack_fixture_plugin() {
local pack_dir="$1"
local output_tgz="$2"
local id="$3"
local version="$4"
local method="$5"
local name="$6"
mkdir -p "$pack_dir/package"
write_fixture_plugin "$pack_dir/package" "$id" "$version" "$method" "$name"
tar -czf "$output_tgz" -C "$pack_dir" package
}
pack_fixture_plugin_with_invalid_extension_entry() {
local pack_dir="$1"
local output_tgz="$2"
local id="$3"
local version="$4"
local method="$5"
local name="$6"
mkdir -p "$pack_dir/package"
write_fixture_plugin "$pack_dir/package" "$id" "$version" "$method" "$name"
node --input-type=module - "$pack_dir/package/package.json" <<'NODE'
import fs from "node:fs";
const packageJsonPath = process.argv[2];
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
packageJson.openclaw.extensions = ["./index.js", " "];
fs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, "utf8");
NODE
tar -czf "$output_tgz" -C "$pack_dir" package
}
start_npm_fixture_registry() {
local package_name="$1"
local version="$2"
local tarball="$3"
local fixture_dir="$4"
local server_log="$fixture_dir/npm-registry.log"
local server_port_file="$fixture_dir/npm-registry-port"
local server_pid_file="$fixture_dir/npm-registry-pid"
shift 4
openclaw_plugins_validate_fixture_log_print_bytes || return $?
node scripts/e2e/lib/plugins/npm-registry-server.mjs "$server_port_file" "$package_name" "$version" "$tarball" "$@" >"$server_log" 2>&1 &
local server_pid="$!"
echo "$server_pid" >"$server_pid_file"
openclaw_plugins_register_fixture_pid_file "$server_pid_file"
for _ in $(seq 1 100); do
if [[ -s "$server_port_file" ]]; then
export NPM_CONFIG_REGISTRY="http://127.0.0.1:$(cat "$server_port_file")"
return 0
fi
if ! kill -0 "$server_pid" 2>/dev/null; then
openclaw_plugins_print_fixture_log "$server_log"
return 1
fi
sleep 0.1
done
openclaw_plugins_print_fixture_log "$server_log"
echo "Timed out waiting for npm fixture registry." >&2
return 1
}
write_claude_bundle_fixture() {
local bundle_root="$1"
node scripts/e2e/lib/fixture.mjs claude-bundle "$bundle_root"
}

View File

@@ -0,0 +1,45 @@
run_plugins_marketplace_scenario() {
echo "Testing marketplace install and update flows..."
marketplace_root="$HOME/.claude/plugins/marketplaces/fixture-marketplace"
mkdir -p "$HOME/.claude/plugins" "$marketplace_root/.claude-plugin"
write_fixture_plugin \
"$marketplace_root/plugins/marketplace-shortcut" \
"marketplace-shortcut" \
"0.0.1" \
"demo.marketplace.shortcut.v1" \
"Marketplace Shortcut"
write_fixture_plugin \
"$marketplace_root/plugins/marketplace-direct" \
"marketplace-direct" \
"0.0.1" \
"demo.marketplace.direct.v1" \
"Marketplace Direct"
node scripts/e2e/lib/fixture.mjs marketplace "$marketplace_root"
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/marketplace-list.json" plugins marketplace list claude-fixtures --json
node scripts/e2e/lib/plugins/assertions.mjs marketplace-list
run_plugins_openclaw_logged install-marketplace-shortcut plugins install marketplace-shortcut@claude-fixtures
run_plugins_openclaw_logged install-marketplace-direct plugins install marketplace-direct --marketplace claude-fixtures
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-marketplace.json" plugins list --json
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-marketplace-shortcut-inspect.json" plugins inspect marketplace-shortcut --runtime --json
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-marketplace-direct-inspect.json" plugins inspect marketplace-direct --runtime --json
node scripts/e2e/lib/plugins/assertions.mjs marketplace-installed
node scripts/e2e/lib/plugins/assertions.mjs marketplace-records
write_fixture_plugin \
"$marketplace_root/plugins/marketplace-shortcut" \
"marketplace-shortcut" \
"0.0.2" \
"demo.marketplace.shortcut.v2" \
"Marketplace Shortcut"
run_plugins_openclaw_logged update-marketplace-shortcut-dry-run plugins update marketplace-shortcut --dry-run
run_plugins_openclaw_logged update-marketplace-shortcut plugins update marketplace-shortcut
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-marketplace-updated.json" plugins list --json
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-marketplace-updated-inspect.json" plugins inspect marketplace-shortcut --runtime --json
node scripts/e2e/lib/plugins/assertions.mjs marketplace-updated
}

View File

@@ -0,0 +1,120 @@
// Fixture npm registry server for plugin E2E scenarios.
import crypto from "node:crypto";
import fs from "node:fs";
import http from "node:http";
import path from "node:path";
const [portFile, ...packageArgs] = process.argv.slice(2);
if (!portFile || packageArgs.length === 0 || packageArgs.length % 3 !== 0) {
console.error(
"usage: npm-registry-server.mjs <port-file> <package-name> <version> <tarball-path> [...]",
);
process.exit(1);
}
const packages = new Map();
for (let index = 0; index < packageArgs.length; index += 3) {
const packageName = packageArgs[index];
const version = packageArgs[index + 1];
const tarballPath = packageArgs[index + 2];
const archive = fs.readFileSync(tarballPath);
const existing = packages.get(packageName) ?? {
encodedPackageName: encodeURIComponent(packageName).replace("%40", "@"),
packageName,
latestVersion: version,
versions: new Map(),
};
existing.latestVersion = version;
existing.versions.set(version, {
archive,
dependencies: packageName === "@openclaw/demo-plugin-npm" ? { "is-number": "7.0.0" } : {},
integrity: `sha512-${crypto.createHash("sha512").update(archive).digest("base64")}`,
shasum: crypto.createHash("sha1").update(archive).digest("hex"),
tarballName: path.basename(tarballPath),
version,
});
packages.set(packageName, existing);
}
const metadataFor = (entry, baseUrl) => ({
name: entry.packageName,
"dist-tags": { latest: entry.latestVersion },
versions: Object.fromEntries(
[...entry.versions.entries()].map(([version, versionEntry]) => [
version,
{
dependencies: versionEntry.dependencies,
name: entry.packageName,
version,
dist: {
integrity: versionEntry.integrity,
shasum: versionEntry.shasum,
tarball: `${baseUrl}/${entry.encodedPackageName}/-/${versionEntry.tarballName}`,
},
},
]),
),
});
function decodePackagePath(pathname) {
try {
return decodeURIComponent(pathname.slice(1));
} catch {
return undefined;
}
}
function findPackageForPath(pathname) {
const packageName = decodePackagePath(pathname);
return packageName === undefined ? undefined : packages.get(packageName);
}
function findTarballForPath(pathname) {
for (const entry of packages.values()) {
const prefix = `/${entry.encodedPackageName}/-/`;
if (!pathname.toLowerCase().startsWith(prefix.toLowerCase())) {
continue;
}
for (const versionEntry of entry.versions.values()) {
if (pathname.endsWith(`/${versionEntry.tarballName}`)) {
return versionEntry;
}
}
}
return undefined;
}
const server = http.createServer((request, response) => {
const url = new URL(request.url ?? "/", "http://127.0.0.1");
const baseUrl = `http://127.0.0.1:${server.address().port}`;
if (request.method !== "GET") {
response.writeHead(405, { "content-type": "text/plain" });
response.end("method not allowed");
return;
}
const packageEntry = findPackageForPath(url.pathname);
if (packageEntry) {
response.writeHead(200, { "content-type": "application/json" });
response.end(`${JSON.stringify(metadataFor(packageEntry, baseUrl))}\n`);
return;
}
const tarballEntry = findTarballForPath(url.pathname);
if (tarballEntry) {
response.writeHead(200, {
"content-type": "application/octet-stream",
"content-length": String(tarballEntry.archive.length),
});
response.end(tarballEntry.archive);
return;
}
response.writeHead(404, { "content-type": "text/plain" });
response.end(`not found: ${url.pathname}`);
});
server.listen(0, "127.0.0.1", () => {
fs.writeFileSync(portFile, String(server.address().port));
});

View File

@@ -0,0 +1,236 @@
#!/usr/bin/env bash
set -euo pipefail
source scripts/lib/openclaw-e2e-instance.sh
source scripts/lib/docker-e2e-logs.sh
OPENCLAW_PLUGINS_SWEEP_SOURCE_ONLY="${OPENCLAW_PLUGINS_SWEEP_SOURCE_ONLY:-0}"
if [[ -z "${OPENCLAW_ENTRY:-}" && "$OPENCLAW_PLUGINS_SWEEP_SOURCE_ONLY" != "1" ]]; then
OPENCLAW_ENTRY="$(openclaw_e2e_resolve_entrypoint)"
fi
export OPENCLAW_ENTRY
OPENCLAW_PLUGINS_CREATED_TMP_DIR=0
if [[ -z "${OPENCLAW_PLUGINS_TMP_DIR:-}" ]]; then
OPENCLAW_PLUGINS_TMP_DIR="$(mktemp -d "/tmp/openclaw-plugins.XXXXXX")"
OPENCLAW_PLUGINS_CREATED_TMP_DIR=1
fi
export OPENCLAW_PLUGINS_TMP_DIR
OPENCLAW_PLUGINS_CLI_TIMEOUT="${OPENCLAW_PLUGINS_CLI_TIMEOUT:-180s}"
mkdir -p "$OPENCLAW_PLUGINS_TMP_DIR"
run_plugins_openclaw_logged() {
local label="$1"
shift
run_logged "$label" openclaw_e2e_maybe_timeout "$OPENCLAW_PLUGINS_CLI_TIMEOUT" node "$OPENCLAW_ENTRY" "$@"
}
run_plugins_openclaw_capture() {
local output_file="$1"
shift
openclaw_e2e_maybe_timeout "$OPENCLAW_PLUGINS_CLI_TIMEOUT" node "$OPENCLAW_ENTRY" "$@" >"$output_file"
}
run_plugins_shell_logged() {
local label="$1"
shift
local command="$1"
run_logged "$label" openclaw_e2e_maybe_timeout "$OPENCLAW_PLUGINS_CLI_TIMEOUT" bash -c "$command"
}
source scripts/e2e/lib/plugins/fixtures.sh
source scripts/e2e/lib/plugins/marketplace.sh
source scripts/e2e/lib/plugins/clawhub.sh
cleanup_openclaw_plugins_sweep() {
openclaw_plugins_cleanup_fixture_servers
if [[ "${OPENCLAW_PLUGINS_CREATED_TMP_DIR:-0}" = "1" ]]; then
rm -rf "$OPENCLAW_PLUGINS_TMP_DIR"
fi
}
if [[ "$OPENCLAW_PLUGINS_SWEEP_SOURCE_ONLY" = "1" ]]; then
return 0 2>/dev/null || { cleanup_openclaw_plugins_sweep; exit 0; }
fi
trap cleanup_openclaw_plugins_sweep EXIT
openclaw_e2e_eval_test_state_from_b64 "${OPENCLAW_TEST_STATE_SCRIPT_B64:?missing OPENCLAW_TEST_STATE_SCRIPT_B64}"
PACKAGE_VERSION="$(node -p 'require("./package.json").version')"
OPENCLAW_PACKAGE_ACCEPTANCE_LEGACY_COMPAT="$(node scripts/e2e/lib/package-compat.mjs "$PACKAGE_VERSION")"
export OPENCLAW_PACKAGE_ACCEPTANCE_LEGACY_COMPAT
BUNDLED_PLUGIN_ROOT_DIR="extensions"
OPENCLAW_PLUGIN_HOME="$HOME/.openclaw/$BUNDLED_PLUGIN_ROOT_DIR"
demo_plugin_id="demo-plugin"
demo_plugin_root="$OPENCLAW_PLUGIN_HOME/$demo_plugin_id"
write_demo_fixture_plugin "$demo_plugin_root"
record_fixture_plugin_trust "$demo_plugin_id" "$demo_plugin_root" 1
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins.json" plugins list --json
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-inspect.json" plugins inspect demo-plugin --runtime --json
node scripts/e2e/lib/plugins/assertions.mjs demo-plugin
echo "Testing tgz install flow..."
pack_dir="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-pack.XXXXXX")"
pack_fixture_plugin "$pack_dir" "$OPENCLAW_PLUGINS_TMP_DIR/demo-plugin-tgz.tgz" demo-plugin-tgz 0.0.1 demo.tgz "Demo Plugin TGZ"
run_plugins_openclaw_logged install-tgz plugins install "$OPENCLAW_PLUGINS_TMP_DIR/demo-plugin-tgz.tgz"
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins2.json" plugins list --json
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins2-inspect.json" plugins inspect demo-plugin-tgz --runtime --json
node scripts/e2e/lib/plugins/assertions.mjs plugin-tgz
run_plugins_openclaw_logged uninstall-tgz plugins uninstall demo-plugin-tgz --force
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins2-uninstalled.json" plugins list --json
node scripts/e2e/lib/plugins/assertions.mjs plugin-tgz-removed
echo "Testing install from local folder (plugins.load.paths)..."
dir_plugin="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-dir.XXXXXX")"
write_fixture_plugin "$dir_plugin" demo-plugin-dir 0.0.1 demo.dir "Demo Plugin DIR"
run_plugins_openclaw_logged install-dir plugins install "$dir_plugin"
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins3.json" plugins list --json
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins3-inspect.json" plugins inspect demo-plugin-dir --runtime --json
node scripts/e2e/lib/plugins/assertions.mjs plugin-dir "$dir_plugin"
openclaw_e2e_maybe_timeout "$OPENCLAW_PLUGINS_CLI_TIMEOUT" node "$OPENCLAW_ENTRY" plugins update demo-plugin-dir >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-dir-update.log" 2>&1
node scripts/e2e/lib/plugins/assertions.mjs plugin-dir-update-skipped
run_plugins_openclaw_logged uninstall-dir plugins uninstall demo-plugin-dir --force
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins3-uninstalled.json" plugins list --json
node scripts/e2e/lib/plugins/assertions.mjs plugin-dir-removed
echo "Testing install from local folder with preinstalled dependencies..."
dir_deps_plugin="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-dir-deps.XXXXXX")"
write_fixture_plugin_with_vendored_dependency "$dir_deps_plugin" demo-plugin-dir-deps 0.0.1 demo.dir.deps "Demo Plugin DIR Deps"
run_plugins_openclaw_logged install-dir-deps plugins install "$dir_deps_plugin"
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-dir-deps.json" plugins list --json
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-dir-deps-inspect.json" plugins inspect demo-plugin-dir-deps --runtime --json
node scripts/e2e/lib/plugins/assertions.mjs plugin-dir-deps "$dir_deps_plugin"
run_plugins_openclaw_logged uninstall-dir-deps plugins uninstall demo-plugin-dir-deps --force
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-dir-deps-uninstalled.json" plugins list --json
node scripts/e2e/lib/plugins/assertions.mjs plugin-dir-deps-removed
echo "Testing install from npm spec (file:)..."
file_pack_dir="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-filepack.XXXXXX")"
write_fixture_plugin "$file_pack_dir/package" demo-plugin-file 0.0.1 demo.file "Demo Plugin FILE"
run_plugins_openclaw_logged install-file plugins install "file:$file_pack_dir/package"
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins4.json" plugins list --json
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins4-inspect.json" plugins inspect demo-plugin-file --runtime --json
node scripts/e2e/lib/plugins/assertions.mjs plugin-file "$file_pack_dir/package"
run_plugins_openclaw_logged uninstall-file plugins uninstall demo-plugin-file --force
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins4-uninstalled.json" plugins list --json
node scripts/e2e/lib/plugins/assertions.mjs plugin-file-removed
echo "Testing install and update from npm registry..."
npm_pack_dir="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-npm-pack.XXXXXX")"
npm_dep_pack_dir="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-npm-dep-pack.XXXXXX")"
invalid_npm_pack_dir="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-invalid-metadata-pack.XXXXXX")"
npm_registry_dir="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-npm-registry.XXXXXX")"
pack_fixture_plugin_with_cli_registry_dependency "$npm_pack_dir" "$OPENCLAW_PLUGINS_TMP_DIR/demo-plugin-npm.tgz" demo-plugin-npm 0.0.1 demo.npm "Demo Plugin NPM" demo-npm "demo-plugin-npm:pong"
pack_fake_is_number_package "$npm_dep_pack_dir" "$OPENCLAW_PLUGINS_TMP_DIR/is-number-7.0.0.tgz"
pack_fixture_plugin_with_invalid_extension_entry "$invalid_npm_pack_dir" "$OPENCLAW_PLUGINS_TMP_DIR/demo-plugin-invalid-metadata.tgz" demo-plugin-invalid-metadata 0.0.1 demo.invalid.metadata "Demo Plugin Invalid Metadata"
start_npm_fixture_registry "@openclaw/demo-plugin-npm" "0.0.1" "$OPENCLAW_PLUGINS_TMP_DIR/demo-plugin-npm.tgz" "$npm_registry_dir" "is-number" "7.0.0" "$OPENCLAW_PLUGINS_TMP_DIR/is-number-7.0.0.tgz" "@openclaw/demo-plugin-invalid-metadata" "0.0.1" "$OPENCLAW_PLUGINS_TMP_DIR/demo-plugin-invalid-metadata.tgz"
run_plugins_openclaw_logged install-npm plugins install "npm:@openclaw/demo-plugin-npm@0.0.1"
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-npm.json" plugins list --json
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-npm-inspect.json" plugins inspect demo-plugin-npm --runtime --json
run_plugins_shell_logged exec-npm-plugin-cli 'node "$OPENCLAW_ENTRY" demo-npm ping >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-npm-cli.txt"'
node scripts/e2e/lib/plugins/assertions.mjs plugin-npm
openclaw_e2e_maybe_timeout "$OPENCLAW_PLUGINS_CLI_TIMEOUT" node "$OPENCLAW_ENTRY" plugins update demo-plugin-npm >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-npm-update.log" 2>&1
node scripts/e2e/lib/plugins/assertions.mjs plugin-npm-update
run_plugins_openclaw_logged uninstall-npm plugins uninstall demo-plugin-npm --force
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-npm-uninstalled.json" plugins list --json
node scripts/e2e/lib/plugins/assertions.mjs plugin-npm-removed
echo "Testing npm install rejects malformed package metadata..."
if openclaw_e2e_maybe_timeout "$OPENCLAW_PLUGINS_CLI_TIMEOUT" node "$OPENCLAW_ENTRY" plugins install "npm:@openclaw/demo-plugin-invalid-metadata@0.0.1" >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-invalid-openclaw-extensions.log" 2>&1; then
cat "$OPENCLAW_PLUGINS_TMP_DIR/plugins-invalid-openclaw-extensions.log"
echo "Expected malformed package metadata install to fail." >&2
exit 1
fi
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-invalid-openclaw-extensions-list.json" plugins list --json
node scripts/e2e/lib/plugins/assertions.mjs invalid-openclaw-extensions
echo "Testing install from git repo and plugin CLI execution..."
git_fixture_root="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-git.XXXXXX")"
git_repo="$git_fixture_root/repo"
git_repo_url="file://$git_repo"
write_fixture_plugin_with_cli "$git_repo" demo-plugin-git 0.0.1 demo.git "Demo Plugin Git" demo-git "demo-plugin-git:pong"
git -C "$git_repo" init -q
git -C "$git_repo" config user.email "docker-e2e@openclaw.local"
git -C "$git_repo" config user.name "OpenClaw Docker E2E"
git -C "$git_repo" add -A
git -C "$git_repo" commit -qm "test fixture"
git_ref="$(git -C "$git_repo" rev-parse HEAD)"
run_plugins_openclaw_logged install-git plugins install "git:$git_repo_url@$git_ref"
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-git.json" plugins list --json
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-git-inspect.json" plugins inspect demo-plugin-git --runtime --json
run_plugins_shell_logged exec-git-plugin-cli 'node "$OPENCLAW_ENTRY" demo-git ping >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-git-cli.txt"'
node scripts/e2e/lib/plugins/assertions.mjs plugin-git "$git_repo_url" "$git_ref"
run_plugins_openclaw_logged uninstall-git plugins uninstall demo-plugin-git --force
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-git-uninstalled.json" plugins list --json
node scripts/e2e/lib/plugins/assertions.mjs plugin-git-removed
echo "Testing git plugin update from moving ref..."
git_update_fixture_root="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-git-update.XXXXXX")"
git_update_repo="$git_update_fixture_root/repo"
git_update_repo_url="file://$git_update_repo"
write_fixture_plugin_with_cli "$git_update_repo" demo-plugin-git-update 0.0.1 demo.git.update.v1 "Demo Plugin Git Update" demo-git-update "demo-plugin-git-update:pong-v1"
git -C "$git_update_repo" init -q
git -C "$git_update_repo" config user.email "docker-e2e@openclaw.local"
git -C "$git_update_repo" config user.name "OpenClaw Docker E2E"
git -C "$git_update_repo" checkout -qb main
git -C "$git_update_repo" add -A
git -C "$git_update_repo" commit -qm "test fixture v1"
git_update_ref_v1="$(git -C "$git_update_repo" rev-parse HEAD)"
run_plugins_openclaw_logged install-git-update plugins install "git:$git_update_repo_url@main"
write_fixture_plugin_with_cli "$git_update_repo" demo-plugin-git-update 0.0.2 demo.git.update.v2 "Demo Plugin Git Update" demo-git-update "demo-plugin-git-update:pong-v2"
git -C "$git_update_repo" add -A
git -C "$git_update_repo" commit -qm "test fixture v2"
openclaw_e2e_maybe_timeout "$OPENCLAW_PLUGINS_CLI_TIMEOUT" node "$OPENCLAW_ENTRY" plugins update demo-plugin-git-update >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-git-update.log" 2>&1
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-git-update.json" plugins list --json
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-git-update-inspect.json" plugins inspect demo-plugin-git-update --runtime --json
run_plugins_shell_logged exec-updated-git-plugin-cli 'node "$OPENCLAW_ENTRY" demo-git-update ping >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-git-update-cli.txt"'
node scripts/e2e/lib/plugins/assertions.mjs plugin-git-updated "$git_update_ref_v1"
echo "Testing Claude bundle enable and inspect flow..."
bundle_plugin_id="claude-bundle-e2e"
bundle_root="$OPENCLAW_PLUGIN_HOME/$bundle_plugin_id"
write_claude_bundle_fixture "$bundle_root"
record_fixture_plugin_trust "$bundle_plugin_id" "$bundle_root" 0
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-bundle-disabled.json" plugins list --json
node scripts/e2e/lib/plugins/assertions.mjs bundle-disabled
run_plugins_openclaw_logged enable-claude-bundle plugins enable claude-bundle-e2e
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-bundle-inspect.json" plugins inspect claude-bundle-e2e --json
node scripts/e2e/lib/plugins/assertions.mjs bundle-inspect
echo "Testing plugin install visible after explicit restart..."
slash_install_dir="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-slash-install.XXXXXX")"
write_fixture_plugin "$slash_install_dir" slash-install-plugin 0.0.1 demo.slash.install "Slash Install Plugin"
run_plugins_openclaw_logged install-slash-plugin plugins install "$slash_install_dir"
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugin-command-install-show.json" plugins inspect slash-install-plugin --runtime --json
node scripts/e2e/lib/plugins/assertions.mjs slash-install
run_plugins_marketplace_scenario
run_plugins_clawhub_scenario