fix(coding-agent): cache extension imports for session switches
Closes #5905.
This commit is contained in:
@@ -117,6 +117,30 @@ function getAliases(): Record<string, string> {
|
||||
|
||||
type HandlerFn = (...args: unknown[]) => Promise<unknown>;
|
||||
|
||||
let extensionCacheCwd: string | undefined;
|
||||
let extensionCacheGeneration = 0;
|
||||
const extensionCache = new Map<string, ExtensionFactory>();
|
||||
|
||||
interface ExtensionCacheToken {
|
||||
cwd: string;
|
||||
generation: number;
|
||||
}
|
||||
|
||||
export function clearExtensionCache(): void {
|
||||
extensionCache.clear();
|
||||
extensionCacheCwd = undefined;
|
||||
extensionCacheGeneration++;
|
||||
}
|
||||
|
||||
function useExtensionCacheCwd(cwd: string): ExtensionCacheToken {
|
||||
const resolvedCwd = resolvePath(cwd);
|
||||
if (extensionCacheCwd !== undefined && extensionCacheCwd !== resolvedCwd) {
|
||||
clearExtensionCache();
|
||||
}
|
||||
extensionCacheCwd = resolvedCwd;
|
||||
return { cwd: resolvedCwd, generation: extensionCacheGeneration };
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a runtime with throwing stubs for action methods.
|
||||
* Runner.bindCore() replaces these with real implementations.
|
||||
@@ -328,7 +352,22 @@ function createExtensionAPI(
|
||||
return api;
|
||||
}
|
||||
|
||||
async function loadExtensionModule(extensionPath: string) {
|
||||
function isCurrentCacheToken(cacheToken: ExtensionCacheToken | undefined): cacheToken is ExtensionCacheToken {
|
||||
return (
|
||||
cacheToken !== undefined &&
|
||||
extensionCacheCwd === cacheToken.cwd &&
|
||||
extensionCacheGeneration === cacheToken.generation
|
||||
);
|
||||
}
|
||||
|
||||
async function loadExtensionModule(extensionPath: string, cacheToken?: ExtensionCacheToken) {
|
||||
if (isCurrentCacheToken(cacheToken)) {
|
||||
const cachedFactory = extensionCache.get(extensionPath);
|
||||
if (cachedFactory) {
|
||||
return cachedFactory;
|
||||
}
|
||||
}
|
||||
|
||||
const jiti = createJiti(import.meta.url, {
|
||||
moduleCache: false,
|
||||
// In Bun binary: use virtualModules for bundled packages (no filesystem resolution)
|
||||
@@ -339,7 +378,13 @@ async function loadExtensionModule(extensionPath: string) {
|
||||
|
||||
const module = await jiti.import(extensionPath, { default: true });
|
||||
const factory = module as ExtensionFactory;
|
||||
return typeof factory !== "function" ? undefined : factory;
|
||||
if (typeof factory !== "function") {
|
||||
return undefined;
|
||||
}
|
||||
if (isCurrentCacheToken(cacheToken)) {
|
||||
extensionCache.set(extensionPath, factory);
|
||||
}
|
||||
return factory;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -370,11 +415,12 @@ async function loadExtension(
|
||||
cwd: string,
|
||||
eventBus: EventBus,
|
||||
runtime: ExtensionRuntime,
|
||||
cacheToken?: ExtensionCacheToken,
|
||||
): Promise<{ extension: Extension | null; error: string | null }> {
|
||||
const resolvedPath = resolvePath(extensionPath, cwd, { normalizeUnicodeSpaces: true });
|
||||
|
||||
try {
|
||||
const factory = await loadExtensionModule(resolvedPath);
|
||||
const factory = await loadExtensionModule(resolvedPath, cacheToken);
|
||||
if (!factory) {
|
||||
return { extension: null, error: `Extension does not export a valid factory function: ${extensionPath}` };
|
||||
}
|
||||
@@ -410,20 +456,28 @@ export async function loadExtensionFromFactory(
|
||||
/**
|
||||
* Load extensions from paths.
|
||||
*/
|
||||
export async function loadExtensions(
|
||||
async function loadExtensionsInternal(
|
||||
paths: string[],
|
||||
cwd: string,
|
||||
eventBus?: EventBus,
|
||||
runtime?: ExtensionRuntime,
|
||||
useCache = false,
|
||||
): Promise<LoadExtensionsResult> {
|
||||
const extensions: Extension[] = [];
|
||||
const errors: Array<{ path: string; error: string }> = [];
|
||||
const resolvedCwd = resolvePath(cwd);
|
||||
const cacheToken = useCache ? useExtensionCacheCwd(cwd) : undefined;
|
||||
const resolvedCwd = cacheToken?.cwd ?? resolvePath(cwd);
|
||||
const resolvedEventBus = eventBus ?? createEventBus();
|
||||
const resolvedRuntime = runtime ?? createExtensionRuntime();
|
||||
|
||||
for (const extPath of paths) {
|
||||
const { extension, error } = await loadExtension(extPath, resolvedCwd, resolvedEventBus, resolvedRuntime);
|
||||
const { extension, error } = await loadExtension(
|
||||
extPath,
|
||||
resolvedCwd,
|
||||
resolvedEventBus,
|
||||
resolvedRuntime,
|
||||
cacheToken,
|
||||
);
|
||||
|
||||
if (error) {
|
||||
errors.push({ path: extPath, error });
|
||||
@@ -442,6 +496,24 @@ export async function loadExtensions(
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadExtensions(
|
||||
paths: string[],
|
||||
cwd: string,
|
||||
eventBus?: EventBus,
|
||||
runtime?: ExtensionRuntime,
|
||||
): Promise<LoadExtensionsResult> {
|
||||
return loadExtensionsInternal(paths, cwd, eventBus, runtime);
|
||||
}
|
||||
|
||||
export async function loadExtensionsCached(
|
||||
paths: string[],
|
||||
cwd: string,
|
||||
eventBus?: EventBus,
|
||||
runtime?: ExtensionRuntime,
|
||||
): Promise<LoadExtensionsResult> {
|
||||
return loadExtensionsInternal(paths, cwd, eventBus, runtime, true);
|
||||
}
|
||||
|
||||
interface PiManifest {
|
||||
extensions?: string[];
|
||||
themes?: string[];
|
||||
|
||||
Reference in New Issue
Block a user