Files
openclaw/scripts/e2e/parallels/plugin-isolation.ts
2026-06-16 14:31:23 +08:00

206 lines
8.2 KiB
TypeScript

// Plugin Isolation script supports OpenClaw repository automation.
import { shellQuote } from "./host-command.ts";
import { providerIdFromModelId } from "./provider-auth.ts";
interface PluginIsolationOptions {
fallbackPluginId: string;
homeFallback?: string;
modelId: string;
nodeCommand?: string;
}
export function posixCodexPlatformPackageRepairFunction(): string {
return `repair_missing_codex_platform_package() {
output_file="$1"
grep -F 'Missing optional dependency @openai/codex-' "$output_file" >/dev/null 2>&1 || return 1
state_home="\${OPENCLAW_PARALLELS_HOME:-\${HOME:-}}"
codex_manifest=""
for candidate in "$state_home"/.openclaw/npm/projects/*/node_modules/@openclaw/codex/package.json; do
[ -f "$candidate" ] || continue
codex_manifest="$candidate"
break
done
if [ -z "$codex_manifest" ]; then
echo "codex-platform-repair: managed Codex project not found" >&2
return 1
fi
project_root="\${codex_manifest%/node_modules/@openclaw/codex/package.json}"
cache_dir="$(mktemp -d "\${TMPDIR:-/tmp}/openclaw-npm-cache.XXXXXX")"
echo "codex-platform-repair: retrying managed npm install once with a fresh cache" >&2
repair_rc=0
(
cd "$project_root"
NPM_CONFIG_CACHE="$cache_dir" npm_config_cache="$cache_dir" npm install --omit=dev --omit=peer --legacy-peer-deps --ignore-scripts --no-audit --no-fund
) || repair_rc=$?
rm -rf "$cache_dir"
if [ "$repair_rc" -ne 0 ]; then
echo "codex-platform-repair: npm install failed with exit code $repair_rc" >&2
return "$repair_rc"
fi
echo "codex-platform-repair: managed npm install completed" >&2
}`;
}
export function windowsCodexPlatformPackageRepairFunction(): string {
return String.raw`function Repair-MissingCodexPlatformPackage {
param([object[]] $Output)
$outputText = $Output | Out-String
if ($outputText -notmatch [regex]::Escape('Missing optional dependency @openai/codex-')) {
return $false
}
$projectsRoot = Join-Path $env:USERPROFILE '.openclaw\npm\projects'
$codexManifest = Get-ChildItem -Path $projectsRoot -Filter package.json -File -Recurse -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match 'node_modules[\\/]@openclaw[\\/]codex[\\/]package\.json$' } |
Select-Object -First 1
if (-not $codexManifest) {
Write-Warning 'codex-platform-repair: managed Codex project not found'
return $false
}
$projectRoot = $codexManifest.Directory.Parent.Parent.Parent.FullName
$cacheDir = Join-Path ([System.IO.Path]::GetTempPath()) ('openclaw-npm-cache-' + [guid]::NewGuid().ToString('N'))
$oldUpperCache = [Environment]::GetEnvironmentVariable('NPM_CONFIG_CACHE', 'Process')
$oldLowerCache = [Environment]::GetEnvironmentVariable('npm_config_cache', 'Process')
$pushedLocation = $false
$repairExit = 1
try {
New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null
[Environment]::SetEnvironmentVariable('NPM_CONFIG_CACHE', $cacheDir, 'Process')
[Environment]::SetEnvironmentVariable('npm_config_cache', $cacheDir, 'Process')
Push-Location $projectRoot
$pushedLocation = $true
Write-Host 'codex-platform-repair: retrying managed npm install once with a fresh cache'
$repairOutput = & npm.cmd install --omit=dev --omit=peer --legacy-peer-deps --ignore-scripts --no-audit --no-fund 2>&1
$repairExit = $LASTEXITCODE
if ($null -ne $repairOutput) { $repairOutput | ForEach-Object { Write-Host $_ } }
} finally {
if ($pushedLocation) { Pop-Location }
[Environment]::SetEnvironmentVariable('NPM_CONFIG_CACHE', $oldUpperCache, 'Process')
[Environment]::SetEnvironmentVariable('npm_config_cache', $oldLowerCache, 'Process')
Remove-Item $cacheDir -Force -Recurse -ErrorAction SilentlyContinue
}
if ($repairExit -ne 0) {
Write-Warning "codex-platform-repair: npm install failed with exit code $repairExit"
return $false
}
Write-Host 'codex-platform-repair: managed npm install completed'
return $true
}`;
}
export function providerOnlyPluginId(modelId: string, fallbackPluginId: string): string {
return providerIdFromModelId(modelId) || fallbackPluginId;
}
export function posixProviderOnlyPluginIsolationScript(options: PluginIsolationOptions): string {
const nodeCommand = shellQuote(options.nodeCommand ?? "node");
const homeEnv = options.homeFallback
? `OPENCLAW_PARALLELS_HOME=${shellQuote(options.homeFallback)} `
: "";
return `/usr/bin/env ${homeEnv}${nodeCommand} - <<'JS'
${providerOnlyPluginIsolationNodeScript(options)}
JS`;
}
export function windowsProviderOnlyPluginIsolationScript(options: PluginIsolationOptions): string {
const payloadJson = JSON.stringify({
modelId: options.modelId,
pluginId: providerOnlyPluginId(options.modelId, options.fallbackPluginId),
});
return `$env:OPENCLAW_PARALLELS_PLUGIN_ISOLATION = @'
${payloadJson}
'@
$isolationScriptPath = Join-Path ([System.IO.Path]::GetTempPath()) 'openclaw-parallels-plugin-isolation.cjs'
@'
${providerOnlyPluginIsolationNodeSource()}
'@ | Set-Content -Path $isolationScriptPath -Encoding UTF8
node.exe $isolationScriptPath
if ($LASTEXITCODE -ne 0) { throw "plugin isolation failed with exit code $LASTEXITCODE" }
Remove-Item $isolationScriptPath -Force -ErrorAction SilentlyContinue
Remove-Item Env:OPENCLAW_PARALLELS_PLUGIN_ISOLATION -Force -ErrorAction SilentlyContinue`;
}
function providerOnlyPluginIsolationNodeScript(options: PluginIsolationOptions): string {
const payloadJson = JSON.stringify({
homeFallback: options.homeFallback,
modelId: options.modelId,
pluginId: providerOnlyPluginId(options.modelId, options.fallbackPluginId),
});
return `process.env.OPENCLAW_PARALLELS_PLUGIN_ISOLATION = ${JSON.stringify(payloadJson)};
${providerOnlyPluginIsolationNodeSource()}`;
}
function providerOnlyPluginIsolationNodeSource(): string {
return String.raw`const fs = require("node:fs");
const path = require("node:path");
const payload = JSON.parse(process.env.OPENCLAW_PARALLELS_PLUGIN_ISOLATION || "{}");
const home =
process.env.OPENCLAW_PARALLELS_HOME ||
payload.homeFallback ||
process.env.HOME ||
process.env.USERPROFILE ||
"/root";
const configPath = path.join(home, ".openclaw", "openclaw.json");
const stateDir = path.dirname(configPath);
const modelId = String(payload.modelId || "");
const allowedPluginId = String(payload.pluginId || "").trim();
if (!allowedPluginId || !modelId) {
throw new Error("missing plugin isolation payload");
}
const readConfig = () => {
if (!fs.existsSync(configPath)) {
return {};
}
return JSON.parse(fs.readFileSync(configPath, "utf8"));
};
const objectRecord = (value) =>
value && typeof value === "object" && !Array.isArray(value) ? value : {};
const config = readConfig();
config.plugins = objectRecord(config.plugins);
config.plugins.entries = { [allowedPluginId]: { enabled: true } };
config.plugins.allow = [allowedPluginId];
config.agents = objectRecord(config.agents);
config.agents.defaults = objectRecord(config.agents.defaults);
config.agents.defaults.model = {
...objectRecord(config.agents.defaults.model),
primary: modelId,
};
config.agents.defaults.models = objectRecord(config.agents.defaults.models);
const selectedModelEntry = config.agents.defaults.models[modelId];
if (selectedModelEntry && typeof selectedModelEntry === "object" && !Array.isArray(selectedModelEntry)) {
delete selectedModelEntry.agentRuntime;
}
const providerId = modelId.split("/", 1)[0] || "";
const providerModelId = modelId.slice(providerId.length + 1);
const providers = objectRecord(objectRecord(config.models).providers);
const providerEntry = providers[providerId];
if (providerEntry && typeof providerEntry === "object" && !Array.isArray(providerEntry)) {
delete providerEntry.agentRuntime;
if (Array.isArray(providerEntry.models)) {
for (const model of providerEntry.models) {
if (
model &&
typeof model === "object" &&
(model.id === providerModelId ||
model.id === modelId ||
model.name === providerModelId ||
model.name === modelId)
) {
delete model.agentRuntime;
}
}
}
}
fs.rmSync(path.join(stateDir, "npm", "node_modules", "@openclaw", "codex"), {
recursive: true,
force: true,
});
fs.mkdirSync(stateDir, { recursive: true });
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");`;
}