fix(plugins): alias bundled public surfaces in source loaders

This commit is contained in:
Vincent Koc
2026-05-03 21:29:14 -07:00
parent b2f0f67e0d
commit dadf0005ec
3 changed files with 201 additions and 0 deletions

View File

@@ -48,6 +48,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Agents/session status: keep semantic `session_status({ sessionKey: "current" })` on the live run session even before that run has a persisted session-store entry, instead of falling back to the sandbox policy key. Thanks @vincentkoc.
- QA/Slack: resolve bundled official plugin public-surface package aliases during source-mode QA runs, so release Slack live validation can load `@openclaw/slack/api.js` without workspace symlinks. Thanks @vincentkoc.
- Codex: pass the live run session key into app-server dynamic tools when sandbox policy uses a separate session key, so `session_status({ sessionKey: "current" })` reports the active run instead of the sandbox policy key. Thanks @vincentkoc.
- Plugins/tools: mark manifest-optional sibling tools as optional even when they come from a shared non-optional factory, so cached/status/MCP metadata keeps opt-in tool policy accurate. Thanks @vincentkoc.
- Matrix: keep `streaming.progress.toolProgress` scoped to progress draft mode, so partial and quiet Matrix previews do not lose tool progress unless `streaming.preview.toolProgress` is disabled. Thanks @vincentkoc.

View File

