mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:20:43 +00:00
perf(plugins): cache normalized jiti aliases
This commit is contained in:
@@ -21,6 +21,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
- Discord: harden inbound thread metadata handling against partial Carbon channel getters, so non-command thread messages and queued jobs no longer crash when `name`, `parentId`, `parent`, or `ownerId` requires fetched raw data.
|
||||
- Discord: let `message` tool reactions resolve `user:<id>` DM targets and preserve `channels.discord.guilds.<guild>.channels.<channel>.requireMention: false` during reply-stage activation fallback. Fixes #70165 and #69441.
|
||||
- Plugins/startup: pre-normalize and cache Jiti alias maps before creating plugin loaders, so module-scoped loader filenames do not reintroduce per-plugin alias-normalization startup cost. Fixes #70186.
|
||||
- Telegram/webhooks: lower the grammY webhook callback timeout to 5s so Telegram gets an early 200 response instead of retrying long-running updates as read timeouts. (#70146) Thanks @friday-james.
|
||||
- Telegram/polling: rebuild the polling HTTP transport after `getUpdates` 409 conflicts, so retries use a fresh TCP connection instead of looping on a Telegram-terminated keep-alive socket. (#69873) Thanks @hclsys.
|
||||
- Media delivery: strip persisted base64 audio payloads from webchat history, resolve stored `media://inbound/*` attachments before local-root checks, suppress duplicate Telegram voice/audio sends when TTS emits the same media twice, and support custom image-model IDs that already include their provider prefix.
|
||||
|
||||
@@ -163,4 +163,43 @@ describe("getCachedPluginJitiLoader", () => {
|
||||
expect(createJiti).toHaveBeenCalledTimes(1);
|
||||
expect(cache.size).toBe(1);
|
||||
});
|
||||
|
||||
it("reuses pre-normalized alias options across module-scoped loader filenames", async () => {
|
||||
const { createJiti, getCachedPluginJitiLoader } =
|
||||
await loadCachedPluginJitiLoader("module-filename-aliases");
|
||||
|
||||
const cache = new Map();
|
||||
getCachedPluginJitiLoader({
|
||||
cache,
|
||||
modulePath: "/repo/extensions/demo-a/index.ts",
|
||||
importerUrl: "file:///repo/src/plugins/loader.ts",
|
||||
jitiFilename: "/repo/extensions/demo-a/index.ts",
|
||||
aliasMap: {
|
||||
alpha: "/repo/alpha",
|
||||
beta: "alpha/sub",
|
||||
},
|
||||
tryNative: false,
|
||||
});
|
||||
getCachedPluginJitiLoader({
|
||||
cache,
|
||||
modulePath: "/repo/extensions/demo-b/index.ts",
|
||||
importerUrl: "file:///repo/src/plugins/loader.ts",
|
||||
jitiFilename: "/repo/extensions/demo-b/index.ts",
|
||||
aliasMap: {
|
||||
beta: "alpha/sub",
|
||||
alpha: "/repo/alpha",
|
||||
},
|
||||
tryNative: false,
|
||||
});
|
||||
|
||||
const marker = Symbol.for("pathe:normalizedAlias");
|
||||
const firstAlias = (createJiti.mock.calls[0]?.[1] as { alias?: Record<string, string> }).alias;
|
||||
const secondAlias = (createJiti.mock.calls[1]?.[1] as { alias?: Record<string, string> }).alias;
|
||||
|
||||
expect(createJiti).toHaveBeenCalledTimes(2);
|
||||
expect(cache.size).toBe(2);
|
||||
expect(secondAlias).toBe(firstAlias);
|
||||
expect(firstAlias?.beta).toBe("/repo/alpha/sub");
|
||||
expect((firstAlias as Record<symbol, unknown>)[marker]).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1267,3 +1267,37 @@ describe("buildPluginLoaderAliasMap memoization", () => {
|
||||
expect(Object.keys(second).toSorted()).toEqual(Object.keys(first).toSorted());
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildPluginLoaderJitiOptions", () => {
|
||||
it("pre-normalizes and marks alias maps for Jiti", () => {
|
||||
const marker = Symbol.for("pathe:normalizedAlias");
|
||||
const aliasMap = {
|
||||
"openclaw/plugin-sdk/core": "/repo/src/plugin-sdk/core.ts",
|
||||
"openclaw/plugin-sdk": "/repo/src/plugin-sdk/root-alias.cjs",
|
||||
"@openclaw/plugin-sdk": "/repo/src/plugin-sdk/root-alias.cjs",
|
||||
};
|
||||
|
||||
const first = buildPluginLoaderJitiOptions(aliasMap).alias as Record<string, string>;
|
||||
const second = buildPluginLoaderJitiOptions({ ...aliasMap }).alias as Record<string, string>;
|
||||
|
||||
expect(second).toBe(first);
|
||||
expect((first as Record<symbol, unknown>)[marker]).toBe(true);
|
||||
expect(Object.prototype.propertyIsEnumerable.call(first, marker)).toBe(false);
|
||||
});
|
||||
|
||||
it("applies Jiti alias-target normalization before caching", () => {
|
||||
const aliasMap = {
|
||||
alpha: "/repo/alpha",
|
||||
beta: "alpha/sub",
|
||||
};
|
||||
|
||||
const alias = buildPluginLoaderJitiOptions(aliasMap).alias as Record<string, string>;
|
||||
|
||||
expect(alias).not.toBe(aliasMap);
|
||||
expect(alias.beta).toBe("/repo/alpha/sub");
|
||||
});
|
||||
|
||||
it("does not attach an empty alias map", () => {
|
||||
expect(buildPluginLoaderJitiOptions({})).not.toHaveProperty("alias");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -481,12 +481,15 @@ export function resolveExtensionApiAlias(params: LoaderModuleResolveParams = {})
|
||||
}
|
||||
|
||||
const MAX_PLUGIN_LOADER_ALIAS_CACHE_ENTRIES = 512;
|
||||
const JITI_NORMALIZED_ALIAS_SYMBOL = Symbol.for("pathe:normalizedAlias");
|
||||
const JITI_ALIAS_ROOT_SENTINELS = new Set<string | undefined>(["/", "\\", undefined]);
|
||||
|
||||
// Memoize loader alias/config by effective resolution context so repeated
|
||||
// loader setup avoids rebuilding the same filesystem-derived map and cache key.
|
||||
// Include cwd/env inputs because the fallback root and private QA alias
|
||||
// surfaces depend on them.
|
||||
const aliasMapCache = new Map<string, Record<string, string>>();
|
||||
const normalizedJitiAliasMapCache = new Map<string, Record<string, string>>();
|
||||
const pluginLoaderJitiConfigCache = new Map<
|
||||
string,
|
||||
{
|
||||
@@ -510,6 +513,54 @@ function setBoundedCacheValue<T>(cache: Map<string, T>, key: string, value: T) {
|
||||
}
|
||||
}
|
||||
|
||||
function hasJitiNormalizedAliasMarker(aliasMap: Record<string, string>) {
|
||||
return Boolean((aliasMap as Record<symbol, unknown>)[JITI_NORMALIZED_ALIAS_SYMBOL]);
|
||||
}
|
||||
|
||||
function createJitiAliasContentCacheKey(aliasMap: Record<string, string>) {
|
||||
return JSON.stringify(
|
||||
Object.entries(aliasMap).toSorted(([left], [right]) => left.localeCompare(right)),
|
||||
);
|
||||
}
|
||||
|
||||
function normalizePluginLoaderAliasMapForJiti(
|
||||
aliasMap: Record<string, string>,
|
||||
): Record<string, string> {
|
||||
if (hasJitiNormalizedAliasMarker(aliasMap)) {
|
||||
return aliasMap;
|
||||
}
|
||||
const cacheKey = createJitiAliasContentCacheKey(aliasMap);
|
||||
const cached = normalizedJitiAliasMapCache.get(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const normalizedAliasMap = Object.fromEntries(
|
||||
Object.entries(aliasMap).toSorted(
|
||||
([left], [right]) => right.split("/").length - left.split("/").length,
|
||||
),
|
||||
);
|
||||
for (const aliasKey in normalizedAliasMap) {
|
||||
for (const candidateKey in normalizedAliasMap) {
|
||||
if (
|
||||
candidateKey === aliasKey ||
|
||||
aliasKey.startsWith(candidateKey) ||
|
||||
!normalizedAliasMap[aliasKey]?.startsWith(candidateKey) ||
|
||||
!JITI_ALIAS_ROOT_SENTINELS.has(normalizedAliasMap[aliasKey]?.[candidateKey.length])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
normalizedAliasMap[aliasKey] =
|
||||
normalizedAliasMap[candidateKey] + normalizedAliasMap[aliasKey].slice(candidateKey.length);
|
||||
}
|
||||
}
|
||||
Object.defineProperty(normalizedAliasMap, JITI_NORMALIZED_ALIAS_SYMBOL, {
|
||||
value: true,
|
||||
enumerable: false,
|
||||
});
|
||||
setBoundedCacheValue(normalizedJitiAliasMapCache, cacheKey, normalizedAliasMap);
|
||||
return normalizedAliasMap;
|
||||
}
|
||||
|
||||
function buildPluginLoaderAliasMapCacheKey(params: {
|
||||
modulePath: string;
|
||||
argv1?: string;
|
||||
@@ -626,15 +677,17 @@ export function resolvePluginRuntimeModulePath(
|
||||
}
|
||||
|
||||
export function buildPluginLoaderJitiOptions(aliasMap: Record<string, string>) {
|
||||
const hasAliases = Object.keys(aliasMap).length > 0;
|
||||
const jitiAliasMap = hasAliases ? normalizePluginLoaderAliasMapForJiti(aliasMap) : aliasMap;
|
||||
return {
|
||||
interopDefault: true,
|
||||
// Prefer Node's native sync ESM loader for built dist/*.js modules so
|
||||
// bundled plugins and plugin-sdk subpaths stay on the canonical module graph.
|
||||
tryNative: true,
|
||||
extensions: [".ts", ".tsx", ".mts", ".cts", ".mtsx", ".ctsx", ".js", ".mjs", ".cjs", ".json"],
|
||||
...(Object.keys(aliasMap).length > 0
|
||||
...(hasAliases
|
||||
? {
|
||||
alias: aliasMap,
|
||||
alias: jitiAliasMap,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user