mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 18:00:54 +00:00
fix(plugins): preserve source activation config
This commit is contained in:
@@ -23,6 +23,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Hooks/Slack: standardize shared message hook routing fields (`threadId` / `replyToId`) and stop Slack outbound delivery from re-running `message_sending` inside the channel adapter, so plugins like thread-ownership make one outbound routing decision per reply. Thanks @vincentkoc.
|
- Hooks/Slack: standardize shared message hook routing fields (`threadId` / `replyToId`) and stop Slack outbound delivery from re-running `message_sending` inside the channel adapter, so plugins like thread-ownership make one outbound routing decision per reply. Thanks @vincentkoc.
|
||||||
- Auto-reply/media: share one run-scoped reply media context between streamed block delivery and final payload filtering, so a local `MEDIA:` attachment is staged once and duplicate media sends are suppressed reliably. (#68111) Thanks @ayeshakhalid192007-dev.
|
- Auto-reply/media: share one run-scoped reply media context between streamed block delivery and final payload filtering, so a local `MEDIA:` attachment is staged once and duplicate media sends are suppressed reliably. (#68111) Thanks @ayeshakhalid192007-dev.
|
||||||
- Plugins/gateway hooks: expose startup config, workspace dir, and a live cron getter on the typed `gateway_start` hook, and move memory-core managed dreaming off the internal `gateway:startup` bridge so cron reconciliation stays on the public plugin hook path. Thanks @vincentkoc.
|
- Plugins/gateway hooks: expose startup config, workspace dir, and a live cron getter on the typed `gateway_start` hook, and move memory-core managed dreaming off the internal `gateway:startup` bridge so cron reconciliation stays on the public plugin hook path. Thanks @vincentkoc.
|
||||||
|
- Plugins/config: read plugin trust decisions from the source config snapshot when a resolved runtime snapshot is active, so `plugins.allow` remains enforced and `doctor`/gateway startup no longer warn that the allowlist is empty when it is configured. Fixes #70161. Also fixes #70141.
|
||||||
- Gateway/restart: preserve group and channel chat context when resuming an agent turn after a Gateway restart, so continuation replies keep the same prompt, routing, and tool-status behavior as the original conversation.
|
- Gateway/restart: preserve group and channel chat context when resuming an agent turn after a Gateway restart, so continuation replies keep the same prompt, routing, and tool-status behavior as the original conversation.
|
||||||
- Gateway/pairing: shared-secret loopback CLI clients now silently auto-approve `metadata-upgrade` pairing (platform / device family refresh) instead of being disconnected with `1008 pairing required`. This matches the scope-upgrade and role-upgrade behavior added in #69431 and unblocks non-interactive CLI automation when a paired-device record has a stale platform string (e.g. device key replicated across hosts, install migrated between OSes, or platform-string format changed between OpenClaw versions). Browser / Control-UI clients keep the existing approval-required flow for metadata changes.
|
- Gateway/pairing: shared-secret loopback CLI clients now silently auto-approve `metadata-upgrade` pairing (platform / device family refresh) instead of being disconnected with `1008 pairing required`. This matches the scope-upgrade and role-upgrade behavior added in #69431 and unblocks non-interactive CLI automation when a paired-device record has a stale platform string (e.g. device key replicated across hosts, install migrated between OSes, or platform-string format changed between OpenClaw versions). Browser / Control-UI clients keep the existing approval-required flow for metadata changes.
|
||||||
- Gateway/pairing: treat any forwarded-header evidence (`Forwarded`, `X-Forwarded-*`, or `X-Real-IP`) as proxied WebSocket traffic before pairing locality checks, so reverse-proxy topologies cannot use the loopback shared-secret helper auto-pairing path.
|
- Gateway/pairing: treat any forwarded-header evidence (`Forwarded`, `X-Forwarded-*`, or `X-Real-IP`) as proxied WebSocket traffic before pairing locality checks, so reverse-proxy topologies cannot use the loopback shared-secret helper auto-pairing path.
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import JSON5 from "json5";
|
|||||||
import { resolveConfigPath } from "../config/paths.js";
|
import { resolveConfigPath } from "../config/paths.js";
|
||||||
import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
|
import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
|
||||||
import { configMayNeedPluginAutoEnable } from "../config/plugin-auto-enable.shared.js";
|
import { configMayNeedPluginAutoEnable } from "../config/plugin-auto-enable.shared.js";
|
||||||
import { getRuntimeConfigSnapshot } from "../config/runtime-snapshot.js";
|
import {
|
||||||
|
getRuntimeConfigSnapshot,
|
||||||
|
getRuntimeConfigSourceSnapshot,
|
||||||
|
} from "../config/runtime-snapshot.js";
|
||||||
import type { OpenClawConfig } from "../config/types.js";
|
import type { OpenClawConfig } from "../config/types.js";
|
||||||
import { resolveBundledPluginsDir } from "../plugins/bundled-dir.js";
|
import { resolveBundledPluginsDir } from "../plugins/bundled-dir.js";
|
||||||
import {
|
import {
|
||||||
@@ -66,6 +69,10 @@ function readFacadeBoundaryConfigSafely(): {
|
|||||||
cacheKey?: string;
|
cacheKey?: string;
|
||||||
} {
|
} {
|
||||||
try {
|
try {
|
||||||
|
const sourceSnapshot = getRuntimeConfigSourceSnapshot();
|
||||||
|
if (sourceSnapshot) {
|
||||||
|
return { rawConfig: sourceSnapshot };
|
||||||
|
}
|
||||||
const runtimeSnapshot = getRuntimeConfigSnapshot();
|
const runtimeSnapshot = getRuntimeConfigSnapshot();
|
||||||
if (runtimeSnapshot) {
|
if (runtimeSnapshot) {
|
||||||
return { rawConfig: runtimeSnapshot };
|
return { rawConfig: runtimeSnapshot };
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } from "../config/
|
|||||||
import { createPluginActivationSource, normalizePluginsConfig } from "../plugins/config-state.js";
|
import { createPluginActivationSource, normalizePluginsConfig } from "../plugins/config-state.js";
|
||||||
import { clearPluginDiscoveryCache } from "../plugins/discovery.js";
|
import { clearPluginDiscoveryCache } from "../plugins/discovery.js";
|
||||||
import { clearPluginManifestRegistryCache } from "../plugins/manifest-registry.js";
|
import { clearPluginManifestRegistryCache } from "../plugins/manifest-registry.js";
|
||||||
|
import {
|
||||||
|
resetFacadeActivationCheckRuntimeStateForTest,
|
||||||
|
resolveBundledPluginPublicSurfaceAccess as resolveActivationCheckBundledPluginPublicSurfaceAccess,
|
||||||
|
} from "./facade-activation-check.runtime.js";
|
||||||
import {
|
import {
|
||||||
__testing,
|
__testing,
|
||||||
canLoadActivatedBundledPluginPublicSurface,
|
canLoadActivatedBundledPluginPublicSurface,
|
||||||
@@ -34,6 +38,7 @@ afterEach(() => {
|
|||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
clearRuntimeConfigSnapshot();
|
clearRuntimeConfigSnapshot();
|
||||||
resetFacadeRuntimeStateForTest();
|
resetFacadeRuntimeStateForTest();
|
||||||
|
resetFacadeActivationCheckRuntimeStateForTest();
|
||||||
clearPluginDiscoveryCache();
|
clearPluginDiscoveryCache();
|
||||||
clearPluginManifestRegistryCache();
|
clearPluginManifestRegistryCache();
|
||||||
vi.doUnmock("../plugins/manifest-registry.js");
|
vi.doUnmock("../plugins/manifest-registry.js");
|
||||||
@@ -381,4 +386,52 @@ describe("plugin-sdk facade runtime", () => {
|
|||||||
}),
|
}),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("prefers the source runtime snapshot for facade activation checks", () => {
|
||||||
|
const dir = createTempDirSync("openclaw-facade-source-snapshot-");
|
||||||
|
fs.mkdirSync(path.join(dir, "demo"), { recursive: true });
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(dir, "demo", "runtime-api.js"),
|
||||||
|
'export const marker = "source-snapshot";\n',
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(dir, "demo", "openclaw.plugin.json"),
|
||||||
|
JSON.stringify({
|
||||||
|
id: "demo",
|
||||||
|
}),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = dir;
|
||||||
|
setRuntimeConfigSnapshot(
|
||||||
|
{
|
||||||
|
plugins: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
entries: {
|
||||||
|
demo: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveActivationCheckBundledPluginPublicSurfaceAccess({
|
||||||
|
dirName: "demo",
|
||||||
|
artifactBasename: "runtime-api.js",
|
||||||
|
location: {
|
||||||
|
modulePath: path.join(dir, "demo", "runtime-api.js"),
|
||||||
|
boundaryRoot: dir,
|
||||||
|
},
|
||||||
|
sourceExtensionsRoot: dir,
|
||||||
|
resolutionKey: "source-snapshot-demo",
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
allowed: true,
|
||||||
|
pluginId: "demo",
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
19
src/plugins/activation-source-config.ts
Normal file
19
src/plugins/activation-source-config.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import {
|
||||||
|
getRuntimeConfigSnapshot,
|
||||||
|
getRuntimeConfigSourceSnapshot,
|
||||||
|
} from "../config/runtime-snapshot.js";
|
||||||
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||||
|
|
||||||
|
export function resolvePluginActivationSourceConfig(params: {
|
||||||
|
config?: OpenClawConfig;
|
||||||
|
activationSourceConfig?: OpenClawConfig;
|
||||||
|
}): OpenClawConfig {
|
||||||
|
if (params.activationSourceConfig !== undefined) {
|
||||||
|
return params.activationSourceConfig;
|
||||||
|
}
|
||||||
|
const sourceSnapshot = getRuntimeConfigSourceSnapshot();
|
||||||
|
if (sourceSnapshot && params.config === getRuntimeConfigSnapshot()) {
|
||||||
|
return sourceSnapshot;
|
||||||
|
}
|
||||||
|
return params.config ?? {};
|
||||||
|
}
|
||||||
@@ -3,6 +3,10 @@ import path from "node:path";
|
|||||||
import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
|
import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import { listAgentHarnessIds } from "../agents/harness/registry.js";
|
import { listAgentHarnessIds } from "../agents/harness/registry.js";
|
||||||
import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
|
import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
|
||||||
|
import {
|
||||||
|
clearRuntimeConfigSnapshot,
|
||||||
|
setRuntimeConfigSnapshot,
|
||||||
|
} from "../config/runtime-snapshot.js";
|
||||||
import { getContextEngineFactory, listContextEngineIds } from "../context-engine/registry.js";
|
import { getContextEngineFactory, listContextEngineIds } from "../context-engine/registry.js";
|
||||||
import {
|
import {
|
||||||
clearInternalHooks,
|
clearInternalHooks,
|
||||||
@@ -822,6 +826,7 @@ function expectEscapingEntryRejected(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
clearRuntimeConfigSnapshot();
|
||||||
resetPluginLoaderTestStateForTest();
|
resetPluginLoaderTestStateForTest();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -5758,6 +5763,65 @@ module.exports = {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses the source runtime snapshot allowlist for plugin trust checks", () => {
|
||||||
|
useNoBundledPlugins();
|
||||||
|
const stateDir = makeTempDir();
|
||||||
|
withEnv({ OPENCLAW_STATE_DIR: stateDir }, () => {
|
||||||
|
const globalDir = path.join(stateDir, "extensions", "trusted-plugin");
|
||||||
|
mkdirSafe(globalDir);
|
||||||
|
writePlugin({
|
||||||
|
id: "trusted-plugin",
|
||||||
|
body: simplePluginBody("trusted-plugin"),
|
||||||
|
dir: globalDir,
|
||||||
|
filename: "index.cjs",
|
||||||
|
});
|
||||||
|
const untrustedDir = path.join(stateDir, "extensions", "untrusted-plugin");
|
||||||
|
mkdirSafe(untrustedDir);
|
||||||
|
writePlugin({
|
||||||
|
id: "untrusted-plugin",
|
||||||
|
body: simplePluginBody("untrusted-plugin"),
|
||||||
|
dir: untrustedDir,
|
||||||
|
filename: "index.cjs",
|
||||||
|
});
|
||||||
|
const runtimeConfig = {
|
||||||
|
plugins: {
|
||||||
|
enabled: true,
|
||||||
|
allow: ["runtime-added-plugin"],
|
||||||
|
},
|
||||||
|
} satisfies PluginLoadConfig;
|
||||||
|
const sourceConfig = {
|
||||||
|
plugins: {
|
||||||
|
enabled: true,
|
||||||
|
allow: ["trusted-plugin"],
|
||||||
|
},
|
||||||
|
} satisfies PluginLoadConfig;
|
||||||
|
setRuntimeConfigSnapshot(runtimeConfig, sourceConfig);
|
||||||
|
|
||||||
|
const warnings: string[] = [];
|
||||||
|
const registry = loadOpenClawPlugins({
|
||||||
|
cache: false,
|
||||||
|
logger: createWarningLogger(warnings),
|
||||||
|
config: runtimeConfig,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(registry.plugins.find((entry) => entry.id === "trusted-plugin")?.status).toBe(
|
||||||
|
"loaded",
|
||||||
|
);
|
||||||
|
expect(registry.plugins.find((entry) => entry.id === "untrusted-plugin")).toMatchObject({
|
||||||
|
status: "disabled",
|
||||||
|
error: "not in allowlist",
|
||||||
|
});
|
||||||
|
expect(warnings.some((message) => message.includes("plugins.allow is empty"))).toBe(false);
|
||||||
|
expect(
|
||||||
|
warnings.some(
|
||||||
|
(message) =>
|
||||||
|
message.includes("trusted-plugin") &&
|
||||||
|
message.includes("loaded without install/load-path provenance"),
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
{
|
{
|
||||||
name: "rejects plugin entry files that escape plugin root via symlink",
|
name: "rejects plugin entry files that escape plugin root via symlink",
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import {
|
|||||||
restoreDetachedTaskLifecycleRuntimeRegistration,
|
restoreDetachedTaskLifecycleRuntimeRegistration,
|
||||||
} from "../tasks/detached-task-runtime-state.js";
|
} from "../tasks/detached-task-runtime-state.js";
|
||||||
import { resolveUserPath } from "../utils.js";
|
import { resolveUserPath } from "../utils.js";
|
||||||
|
import { resolvePluginActivationSourceConfig } from "./activation-source-config.js";
|
||||||
import { buildPluginApi } from "./api-builder.js";
|
import { buildPluginApi } from "./api-builder.js";
|
||||||
import { inspectBundleMcpRuntimeSupport } from "./bundle-mcp.js";
|
import { inspectBundleMcpRuntimeSupport } from "./bundle-mcp.js";
|
||||||
import {
|
import {
|
||||||
@@ -833,11 +834,18 @@ function hasExplicitCompatibilityInputs(options: PluginLoadOptions): boolean {
|
|||||||
function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) {
|
function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) {
|
||||||
const env = options.env ?? process.env;
|
const env = options.env ?? process.env;
|
||||||
const cfg = applyTestPluginDefaults(options.config ?? {}, env);
|
const cfg = applyTestPluginDefaults(options.config ?? {}, env);
|
||||||
const activationSourceConfig = options.activationSourceConfig ?? options.config ?? {};
|
const activationSourceConfig = resolvePluginActivationSourceConfig({
|
||||||
|
config: options.config,
|
||||||
|
activationSourceConfig: options.activationSourceConfig,
|
||||||
|
});
|
||||||
const normalized = normalizePluginsConfig(cfg.plugins);
|
const normalized = normalizePluginsConfig(cfg.plugins);
|
||||||
const activationSource = createPluginActivationSource({
|
const activationSource = createPluginActivationSource({
|
||||||
config: activationSourceConfig,
|
config: activationSourceConfig,
|
||||||
});
|
});
|
||||||
|
const trustNormalized = mergeTrustPluginConfigFromActivationSource({
|
||||||
|
normalized,
|
||||||
|
activationSource,
|
||||||
|
});
|
||||||
const onlyPluginIds = normalizePluginIdScope(options.onlyPluginIds);
|
const onlyPluginIds = normalizePluginIdScope(options.onlyPluginIds);
|
||||||
const includeSetupOnlyChannelPlugins = options.includeSetupOnlyChannelPlugins === true;
|
const includeSetupOnlyChannelPlugins = options.includeSetupOnlyChannelPlugins === true;
|
||||||
const forceSetupOnlyChannelPlugins = options.forceSetupOnlyChannelPlugins === true;
|
const forceSetupOnlyChannelPlugins = options.forceSetupOnlyChannelPlugins === true;
|
||||||
@@ -848,7 +856,7 @@ function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) {
|
|||||||
const coreGatewayMethodNames = Object.keys(options.coreGatewayHandlers ?? {}).toSorted();
|
const coreGatewayMethodNames = Object.keys(options.coreGatewayHandlers ?? {}).toSorted();
|
||||||
const cacheKey = buildCacheKey({
|
const cacheKey = buildCacheKey({
|
||||||
workspaceDir: options.workspaceDir,
|
workspaceDir: options.workspaceDir,
|
||||||
plugins: normalized,
|
plugins: trustNormalized,
|
||||||
activationMetadataKey: buildActivationMetadataHash({
|
activationMetadataKey: buildActivationMetadataHash({
|
||||||
activationSource,
|
activationSource,
|
||||||
autoEnabledReasons: options.autoEnabledReasons ?? {},
|
autoEnabledReasons: options.autoEnabledReasons ?? {},
|
||||||
@@ -868,7 +876,7 @@ function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) {
|
|||||||
return {
|
return {
|
||||||
env,
|
env,
|
||||||
cfg,
|
cfg,
|
||||||
normalized,
|
normalized: trustNormalized,
|
||||||
activationSourceConfig,
|
activationSourceConfig,
|
||||||
activationSource,
|
activationSource,
|
||||||
autoEnabledReasons: options.autoEnabledReasons ?? {},
|
autoEnabledReasons: options.autoEnabledReasons ?? {},
|
||||||
@@ -884,6 +892,44 @@ function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mergeTrustPluginConfigFromActivationSource(params: {
|
||||||
|
normalized: NormalizedPluginsConfig;
|
||||||
|
activationSource: PluginActivationConfigSource;
|
||||||
|
}): NormalizedPluginsConfig {
|
||||||
|
const source = params.activationSource.plugins;
|
||||||
|
const allow = mergePluginTrustList(params.normalized.allow, source.allow);
|
||||||
|
const deny = mergePluginTrustList(params.normalized.deny, source.deny);
|
||||||
|
const loadPaths = mergePluginTrustList(params.normalized.loadPaths, source.loadPaths);
|
||||||
|
if (
|
||||||
|
allow === params.normalized.allow &&
|
||||||
|
deny === params.normalized.deny &&
|
||||||
|
loadPaths === params.normalized.loadPaths
|
||||||
|
) {
|
||||||
|
return params.normalized;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...params.normalized,
|
||||||
|
allow,
|
||||||
|
deny,
|
||||||
|
loadPaths,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergePluginTrustList(runtimeList: string[], sourceList: readonly string[]): string[] {
|
||||||
|
if (sourceList.length === 0) {
|
||||||
|
return runtimeList;
|
||||||
|
}
|
||||||
|
const merged = [...runtimeList];
|
||||||
|
const seen = new Set(merged);
|
||||||
|
for (const entry of sourceList) {
|
||||||
|
if (!seen.has(entry)) {
|
||||||
|
merged.push(entry);
|
||||||
|
seen.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return merged.length === runtimeList.length ? runtimeList : merged;
|
||||||
|
}
|
||||||
|
|
||||||
function getCompatibleActivePluginRegistry(
|
function getCompatibleActivePluginRegistry(
|
||||||
options: PluginLoadOptions = {},
|
options: PluginLoadOptions = {},
|
||||||
): PluginRegistry | undefined {
|
): PluginRegistry | undefined {
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ const resolveDefaultAgentIdMock = vi.fn<
|
|||||||
|
|
||||||
let resolvePluginRuntimeLoadContext: typeof import("./load-context.js").resolvePluginRuntimeLoadContext;
|
let resolvePluginRuntimeLoadContext: typeof import("./load-context.js").resolvePluginRuntimeLoadContext;
|
||||||
let buildPluginRuntimeLoadOptions: typeof import("./load-context.js").buildPluginRuntimeLoadOptions;
|
let buildPluginRuntimeLoadOptions: typeof import("./load-context.js").buildPluginRuntimeLoadOptions;
|
||||||
|
let clearRuntimeConfigSnapshot: typeof import("../../config/runtime-snapshot.js").clearRuntimeConfigSnapshot;
|
||||||
|
let setRuntimeConfigSnapshot: typeof import("../../config/runtime-snapshot.js").setRuntimeConfigSnapshot;
|
||||||
|
|
||||||
vi.mock("../../config/config.js", () => ({
|
vi.mock("../../config/config.js", () => ({
|
||||||
loadConfig: loadConfigMock,
|
loadConfig: loadConfigMock,
|
||||||
@@ -29,6 +31,8 @@ vi.mock("../../agents/agent-scope.js", () => ({
|
|||||||
describe("resolvePluginRuntimeLoadContext", () => {
|
describe("resolvePluginRuntimeLoadContext", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
vi.resetModules();
|
vi.resetModules();
|
||||||
|
({ clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } =
|
||||||
|
await import("../../config/runtime-snapshot.js"));
|
||||||
({ resolvePluginRuntimeLoadContext, buildPluginRuntimeLoadOptions } =
|
({ resolvePluginRuntimeLoadContext, buildPluginRuntimeLoadOptions } =
|
||||||
await import("./load-context.js"));
|
await import("./load-context.js"));
|
||||||
loadConfigMock.mockReset();
|
loadConfigMock.mockReset();
|
||||||
@@ -42,6 +46,7 @@ describe("resolvePluginRuntimeLoadContext", () => {
|
|||||||
changes: [],
|
changes: [],
|
||||||
autoEnabledReasons: {},
|
autoEnabledReasons: {},
|
||||||
}));
|
}));
|
||||||
|
clearRuntimeConfigSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("builds the runtime plugin load context from the auto-enabled config", () => {
|
it("builds the runtime plugin load context from the auto-enabled config", () => {
|
||||||
@@ -88,6 +93,27 @@ describe("resolvePluginRuntimeLoadContext", () => {
|
|||||||
expect(resolveAgentWorkspaceDirMock).toHaveBeenCalledWith(resolvedConfig, "default");
|
expect(resolveAgentWorkspaceDirMock).toHaveBeenCalledWith(resolvedConfig, "default");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses the source runtime snapshot for plugin activation source config", () => {
|
||||||
|
const runtimeConfig = { plugins: {} };
|
||||||
|
const sourceConfig = {
|
||||||
|
plugins: {
|
||||||
|
allow: ["trusted-plugin"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
setRuntimeConfigSnapshot(runtimeConfig, sourceConfig);
|
||||||
|
loadConfigMock.mockReturnValue(runtimeConfig);
|
||||||
|
|
||||||
|
const context = resolvePluginRuntimeLoadContext();
|
||||||
|
|
||||||
|
expect(context.rawConfig).toBe(runtimeConfig);
|
||||||
|
expect(context.activationSourceConfig).toBe(sourceConfig);
|
||||||
|
expect(applyPluginAutoEnableMock).toHaveBeenCalledWith({
|
||||||
|
config: runtimeConfig,
|
||||||
|
env: process.env,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("builds plugin load options from the shared runtime context", () => {
|
it("builds plugin load options from the shared runtime context", () => {
|
||||||
const context = resolvePluginRuntimeLoadContext({
|
const context = resolvePluginRuntimeLoadContext({
|
||||||
config: { plugins: {} },
|
config: { plugins: {} },
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { loadConfig } from "../../config/config.js";
|
|||||||
import { applyPluginAutoEnable } from "../../config/plugin-auto-enable.js";
|
import { applyPluginAutoEnable } from "../../config/plugin-auto-enable.js";
|
||||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||||
import { createSubsystemLogger } from "../../logging.js";
|
import { createSubsystemLogger } from "../../logging.js";
|
||||||
|
import { resolvePluginActivationSourceConfig } from "../activation-source-config.js";
|
||||||
import type { PluginLoadOptions } from "../loader.js";
|
import type { PluginLoadOptions } from "../loader.js";
|
||||||
import type { PluginLogger } from "../types.js";
|
import type { PluginLogger } from "../types.js";
|
||||||
|
|
||||||
@@ -45,6 +46,10 @@ export function resolvePluginRuntimeLoadContext(
|
|||||||
): PluginRuntimeLoadContext {
|
): PluginRuntimeLoadContext {
|
||||||
const env = options?.env ?? process.env;
|
const env = options?.env ?? process.env;
|
||||||
const rawConfig = options?.config ?? loadConfig();
|
const rawConfig = options?.config ?? loadConfig();
|
||||||
|
const activationSourceConfig = resolvePluginActivationSourceConfig({
|
||||||
|
config: rawConfig,
|
||||||
|
activationSourceConfig: options?.activationSourceConfig,
|
||||||
|
});
|
||||||
const autoEnabled = applyPluginAutoEnable({ config: rawConfig, env });
|
const autoEnabled = applyPluginAutoEnable({ config: rawConfig, env });
|
||||||
const config = autoEnabled.config;
|
const config = autoEnabled.config;
|
||||||
const workspaceDir =
|
const workspaceDir =
|
||||||
@@ -52,7 +57,7 @@ export function resolvePluginRuntimeLoadContext(
|
|||||||
return {
|
return {
|
||||||
rawConfig,
|
rawConfig,
|
||||||
config,
|
config,
|
||||||
activationSourceConfig: options?.activationSourceConfig ?? rawConfig,
|
activationSourceConfig,
|
||||||
autoEnabledReasons: autoEnabled.autoEnabledReasons,
|
autoEnabledReasons: autoEnabled.autoEnabledReasons,
|
||||||
workspaceDir,
|
workspaceDir,
|
||||||
env,
|
env,
|
||||||
|
|||||||
Reference in New Issue
Block a user