fix(plugins): guard runtime facade activation (#59412)

* fix(plugins): guard runtime facade activation

* refactor(plugin-sdk): localize facade load policy

* fix(plugin-sdk): narrow facade activation guards

* fix(browser): keep cleanup helpers outside activation guard

* style(browser): apply formatter follow-ups

* chore(changelog): note plugin activation guard regressions

* fix(discord): keep cleanup thread unbinds outside activation guard

* fix(browser): fallback when trash exits non-zero
This commit is contained in:
Vincent Koc
2026-04-02 14:37:12 +09:00
committed by GitHub
parent ed6012eb5b
commit 52a018680d
27 changed files with 509 additions and 57 deletions

View File

@@ -49,6 +49,8 @@ export const GENERATED_PLUGIN_SDK_FACADES = [
{
subpath: "discord-runtime-surface",
source: pluginSource("discord", "runtime-api.js"),
// Runtime entrypoints should be blocked until the owning plugin is active.
loadPolicy: "activated",
exports: [
"addRoleDiscord",
"auditDiscordChannelPermissions",
@@ -159,6 +161,10 @@ export const GENERATED_PLUGIN_SDK_FACADES = [
{
subpath: "discord-thread-bindings",
source: pluginSource("discord", "runtime-api.js"),
loadPolicy: "activated",
directExports: {
unbindThreadBindingsBySessionKey: "./discord-maintenance.js",
},
exports: [
"autoBindSpawnedDiscordSubagent",
"createThreadBindingManager",
@@ -199,6 +205,7 @@ export const GENERATED_PLUGIN_SDK_FACADES = [
{
subpath: "browser",
source: pluginSource("browser", "runtime-api.js"),
loadPolicy: "activated",
exports: [
"browserHandlers",
"createBrowserPluginService",
@@ -210,12 +217,15 @@ export const GENERATED_PLUGIN_SDK_FACADES = [
{
subpath: "browser-runtime",
source: pluginSource("browser", "runtime-api.js"),
loadPolicy: "activated",
directExports: {
DEFAULT_AI_SNAPSHOT_MAX_CHARS: "./browser-config.js",
DEFAULT_BROWSER_EVALUATE_ENABLED: "./browser-config.js",
DEFAULT_OPENCLAW_BROWSER_COLOR: "./browser-config.js",
DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME: "./browser-config.js",
DEFAULT_UPLOAD_DIR: "./browser-config.js",
closeTrackedBrowserTabsForSessions: "./browser-maintenance.js",
movePathToTrash: "./browser-maintenance.js",
redactCdpUrl: "./browser-config.js",
resolveBrowserConfig: "./browser-config.js",
resolveBrowserControlAuth: "./browser-config.js",
@@ -441,6 +451,7 @@ export const GENERATED_PLUGIN_SDK_FACADES = [
{
subpath: "image-generation-runtime",
source: pluginSource("image-generation-core", "runtime-api.js"),
loadPolicy: "activated",
exports: [
"generateImage",
"listRuntimeImageGenerationProviders",
@@ -487,6 +498,7 @@ export const GENERATED_PLUGIN_SDK_FACADES = [
{
subpath: "media-understanding-runtime",
source: pluginSource("media-understanding-core", "runtime-api.js"),
loadPolicy: "activated",
exports: [
"describeImageFile",
"describeImageFileWithModel",
@@ -501,6 +513,7 @@ export const GENERATED_PLUGIN_SDK_FACADES = [
{
subpath: "memory-core-engine-runtime",
source: pluginSource("memory-core", "runtime-api.js"),
loadPolicy: "activated",
exports: [
"BuiltinMemoryEmbeddingProviderDoctorMetadata",
"getBuiltinMemoryEmbeddingProviderDoctorMetadata",
@@ -530,6 +543,7 @@ export const GENERATED_PLUGIN_SDK_FACADES = [
{
subpath: "line-runtime",
source: pluginSource("line", "runtime-api.js"),
loadPolicy: "activated",
runtimeApiPreExportsPath: runtimeApiSourcePath("line"),
typeExports: [
"Action",
@@ -558,6 +572,8 @@ export const GENERATED_PLUGIN_SDK_FACADES = [
{
subpath: "line-surface",
source: pluginSource("line", "runtime-api.js"),
// This surface is also used by passive reply normalization helpers.
// Keep it loadable without requiring the LINE plugin to be activated.
exports: [
"CardAction",
"createActionCard",
@@ -611,6 +627,7 @@ export const GENERATED_PLUGIN_SDK_FACADES = [
{
subpath: "matrix-runtime-surface",
source: pluginSource("matrix", "runtime-api.js"),
loadPolicy: "activated",
exports: ["resolveMatrixAccountStringValues", "setMatrixRuntime"],
},
{
@@ -850,6 +867,7 @@ export const GENERATED_PLUGIN_SDK_FACADES = [
{
subpath: "speech-runtime",
source: pluginSource("speech-core", "runtime-api.js"),
loadPolicy: "activated",
exports: [
"_test",
"buildTtsSystemPromptHint",
@@ -931,6 +949,7 @@ export const GENERATED_PLUGIN_SDK_FACADES = [
{
subpath: "slack-runtime-surface",
source: pluginSource("slack", "runtime-api.js"),
loadPolicy: "activated",
exports: [
"handleSlackAction",
"listSlackDirectoryGroupsLive",
@@ -1217,6 +1236,16 @@ export const GENERATED_PLUGIN_SDK_FACADES_BY_SUBPATH = Object.fromEntries(
GENERATED_PLUGIN_SDK_FACADES.map((entry) => [entry.subpath, entry]),
);
function resolveFacadeLoadPolicy(entry, sourcePath) {
// Keep loader policy next to the facade entry itself so additions stay local
// and mixed-source facades can opt into per-source behavior later if needed.
const sourcePolicy = entry.sourceLoadPolicy?.[sourcePath];
if (sourcePolicy) {
return sourcePolicy;
}
return entry.loadPolicy ?? "plain";
}
export const GENERATED_PLUGIN_SDK_FACADES_LABEL = "plugin-sdk-facades";
export const GENERATED_PLUGIN_SDK_FACADES_SCRIPT = "scripts/generate-plugin-sdk-facades.mjs";
export const GENERATED_PLUGIN_SDK_FACADE_TYPES_OUTPUT =
@@ -1515,25 +1544,40 @@ export function buildPluginSdkFacadeModule(entry, params = {}) {
}
}
if (valueExports.length) {
const runtimeImports = ["loadBundledPluginPublicSurfaceModuleSync"];
const runtimeImports = new Set();
if (needsLazyArrayHelper) {
runtimeImports.unshift("createLazyFacadeArrayValue");
runtimeImports.add("createLazyFacadeArrayValue");
}
if (needsLazyObjectHelper) {
runtimeImports.unshift("createLazyFacadeObjectValue");
runtimeImports.add("createLazyFacadeObjectValue");
}
lines.push(`import { ${runtimeImports.join(", ")} } from "./facade-runtime.js";`);
for (const sourcePath of listFacadeEntrySourcePaths(entry)) {
const loadPolicy = resolveFacadeLoadPolicy(entry, sourcePath);
runtimeImports.add(
loadPolicy === "activated"
? "loadActivatedBundledPluginPublicSurfaceModuleSync"
: "loadBundledPluginPublicSurfaceModuleSync",
);
}
lines.push(
`import { ${[...runtimeImports].toSorted((left, right) => left.localeCompare(right)).join(", ")} } from "./facade-runtime.js";`,
);
for (const [sourceIndex, sourcePath] of listFacadeEntrySourcePaths(entry).entries()) {
if (!valueExportsBySource.has(sourcePath)) {
continue;
}
const { dirName: sourceDirName, artifactBasename: sourceArtifactBasename } =
normalizeFacadeSourceParts(sourcePath);
const loadPolicy = resolveFacadeLoadPolicy(entry, sourcePath);
const loaderName =
loadPolicy === "activated"
? "loadActivatedBundledPluginPublicSurfaceModuleSync"
: "loadBundledPluginPublicSurfaceModuleSync";
const loaderSuffix = sourceIndex === 0 ? "" : String(sourceIndex + 1);
const moduleTypeName = sourceIndex === 0 ? "FacadeModule" : `FacadeModule${sourceIndex + 1}`;
lines.push("");
lines.push(`function loadFacadeModule${loaderSuffix}(): ${moduleTypeName} {`);
lines.push(` return loadBundledPluginPublicSurfaceModuleSync<${moduleTypeName}>({`);
lines.push(` return ${loaderName}<${moduleTypeName}>({`);
lines.push(` dirName: ${JSON.stringify(sourceDirName)},`);
lines.push(` artifactBasename: ${JSON.stringify(sourceArtifactBasename)},`);
lines.push(" });");