fix(plugins): preserve CLI environment and worker test routing (#161270)

* fix(plugins): preserve setup CLI environment and worker routing

* test(plugins): track CLI policy fixture roots from creation
This commit is contained in:
Peter Steinberger 2026-09-29 10:55:33 -07:00 • committed by GitHub
parent 74cbf1a117
commit 670e3fb4ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 101 additions and 3 deletions

View file

@ -139,7 +139,6 @@ const runtimeConsumers = [
},
...[
"src/agents/agent-command-local.test.ts",
"src/agents/simple-completion-runtime.plugin-scope.test.ts",
"src/agents/runtime-plugins.context-engine.integration.test.ts",
"src/agents/tool-surface-plan.provider-catalog.integration.test.ts",
].map((file) => ({
@ -149,6 +148,7 @@ const runtimeConsumers = [
dir: "src/agents",
})),
...[
"src/agents/simple-completion-runtime.plugin-scope.test.ts",
// Compiled catalog workers load the fixture's public SDK through built host artifacts.
"src/agents/prepared-model-catalog-worker.custody.integration.test.ts",
"src/agents/prepared-model-catalog-worker.integration.test.ts",

View file

@ -1,9 +1,20 @@
// Verifies metadata-backed setup registry descriptor lookup.
import { afterEach, describe, expect, it, vi } from "vitest";
import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js";
import { writeConfigMachineState } from "../state/config-machine-state-write.js";
import { closeOpenClawStateDatabaseByPath } from "../state/openclaw-state-db-cache.js";
import { resolveOpenClawStateSqlitePath } from "../state/openclaw-state-db.paths.js";
import { captureEnv, setTestEnvValue } from "../test-utils/env.js";
import { observeMainThreadSql } from "../test-utils/main-thread-sql-spies.test-support.js";
import {
clearBundledDiscoveryModeMemo,
prepareBundledDiscoveryMode,
} from "./bundled-discovery-state.js";
import { withPluginMetadataSnapshotScope } from "./current-plugin-metadata-snapshot.js";
import { setCurrentPluginMetadataSnapshot } from "./current-plugin-metadata.test-support.js";
import { resolveInstalledPluginIndexPolicyHash } from "./installed-plugin-index-policy.js";
import * as installedPluginIndex from "./installed-plugin-index.js";
import { createPluginCache, retirePluginCache, withPluginCache } from "./plugin-cache.js";
import { clearPluginMetadataLifecycleCaches } from "./plugin-metadata-lifecycle.js";
import {
projectPluginMetadataSnapshot,
@ -53,6 +64,17 @@ afterEach(() => {
vi.restoreAllMocks();
});
const tempDirs = useAutoCleanupTempDirTracker((cleanup) =>
afterEach(() => {
for (const stateDir of tempDirs.dirs) {
closeOpenClawStateDatabaseByPath(
resolveOpenClawStateSqlitePath({ OPENCLAW_STATE_DIR: stateDir }),
);
}
cleanup();
}),
);
function createCurrentSnapshot(params: {
manifestHash: string;
cliBackends: string[];
@ -79,6 +101,73 @@ function createCurrentSnapshot(params: {
}
describe("setup-registry descriptor lookup", () => {
it("keeps prepared CLI activation in the caller's machine-state root", async () => {
const { resolvePluginSetupCliBackendDescriptor, resolvePluginSetupCliBackendIds } =
await import("./setup-registry.runtime.js");
const compatRoot = tempDirs.make("openclaw-cli-compat-");
const strictRoot = tempDirs.make("openclaw-cli-strict-");
const envSnapshot = captureEnv(["OPENCLAW_STATE_DIR"]);
const cache = createPluginCache();
try {
setTestEnvValue("OPENCLAW_STATE_DIR", strictRoot);
const compatEnv = { ...process.env, OPENCLAW_STATE_DIR: compatRoot };
const strictEnv = { ...process.env };
writeConfigMachineState("plugins.bundledDiscovery", "compat", { env: compatEnv });
writeConfigMachineState("plugins.bundledDiscovery", "allowlist", { env: strictEnv });
clearBundledDiscoveryModeMemo();
await withPluginCache(cache, async () => {
const snapshot = createPluginMetadataSnapshotFixture({
plugins: [
{
id: "bundled-cli-owner",
origin: "bundled",
providers: ["fixture-provider"],
cliBackends: ["scope-cli"],
enabledByDefault: true,
},
],
});
snapshot.index.plugins[0]!.contributions = {
channels: [],
channelConfigs: [],
providers: snapshot.plugins[0]!.providers,
modelCatalogProviders: [],
modelSupportPrefixes: [],
modelSupportPatterns: [],
autoEnableProviderIds: [],
commandAliases: [],
contracts: {},
};
const config = { plugins: { allow: ["other-owner"] } };
await prepareBundledDiscoveryMode(compatEnv);
await prepareBundledDiscoveryMode(strictEnv);
const sql = observeMainThreadSql();
try {
sql.calibrate();
for (const env of [compatEnv, strictEnv, compatEnv]) {
const enabled = env === compatEnv;
const params = { config, env, metadataSnapshot: snapshot };
expect(
resolvePluginSetupCliBackendDescriptor({ ...params, backend: "scope-cli" }),
).toEqual(
enabled ? { pluginId: "bundled-cli-owner", backend: { id: "scope-cli" } } : undefined,
);
expect(resolvePluginSetupCliBackendIds(params)).toEqual(enabled ? ["scope-cli"] : []);
}
sql.expectIdle();
} finally {
sql.restore();
}
});
} finally {
try {
await retirePluginCache(cache);
} finally {
envSnapshot.restore();
}
}
});
it("evaluates activation only for owners of the requested CLI backend", async () => {
const { resolvePluginSetupCliBackendDescriptor } = await import("./setup-registry.runtime.js");
const snapshot = createPluginMetadataSnapshotFixture({

View file

@ -45,7 +45,7 @@ export function resolvePluginSetupCliBackendDescriptor(
// The immutable owner map preserves declaration order; only activation uses live policy.
const pluginId = snapshot.owners.cliBackends
.get(normalized)
?.find((id) => isInstalledPluginEnabled(snapshot.index, id, params.config));
?.find((id) => isInstalledPluginEnabled(snapshot.index, id, params.config, params.env));
const plugin = pluginId ? snapshot.byPluginId.get(pluginId) : undefined;
if (!plugin) {
return undefined;
@ -63,7 +63,8 @@ export function resolvePluginSetupCliBackendIds(
const snapshot = resolveSetupCliBackendSnapshot(params);
return snapshot.plugins.flatMap((plugin) => {
const ids = plugin.cliBackends.concat(plugin.setup?.cliBackends ?? []);
return ids.length > 0 && isInstalledPluginEnabled(snapshot.index, plugin.id, params.config)
return ids.length > 0 &&
isInstalledPluginEnabled(snapshot.index, plugin.id, params.config, params.env)
? ids
: [];
});

View file

@ -671,6 +671,12 @@ export const databaseWorkerCoreTestFiles = [
"src/tts/tts-summary.static-catalog.test.ts",
"src/tts/tts-summary.selection.test.ts",
"src/agents/prepared-model-catalog.resources.test.ts",
"src/agents/embedded-agent-runner/compaction-runtime-admission.test.ts",
"src/agents/tools/pdf-tool.resources.test.ts",
"src/media-understanding/image.resources.test.ts",
"src/tts/tts-summary.resources.test.ts",
"src/agents/simple-completion-runtime.plugin-scope.test.ts",
"src/agents/tools/pdf-tool.static-runtime.test.ts",
"src/agents/prepared-model-catalog-worker.chat-metadata.integration.test.ts",
"src/agents/prepared-model-catalog-worker.custody.integration.test.ts",
"src/agents/prepared-model-catalog-worker.directory.test.ts",

View file

@ -1,10 +1,12 @@
// Vitest media understanding config wires the media understanding test shard.
import { databaseWorkerCoreTestFiles } from "./vitest.database-worker-core-paths.mjs";
import { createScopedVitestConfig } from "./vitest.scoped-config.ts";
export function createMediaUnderstandingVitestConfig(env?: Record<string, string | undefined>) {
return createScopedVitestConfig(["src/media-understanding/**/*.test.ts"], {
dir: "src",
env,
exclude: databaseWorkerCoreTestFiles,
intersectIncludeFile: true,
includeOpenClawRuntimeSetup: false,
name: "media-understanding",