can1357/oh-my-pi · error · TypeError
pluginClass must be a MnemopiPlugin subclass
Error message
pluginClass must be a MnemopiPlugin subclass
What it means
registerPlugin validates that the second argument is a constructor function whose prototype chain derives from MnemopiPlugin. The TypeError fires when you pass anything else — a plain class, a function, or a non-function value — because the manager relies on MnemopiPlugin's interface for initialize/shutdown lifecycle calls.
Source
Thrown at packages/mnemopi/src/core/plugins.ts:273
override onRecall(_memory: MemoryDict): void {}
override onConsolidate(_summary: MemoryDict): void {}
override onInvalidate(_memoryId: string): void {}
}
export type PluginConstructor<T extends MnemopiPlugin = MnemopiPlugin> = new (config?: PluginConfig) => T;
export class PluginManager {
private readonly registry = new Map<string, PluginConstructor>();
private readonly instances = new Map<string, MnemopiPlugin>();
constructor(private readonly pluginDir = DEFAULT_PLUGIN_DIR) {
this.registerPlugin("logging", LoggingPlugin);
this.registerPlugin("metrics", MetricsPlugin);
this.registerPlugin("filter", FilterPlugin);
this.registerPlugin("compression", CompressionPlugin);
}
registerPlugin(name: string, pluginClass: PluginConstructor): void {
if (typeof pluginClass !== "function" || !(pluginClass.prototype instanceof MnemopiPlugin)) {
throw new TypeError("pluginClass must be a MnemopiPlugin subclass");
}
if (this.registry.has(name)) throw new ValueError(`Plugin '${name}' is already registered`);
this.registry.set(name, pluginClass);
}
loadPlugin(name: string, config: PluginConfig = {}): MnemopiPlugin {
const pluginClass = this.registry.get(name);
if (pluginClass === undefined) throw new ValueError(`Plugin '${name}' is not registered`);
if (this.instances.has(name)) throw new Error(`Plugin '${name}' is already loaded`);
const instance = new pluginClass(config);
instance.initialize();
this.instances.set(name, instance);
return instance;
}
unloadPlugin(name: string): void {
const instance = this.instances.get(name);
if (instance === undefined) throw new ValueError(`Plugin '${name}' is not loaded`);
this.instances.delete(name);
instance.shutdown();View on GitHub (pinned to 9690622007)
Solutions
- Make the plugin class extend MnemopiPlugin: `class MyPlugin extends MnemopiPlugin { ... }`
- Verify the import resolves to the class itself (console.log(pluginClass) before registering; fix default/named import)
- Pass a class constructor, not an instance or a factory function that returns one
Example fix
// before
manager.registerPlugin("custom", createCustomPlugin);
// after
class CustomPlugin extends MnemopiPlugin { /* ... */ }
manager.registerPlugin("custom", CustomPlugin); Defensive patterns
Strategy: type-guard
Validate before calling
import { MnemopiPlugin } from "...";
if (typeof pluginClass === "function" && pluginClass.prototype instanceof MnemopiPlugin) {
manager.registerPlugin(name, pluginClass);
} Type guard
function isPluginConstructor(value: unknown): value is new (config: PluginConfig) => MnemopiPlugin {
return typeof value === "function" && value.prototype instanceof MnemopiPlugin;
} Try / catch
try {
manager.registerPlugin(name, pluginClass);
} catch (err) {
if (err instanceof TypeError) {
throw new Error(`registerPlugin(${name}): not a MnemopiPlugin subclass`, { cause: err });
}
throw err;
} Prevention
- Always declare custom plugins as `class X extends MnemopiPlugin`
- Type the registration argument as PluginConstructor so TS catches non-class values at compile time
- Verify imports resolve to the class (not undefined) before registering
When it happens
Trigger: Calling manager.registerPlugin(name, X) where X is a plain object, an arrow function, a class not extending MnemopiPlugin, or a mis-typed import (e.g. passing the module namespace instead of the class).
Common situations: Importing the plugin class incorrectly (default vs named import) so the value is undefined; writing a custom plugin that forgets `extends MnemopiPlugin`; refactoring that replaced the class with a factory function.
Related errors
- Relative plugin source paths must start with "./" — got: "${
- queryTime must be null, an ISO date string, or a valid Date
- Plugin '${name}' is already registered
- event_type is required
- Unsupported language '{value}'. Supported: {}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/a439c9e96da5f708.
Report an issue: GitHub.