@@ -198,6 +198,39 @@ function createPluginSdkAliasTargetFixture(params?: {
};
}
function createBundledPluginPackagePublicSurfaceAliasFixture() {
const fixture = createPluginSdkAliasTargetFixture();
const extensionRoot = path.join(fixture.fixture.root, bundledPluginRoot("slack"));
const distExtensionRoot = path.join(fixture.fixture.root, "dist", "extensions", "slack");
mkdirSafeDir(extensionRoot);
mkdirSafeDir(distExtensionRoot);
fs.writeFileSync(
path.join(extensionRoot, "package.json"),
JSON.stringify({ name: "@openclaw/slack", type: "module" }, null, 2),
"utf-8",
);
const sourceApiPath = path.join(extensionRoot, "api.ts");
const sourceRuntimeApiPath = path.join(extensionRoot, "runtime-api.ts");
const distApiPath = path.join(distExtensionRoot, "api.js");
const distRuntimeApiPath = path.join(distExtensionRoot, "runtime-api.js");
fs.writeFileSync(sourceApiPath, "export const slackApi = 'source';\n", "utf-8");
fs.writeFileSync(sourceRuntimeApiPath, "export const slackRuntimeApi = 'source';\n", "utf-8");
fs.writeFileSync(distApiPath, "export const slackApi = 'dist';\n", "utf-8");
fs.writeFileSync(distRuntimeApiPath, "export const slackRuntimeApi = 'dist';\n", "utf-8");
fs.writeFileSync(
path.join(extensionRoot, "internal.ts"),
"export const internal = true;\n",
"utf-8",
);
return {
...fixture,
distApiPath,
distRuntimeApiPath,
sourceApiPath,
sourceRuntimeApiPath,
};
}
function writePluginEntry(root: string, relativePath: string) {
const pluginEntry = path.join(root, relativePath);
fs.mkdirSync(path.dirname(pluginEntry), { recursive: true });
@@ -777,6 +810,47 @@ describe("plugin sdk alias helpers", () => {
});
});
it("aliases bundled plugin package public surfaces for source plugin transforms", () => {
const { fixture, sourceApiPath, sourceRuntimeApiPath } =
createBundledPluginPackagePublicSurfaceAliasFixture();
const sourcePluginEntry = writePluginEntry(
fixture.root,
bundledPluginFile("qa-lab", "src/live-transports/slack/slack-live.runtime.ts"),
);
const aliases = withEnv({ NODE_ENV: undefined }, () =>
buildPluginLoaderAliasMap(sourcePluginEntry),
);
expect(fs.realpathSync(aliases["@openclaw/slack/api.js"] ?? "")).toBe(
fs.realpathSync(sourceApiPath),
);
expect(fs.realpathSync(aliases["@openclaw/slack/runtime-api.js"] ?? "")).toBe(
fs.realpathSync(sourceRuntimeApiPath),
);
expect(aliases["@openclaw/slack/internal.js"]).toBeUndefined();
});
it("aliases bundled plugin package public surfaces to dist when dist resolution is requested", () => {
const { fixture, distApiPath, distRuntimeApiPath } =
createBundledPluginPackagePublicSurfaceAliasFixture();
const sourcePluginEntry = writePluginEntry(
fixture.root,
bundledPluginFile("qa-lab", "src/live-transports/slack/slack-live.runtime.ts"),
);
const aliases = withEnv({ NODE_ENV: undefined }, () =>
buildPluginLoaderAliasMap(sourcePluginEntry, undefined, undefined, "dist"),
);
expect(fs.realpathSync(aliases["@openclaw/slack/api.js"] ?? "")).toBe(
fs.realpathSync(distApiPath),
);
expect(fs.realpathSync(aliases["@openclaw/slack/runtime-api.js"] ?? "")).toBe(
fs.realpathSync(distRuntimeApiPath),
);
});
it("falls back to source plugin-sdk subpath aliases when dist chunks are stale", () => {
const fixture = createPluginSdkAliasFixture({
srcFile: "provider-entry.ts",

View File

@@ -274,6 +274,7 @@ const PLUGIN_SDK_SOURCE_CANDIDATE_EXTENSIONS = [
".cts",
".cjs",
] as const;
const BUNDLED_PLUGIN_PUBLIC_SURFACE_SOURCE_PATTERN = /^(?:api|runtime-api|test-api|.+-api)$/u;
const JS_STATIC_RELATIVE_DEPENDENCY_PATTERN =
/(?:\bfrom\s*["']|\bimport\s*\(\s*["']|\brequire\s*\(\s*["'])(\.{1,2}\/[^"']+)["']/g;
@@ -320,6 +321,125 @@ function readPrivateLocalOnlyPluginSdkSubpaths(packageRoot: string): string[] {
}
}
function readBundledPluginPackageName(packageJsonPath: string): string | null {
try {
const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")) as { name?: unknown };
const name = typeof parsed.name === "string" ? parsed.name.trim() : "";
return name.startsWith("@openclaw/") ? name : null;
} catch {
return null;
}
}
function listBundledPluginPublicSurfaceSourceBasenames(extensionSourceRoot: string): string[] {
try {
return fs
.readdirSync(extensionSourceRoot, { withFileTypes: true })
.filter((entry) => entry.isFile())
.map((entry) => entry.name)
.flatMap((fileName) => {
const ext = PLUGIN_SDK_SOURCE_CANDIDATE_EXTENSIONS.find((candidateExt) =>
fileName.endsWith(candidateExt),
);
if (!ext) {
return [];
}
const basename = fileName.slice(0, -ext.length);
return BUNDLED_PLUGIN_PUBLIC_SURFACE_SOURCE_PATTERN.test(basename) ? [basename] : [];
})
.toSorted();
} catch {
return [];
}
}
function resolveBundledPluginPublicSurfaceAliasTarget(params: {
packageRoot: string;
dirName: string;
basename: string;
orderedKinds: PluginSdkAliasCandidateKind[];
}): string | null {
for (const kind of params.orderedKinds) {
if (kind === "dist") {
const candidate = path.join(
params.packageRoot,
"dist",
"extensions",
params.dirName,
`${params.basename}.js`,
);
if (fs.existsSync(candidate)) {
return candidate;
}
continue;
}
for (const ext of PLUGIN_SDK_SOURCE_CANDIDATE_EXTENSIONS) {
const candidate = path.join(
params.packageRoot,
"extensions",
params.dirName,
`${params.basename}${ext}`,
);
if (fs.existsSync(candidate)) {
return candidate;
}
}
}
return null;
}
function resolveBundledPluginPackagePublicSurfaceAliasMap(params: {
modulePath: string;
argv1?: string;
moduleUrl?: string;
pluginSdkResolution: PluginSdkResolutionPreference;
}): Record<string, string> {
const packageRoot = resolveLoaderPluginSdkPackageRoot(params);
if (!packageRoot) {
return {};
}
const extensionsRoot = path.join(packageRoot, "extensions");
let extensionDirs: fs.Dirent[];
try {
extensionDirs = fs.readdirSync(extensionsRoot, { withFileTypes: true });
} catch {
return {};
}
const orderedKinds = resolvePluginSdkAliasCandidateOrder({
modulePath: params.modulePath,
isProduction: process.env.NODE_ENV === "production",
pluginSdkResolution: params.pluginSdkResolution,
});
const aliasMap: Record<string, string> = {};
for (const entry of extensionDirs) {
if (!entry.isDirectory()) {
continue;
}
const dirName = entry.name;
const packageName = readBundledPluginPackageName(
path.join(extensionsRoot, dirName, "package.json"),
);
if (!packageName) {
continue;
}
for (const basename of listBundledPluginPublicSurfaceSourceBasenames(
path.join(extensionsRoot, dirName),
)) {
const target = resolveBundledPluginPublicSurfaceAliasTarget({
packageRoot,
dirName,
basename,
orderedKinds,
});
if (!target) {
continue;
}
aliasMap[`${packageName}/${basename}.js`] = normalizeJitiAliasTargetPath(target);
}
}
return aliasMap;
}
function shouldIncludePrivateLocalOnlyPluginSdkSubpaths() {
return process.env.OPENCLAW_ENABLE_PRIVATE_QA_CLI === "1";
}
@@ -626,6 +746,12 @@ export function buildPluginLoaderAliasMap(
...(extensionApiAlias
? { "openclaw/extension-api": normalizeJitiAliasTargetPath(extensionApiAlias) }
: {}),
...resolveBundledPluginPackagePublicSurfaceAliasMap({
modulePath,
argv1,
moduleUrl,
pluginSdkResolution,
}),
...(pluginSdkAlias
? Object.fromEntries(
PLUGIN_SDK_PACKAGE_NAMES.map((packageName) => [