feat(FN-3156): add database schema init hook runner for plugin settings lif

The merge adds plugin schema initialization lifecycle support (FN-3156), introduces a ResearchTaskActionModal with model fallback notifications (FN-3012, FN-3008), adds async planning draft sync to prevent UI blocking (FN-3229), and implements viewport-conditional mission split layout (FN-3130). Inf

Fusion-Task-Id: FN-3156
This commit is contained in:
Fusion
2026-05-02 20:25:46 -07:00
committed by gsxdsm
parent c6119f78ef
commit d1cb401a0d
11 changed files with 237 additions and 8 deletions

View File

@@ -30,6 +30,7 @@ describe("PluginRunner", () => {
loadAllPlugins: ReturnType<typeof vi.fn>;
stopAllPlugins: ReturnType<typeof vi.fn>;
invokeHook: ReturnType<typeof vi.fn>;
getPluginSchemaInitHooks: ReturnType<typeof vi.fn>;
getPluginTools: ReturnType<typeof vi.fn>;
getPluginRoutes: ReturnType<typeof vi.fn>;
getPluginUiSlots: ReturnType<typeof vi.fn>;
@@ -55,6 +56,7 @@ describe("PluginRunner", () => {
on: ReturnType<typeof vi.fn>;
off: ReturnType<typeof vi.fn>;
getTask: ReturnType<typeof vi.fn>;
getDatabase: ReturnType<typeof vi.fn>;
};
let pluginRunner: PluginRunner;
@@ -87,6 +89,7 @@ describe("PluginRunner", () => {
loadAllPlugins: vi.fn().mockResolvedValue({ loaded: 2, errors: 0 }),
stopAllPlugins: vi.fn().mockResolvedValue(undefined),
invokeHook: vi.fn().mockResolvedValue(undefined),
getPluginSchemaInitHooks: vi.fn().mockReturnValue([]),
getPluginTools: vi.fn().mockReturnValue([]),
getPluginRoutes: vi.fn().mockReturnValue([]),
getPluginUiSlots: vi.fn().mockReturnValue([]),
@@ -106,10 +109,12 @@ describe("PluginRunner", () => {
const mockOn = vi.fn();
const mockOff = vi.fn();
const mockRunPluginSchemaInits = vi.fn().mockResolvedValue(undefined);
mockTaskStore = {
on: mockOn,
off: mockOff,
getTask: vi.fn(),
getDatabase: vi.fn().mockReturnValue({ runPluginSchemaInits: mockRunPluginSchemaInits }),
};
mockPluginStore = {
@@ -142,6 +147,32 @@ describe("PluginRunner", () => {
expect(mockPluginLoader.loadAllPlugins).toHaveBeenCalled();
});
it("should execute schema init hooks after plugin load", async () => {
const schemaHook = vi.fn();
mockPluginLoader.getPluginSchemaInitHooks.mockReturnValue([
{ pluginId: "plugin-a", hook: schemaHook },
]);
await pluginRunner.init();
expect(mockPluginLoader.getPluginSchemaInitHooks).toHaveBeenCalledTimes(1);
expect(mockTaskStore.getDatabase).toHaveBeenCalledTimes(1);
const db = mockTaskStore.getDatabase.mock.results[0]?.value as {
runPluginSchemaInits: ReturnType<typeof vi.fn>;
};
expect(db.runPluginSchemaInits).toHaveBeenCalledWith([
{ pluginId: "plugin-a", hook: schemaHook },
]);
});
it("should skip schema init execution when no hooks are registered", async () => {
mockPluginLoader.getPluginSchemaInitHooks.mockReturnValue([]);
await pluginRunner.init();
expect(mockTaskStore.getDatabase).not.toHaveBeenCalled();
});
it("should subscribe to plugin store events", async () => {
await pluginRunner.init();
// Should subscribe to plugin lifecycle events

View File

@@ -157,6 +157,19 @@ export class PluginRunner {
const result = await this.options.pluginLoader.loadAllPlugins();
executorLog.log(`PluginRunner loaded ${result.loaded} plugins (${result.errors} errors)`);
// Execute onSchemaInit hooks from loaded plugins.
const schemaInitHooks = this.options.pluginLoader.getPluginSchemaInitHooks();
if (schemaInitHooks.length > 0) {
executorLog.log(`Executing onSchemaInit hooks from ${schemaInitHooks.length} plugins`);
try {
const db = this.options.taskStore.getDatabase();
await db.runPluginSchemaInits(schemaInitHooks);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
executorLog.log(`onSchemaInit execution failed: ${message}`);
}
}
// Subscribe to store events for task lifecycle hooks
this.subscribeToStoreEvents();