mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 10:30:44 +00:00
fix(plugins): alias bundled public surfaces in source loaders
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) => [
|
||||
|
||||
Reference in New Issue
Block a user