FN-7623: wire pluginStore/pluginLoader into desktop embedded dashboard server
Fixes the desktop app's plugin subsystem, which was never wired into createServer(), breaking Settings > Plugins Browse registry and plugin install. - local-runtime.ts: construct a PluginStore + PluginLoader (mirroring the CLI dashboard command), load enabled plugins, run plugin schema-init hooks, and pass pluginStore/pluginLoader/pluginRunner into createServer() - local-server.ts: apply the same wiring to the legacy desktop local server path for consistency - Both paths fail soft: a broken plugin subsystem (e.g. corrupt manifest) is logged/traced but no longer blocks embedded dashboard startup - Extend local-runtime.test.ts and local-server.test.ts to cover plugin wiring and the fail-soft path - Add changeset (patch) documenting the fix Files changed: .changeset/fn-7623-desktop-plugin-wiring.md | 7 ++ .../desktop/src/__tests__/local-runtime.test.ts | 123 ++++++++++++++++++++- .../desktop/src/__tests__/local-server.test.ts | 69 +++++++++++- packages/desktop/src/local-runtime.ts | 50 ++++++++- packages/desktop/src/local-server.ts | 41 ++++++- 5 files changed, 286 insertions(+), 4 deletions(-) Fusion-Task-Id: FN-7623 Fusion-Task-Lineage: c6f291fb-e6aa-4ac1-a3f3-4189fc831c60 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7623-desktop-plugin-wiring.md
Normal file
7
.changeset/fn-7623-desktop-plugin-wiring.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Fix desktop app plugin install and Browse registry (plugin subsystem now wired into the embedded server).
|
||||
category: fix
|
||||
dev: local-runtime.ts / local-server.ts now build a PluginStore + PluginLoader and pass pluginStore/pluginLoader/pluginRunner into createServer, mirroring the CLI dashboard command (FN-7623, issue #1937). Plugin subsystem init is fail-soft — a broken plugin (e.g. corrupt manifest) logs/traces via strace(...) but no longer blocks embedded dashboard startup.
|
||||
@@ -47,6 +47,19 @@ class FakeServer {
|
||||
* `createDashboardServer` in LocalRuntimeManagerOptions). Mirrors local-server.test.ts's pattern.
|
||||
*/
|
||||
const engineMocks = vi.hoisted(() => {
|
||||
// FN-7623: pluginStore/pluginLoader mocks proving createDashboardServerDefault wires the plugin
|
||||
// subsystem into createServer (fixes desktop's "Plugin install mode is not supported" and Browse
|
||||
// registry "Plugin \"registry\" not found" symptoms).
|
||||
const pluginStoreInstance = { init: vi.fn(async () => undefined) };
|
||||
const pluginLoaderInstance = {
|
||||
loadAllPlugins: vi.fn(async () => ({ loaded: 2, errors: 0 })),
|
||||
getPluginSchemaInitHooks: vi.fn(() => []),
|
||||
};
|
||||
const runPluginSchemaInits = vi.fn(async () => undefined);
|
||||
const PluginLoader = vi.fn(function () {
|
||||
return pluginLoaderInstance;
|
||||
});
|
||||
|
||||
const centralCore = {
|
||||
init: vi.fn(async () => undefined),
|
||||
close: vi.fn(async () => undefined),
|
||||
@@ -76,14 +89,18 @@ const engineMocks = vi.hoisted(() => {
|
||||
centralCore,
|
||||
engineManager,
|
||||
CentralCore,
|
||||
PluginLoader,
|
||||
ProjectEngineManager,
|
||||
seedDashboardProviders,
|
||||
seedDashboardProvidersDispose,
|
||||
createServer,
|
||||
pluginStoreInstance,
|
||||
pluginLoaderInstance,
|
||||
runPluginSchemaInits,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@fusion/core", () => ({ CentralCore: engineMocks.CentralCore }));
|
||||
vi.mock("@fusion/core", () => ({ CentralCore: engineMocks.CentralCore, PluginLoader: engineMocks.PluginLoader }));
|
||||
vi.mock("@fusion/dashboard", () => ({ createServer: engineMocks.createServer }));
|
||||
vi.mock("@fusion/engine", () => ({
|
||||
ProjectEngineManager: engineMocks.ProjectEngineManager,
|
||||
@@ -97,6 +114,8 @@ describe("LocalRuntimeManager", () => {
|
||||
init: vi.fn(async () => undefined),
|
||||
watch: vi.fn(async () => undefined),
|
||||
close: vi.fn(),
|
||||
getPluginStore: vi.fn(() => engineMocks.pluginStoreInstance),
|
||||
getDatabase: vi.fn(() => ({ runPluginSchemaInits: engineMocks.runPluginSchemaInits })),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -442,4 +461,106 @@ describe("LocalRuntimeManager", () => {
|
||||
await manager.stopLocal();
|
||||
expect(engineMocks.seedDashboardProvidersDispose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
/*
|
||||
* FN-7623 symptom verification: before this fix, createDashboardServerDefault called createServer
|
||||
* WITHOUT pluginStore/pluginLoader, so desktop's Browse-registry sub-router never mounted ("Plugin
|
||||
* \"registry\" not found") and plugin install threw "Plugin install mode is not supported: plugin
|
||||
* loader not available". Assert the fix in the engine-less (zero-projects) startup state — the
|
||||
* plugin subsystem must wire in regardless of whether a primary engine resolved.
|
||||
*/
|
||||
it("wires PluginStore + PluginLoader into createServer when engine-less (zero projects) (FN-7623)", async () => {
|
||||
const { LocalRuntimeManager } = await import("../local-runtime.ts");
|
||||
const server = new FakeServer(4545);
|
||||
engineMocks.createServer.mockReturnValueOnce({
|
||||
listen: vi.fn(() => {
|
||||
setTimeout(() => server.emit("listening"), 0);
|
||||
return server as unknown as Server;
|
||||
}),
|
||||
});
|
||||
|
||||
const manager = new LocalRuntimeManager({
|
||||
rootDir: "/repo",
|
||||
createStore: async () => store,
|
||||
});
|
||||
|
||||
await manager.startLocal();
|
||||
|
||||
expect(store.getPluginStore).toHaveBeenCalledTimes(1);
|
||||
expect(engineMocks.pluginStoreInstance.init).toHaveBeenCalledTimes(1);
|
||||
expect(engineMocks.PluginLoader).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ pluginStore: engineMocks.pluginStoreInstance, taskStore: expect.anything() }),
|
||||
);
|
||||
expect(engineMocks.pluginLoaderInstance.loadAllPlugins).toHaveBeenCalledTimes(1);
|
||||
expect(engineMocks.createServer).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
pluginStore: engineMocks.pluginStoreInstance,
|
||||
pluginLoader: engineMocks.pluginLoaderInstance,
|
||||
pluginRunner: engineMocks.pluginLoaderInstance,
|
||||
}),
|
||||
);
|
||||
|
||||
await manager.stopLocal();
|
||||
});
|
||||
|
||||
it("wires PluginStore + PluginLoader into createServer when a project engine resolved (projects-present) (FN-7623)", async () => {
|
||||
const { LocalRuntimeManager } = await import("../local-runtime.ts");
|
||||
engineMocks.centralCore.listProjects.mockResolvedValueOnce([
|
||||
{ id: "project-1", name: "Repo", path: "/repo", status: "active" },
|
||||
]);
|
||||
const server = new FakeServer(4545);
|
||||
engineMocks.createServer.mockReturnValueOnce({
|
||||
listen: vi.fn(() => {
|
||||
setTimeout(() => server.emit("listening"), 0);
|
||||
return server as unknown as Server;
|
||||
}),
|
||||
});
|
||||
|
||||
const manager = new LocalRuntimeManager({
|
||||
rootDir: "/repo",
|
||||
createStore: async () => store,
|
||||
});
|
||||
|
||||
await manager.startLocal();
|
||||
|
||||
expect(engineMocks.createServer).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
engine: expect.anything(),
|
||||
pluginStore: engineMocks.pluginStoreInstance,
|
||||
pluginLoader: engineMocks.pluginLoaderInstance,
|
||||
pluginRunner: engineMocks.pluginLoaderInstance,
|
||||
}),
|
||||
);
|
||||
|
||||
await manager.stopLocal();
|
||||
});
|
||||
|
||||
it("boots the dashboard without plugin wiring when the plugin subsystem fails to init (fail-soft) (FN-7623)", async () => {
|
||||
const { LocalRuntimeManager } = await import("../local-runtime.ts");
|
||||
engineMocks.pluginStoreInstance.init.mockRejectedValueOnce(new Error("plugin db locked"));
|
||||
const server = new FakeServer(4545);
|
||||
engineMocks.createServer.mockReturnValueOnce({
|
||||
listen: vi.fn(() => {
|
||||
setTimeout(() => server.emit("listening"), 0);
|
||||
return server as unknown as Server;
|
||||
}),
|
||||
});
|
||||
|
||||
const manager = new LocalRuntimeManager({
|
||||
rootDir: "/repo",
|
||||
createStore: async () => store,
|
||||
});
|
||||
|
||||
const status = await manager.startLocal();
|
||||
|
||||
expect(status).toMatchObject({ source: "embedded-local", state: "running", port: 4545 });
|
||||
expect(engineMocks.createServer).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.not.objectContaining({ pluginStore: expect.anything() }),
|
||||
);
|
||||
|
||||
await manager.stopLocal();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,10 +30,28 @@ const mocks = vi.hoisted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
// FN-7623: pluginStore/pluginLoader mocks proving local-server.ts wires the plugin subsystem
|
||||
// into createServer (the fix for "Plugin install mode is not supported" and the Browse-registry
|
||||
// "Plugin \"registry\" not found" symptoms).
|
||||
const pluginStoreInstance = {
|
||||
init: vi.fn(async () => undefined),
|
||||
};
|
||||
const pluginLoaderInstance = {
|
||||
loadAllPlugins: vi.fn(async () => ({ loaded: 2, errors: 0 })),
|
||||
getPluginSchemaInitHooks: vi.fn(() => []),
|
||||
};
|
||||
const runPluginSchemaInits = vi.fn(async () => undefined);
|
||||
const database = { runPluginSchemaInits };
|
||||
const PluginLoader = vi.fn(function () {
|
||||
return pluginLoaderInstance;
|
||||
});
|
||||
|
||||
const store = {
|
||||
init: vi.fn(async () => undefined),
|
||||
watch: vi.fn(async () => undefined),
|
||||
close: vi.fn(),
|
||||
getPluginStore: vi.fn(() => pluginStoreInstance),
|
||||
getDatabase: vi.fn(() => database),
|
||||
};
|
||||
const centralCore = {
|
||||
init: vi.fn(async () => undefined),
|
||||
@@ -61,6 +79,8 @@ const mocks = vi.hoisted(() => {
|
||||
init = store.init;
|
||||
watch = store.watch;
|
||||
close = store.close;
|
||||
getPluginStore = store.getPluginStore;
|
||||
getDatabase = store.getDatabase;
|
||||
}
|
||||
|
||||
const server = Object.assign(new SimpleEmitter(), {
|
||||
@@ -94,6 +114,7 @@ const mocks = vi.hoisted(() => {
|
||||
return {
|
||||
TaskStore,
|
||||
CentralCore,
|
||||
PluginLoader,
|
||||
ProjectEngineManager,
|
||||
createServer,
|
||||
store,
|
||||
@@ -101,12 +122,15 @@ const mocks = vi.hoisted(() => {
|
||||
centralCore,
|
||||
engineManager,
|
||||
engine,
|
||||
pluginStoreInstance,
|
||||
pluginLoaderInstance,
|
||||
runPluginSchemaInits,
|
||||
seedDashboardProviders,
|
||||
seedDashboardProvidersDispose,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@fusion/core", () => ({ TaskStore: mocks.TaskStore, CentralCore: mocks.CentralCore }));
|
||||
vi.mock("@fusion/core", () => ({ TaskStore: mocks.TaskStore, CentralCore: mocks.CentralCore, PluginLoader: mocks.PluginLoader }));
|
||||
vi.mock("@fusion/dashboard", () => ({ createServer: mocks.createServer }));
|
||||
vi.mock("@fusion/engine", () => ({
|
||||
ProjectEngineManager: mocks.ProjectEngineManager,
|
||||
@@ -245,4 +269,47 @@ describe("DesktopLocalServerManager", () => {
|
||||
await manager.stop();
|
||||
expect(mocks.seedDashboardProvidersDispose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
/*
|
||||
* FN-7623 symptom verification: before this fix, DesktopLocalServerManager.start() called
|
||||
* createServer WITHOUT pluginStore/pluginLoader, so the registry sub-router never mounted
|
||||
* ("Plugin \"registry\" not found" on Browse registry) and install threw "Plugin install mode
|
||||
* is not supported: plugin loader not available". Assert the fix: createServer now receives
|
||||
* pluginStore, pluginLoader, and pluginRunner (aliased to the same PluginLoader instance).
|
||||
*/
|
||||
it("wires PluginStore + PluginLoader into createServer (FN-7623)", async () => {
|
||||
const { DesktopLocalServerManager } = await import("../local-server.ts");
|
||||
const manager = new DesktopLocalServerManager("/repo");
|
||||
|
||||
await manager.start();
|
||||
|
||||
expect(mocks.store.getPluginStore).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.pluginStoreInstance.init).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.PluginLoader).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ pluginStore: mocks.pluginStoreInstance, taskStore: expect.anything() }),
|
||||
);
|
||||
expect(mocks.pluginLoaderInstance.loadAllPlugins).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.createServer).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
pluginStore: mocks.pluginStoreInstance,
|
||||
pluginLoader: mocks.pluginLoaderInstance,
|
||||
pluginRunner: mocks.pluginLoaderInstance,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("boots the dashboard without plugin wiring when the plugin subsystem fails to init (fail-soft)", async () => {
|
||||
mocks.pluginStoreInstance.init.mockRejectedValueOnce(new Error("plugin db locked"));
|
||||
const { DesktopLocalServerManager } = await import("../local-server.ts");
|
||||
const manager = new DesktopLocalServerManager("/repo");
|
||||
|
||||
const runtime = await manager.start();
|
||||
|
||||
expect(runtime.port).toBe(4545);
|
||||
expect(mocks.createServer).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.not.objectContaining({ pluginStore: expect.anything() }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -36,10 +36,23 @@ export interface DesktopRuntimeStatus {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/*
|
||||
* FNXC:DesktopRuntime 2026-07-07-12:00:
|
||||
* FN-7623: the embedded desktop server must wire a PluginStore + PluginLoader into createServer
|
||||
* (as the CLI dashboard command does) or the Settings -> Plugins Browse-registry sub-router never
|
||||
* mounts ("Plugin \"registry\" not found") and plugin install throws "Plugin install mode is not
|
||||
* supported: plugin loader not available". getPluginStore()/getDatabase() are the two TaskStore
|
||||
* members this wiring needs beyond the pre-existing init/watch/close surface.
|
||||
*/
|
||||
type PluginStoreLike = { init(): Promise<void> };
|
||||
type PluginDatabaseLike = { runPluginSchemaInits(hooks: Array<{ pluginId: string; hook: unknown }>): Promise<void> };
|
||||
|
||||
type TaskStoreLike = {
|
||||
init(): Promise<void>;
|
||||
watch(): Promise<void>;
|
||||
close(): void;
|
||||
getPluginStore(): PluginStoreLike;
|
||||
getDatabase(): PluginDatabaseLike;
|
||||
};
|
||||
|
||||
type RuntimeCleanup = () => Promise<void> | void;
|
||||
@@ -87,7 +100,7 @@ async function createStoreDefault(rootDir: string): Promise<TaskStoreLike> {
|
||||
}
|
||||
|
||||
async function createDashboardServerDefault(store: TaskStoreLike, rootDir: string): Promise<{ server: Server; cleanup: RuntimeCleanup }> {
|
||||
const { CentralCore } = await import("@fusion/core");
|
||||
const { CentralCore, PluginLoader } = await import("@fusion/core");
|
||||
const { createServer } = await import("@fusion/dashboard");
|
||||
const { ProjectEngineManager, createFusionAuthStorage, createFusionModelRegistry, seedDashboardProviders } = await import("@fusion/engine");
|
||||
|
||||
@@ -149,6 +162,40 @@ async function createDashboardServerDefault(store: TaskStoreLike, rootDir: strin
|
||||
log: (scope, message) => strace(`[${scope}] ${message}`),
|
||||
});
|
||||
providerSeeding.dispose = dispose;
|
||||
|
||||
/*
|
||||
* FNXC:DesktopRuntime 2026-07-07-12:00:
|
||||
* FN-7623: mirror the CLI dashboard command's plugin wiring (packages/cli/src/commands/dashboard.ts)
|
||||
* — construct the store's PluginStore, build a PluginLoader, load enabled plugins, and run schema-init
|
||||
* hooks — so the desktop embedded server's registry sub-router mounts (GET /api/plugins/registry) and
|
||||
* POST /api/plugins install mode works. Bundled-plugin auto-install (Hermes/OpenClaw/Paperclip/Dependency
|
||||
* Graph) depends on packages/cli/src/plugins/bundled-plugin-install.ts, which is CLI-only and out of
|
||||
* scope for desktop (desktop must not depend on the CLI package) — see FN-7623 scope note. Failures here
|
||||
* must not crash embedded startup: the dashboard still needs to boot even if the plugin subsystem can't
|
||||
* come up (e.g. a corrupt plugin manifest), so this is wrapped and traced rather than left to throw.
|
||||
*/
|
||||
let pluginStore: PluginStoreLike | undefined;
|
||||
let pluginLoader: InstanceType<typeof PluginLoader> | undefined;
|
||||
try {
|
||||
strace("createDashboardServer: pluginStore.init");
|
||||
pluginStore = store.getPluginStore();
|
||||
await pluginStore.init();
|
||||
pluginLoader = new PluginLoader({ pluginStore: pluginStore as never, taskStore: store as never });
|
||||
strace("createDashboardServer: pluginLoader.loadAllPlugins");
|
||||
const { loaded, errors } = await pluginLoader.loadAllPlugins();
|
||||
strace(`createDashboardServer: plugins loaded=${loaded} errors=${errors}`);
|
||||
const schemaHooks = pluginLoader.getPluginSchemaInitHooks();
|
||||
if (schemaHooks.length > 0) {
|
||||
await store.getDatabase().runPluginSchemaInits(schemaHooks);
|
||||
}
|
||||
} catch (error) {
|
||||
strace(
|
||||
`createDashboardServer: plugin subsystem init FAILED (non-fatal, dashboard still boots) — ${error instanceof Error ? error.stack : String(error)}`,
|
||||
);
|
||||
pluginStore = undefined;
|
||||
pluginLoader = undefined;
|
||||
}
|
||||
|
||||
strace("createDashboardServer: createServer");
|
||||
const app = createServer(store as never, {
|
||||
...(primaryEngine ? { engine: primaryEngine } : {}),
|
||||
@@ -156,6 +203,7 @@ async function createDashboardServerDefault(store: TaskStoreLike, rootDir: strin
|
||||
centralCore,
|
||||
authStorage: wrappedAuthStorage,
|
||||
modelRegistry,
|
||||
...(pluginStore && pluginLoader ? { pluginStore: pluginStore as never, pluginLoader, pluginRunner: pluginLoader } : {}),
|
||||
onProjectFirstAccessed: (projectId: string) => engineManager.onProjectAccessed(projectId),
|
||||
});
|
||||
|
||||
|
||||
@@ -4,10 +4,23 @@ import type { Server } from "node:http";
|
||||
|
||||
import { resolveDesktopRuntimePrimaryProject } from "./engine-runtime.js";
|
||||
|
||||
/*
|
||||
* FNXC:DesktopRuntime 2026-07-07-12:00:
|
||||
* FN-7623: this legacy desktop local server path had the same missing plugin-subsystem wiring as
|
||||
* local-runtime.ts — createServer() never received pluginStore/pluginLoader, so Settings -> Plugins
|
||||
* Browse registry ("Plugin \"registry\" not found") and plugin install ("Plugin install mode is not
|
||||
* supported: plugin loader not available") were both dead in this path too. Keep both desktop server
|
||||
* paths consistent (see local-runtime.ts's matching comment).
|
||||
*/
|
||||
type PluginStoreLike = { init(): Promise<void> };
|
||||
type PluginDatabaseLike = { runPluginSchemaInits(hooks: Array<{ pluginId: string; hook: unknown }>): Promise<void> };
|
||||
|
||||
type TaskStoreLike = {
|
||||
init(): Promise<void>;
|
||||
watch(): Promise<void>;
|
||||
close(): void;
|
||||
getPluginStore(): PluginStoreLike;
|
||||
getDatabase(): PluginDatabaseLike;
|
||||
};
|
||||
|
||||
type RuntimeCleanup = () => Promise<void> | void;
|
||||
@@ -53,7 +66,7 @@ export class DesktopLocalServerManager {
|
||||
|
||||
try {
|
||||
const { TaskStore } = await import("@fusion/core");
|
||||
const { CentralCore } = await import("@fusion/core");
|
||||
const { CentralCore, PluginLoader } = await import("@fusion/core");
|
||||
const { createServer } = await import("@fusion/dashboard");
|
||||
const { ProjectEngineManager, createFusionAuthStorage, createFusionModelRegistry, seedDashboardProviders } = await import("@fusion/engine");
|
||||
store = new TaskStore(this.rootDir) as TaskStoreLike;
|
||||
@@ -93,12 +106,38 @@ export class DesktopLocalServerManager {
|
||||
modelRegistry,
|
||||
});
|
||||
providerSeeding.dispose = dispose;
|
||||
|
||||
/*
|
||||
* FNXC:DesktopRuntime 2026-07-07-12:00:
|
||||
* FN-7623: mirror the CLI dashboard command's plugin wiring — construct the store's PluginStore,
|
||||
* build a PluginLoader, load enabled plugins, and run schema-init hooks — so this legacy path's
|
||||
* registry sub-router mounts and install works too. Fail soft: a broken plugin subsystem must not
|
||||
* prevent the embedded dashboard from booting.
|
||||
*/
|
||||
let pluginStore: PluginStoreLike | undefined;
|
||||
let pluginLoader: InstanceType<typeof PluginLoader> | undefined;
|
||||
try {
|
||||
pluginStore = store.getPluginStore();
|
||||
await pluginStore.init();
|
||||
pluginLoader = new PluginLoader({ pluginStore: pluginStore as never, taskStore: store as never });
|
||||
await pluginLoader.loadAllPlugins();
|
||||
const schemaHooks = pluginLoader.getPluginSchemaInitHooks();
|
||||
if (schemaHooks.length > 0) {
|
||||
await store.getDatabase().runPluginSchemaInits(schemaHooks);
|
||||
}
|
||||
} catch {
|
||||
// Plugin subsystem failures must not block embedded dashboard startup (FN-7623).
|
||||
pluginStore = undefined;
|
||||
pluginLoader = undefined;
|
||||
}
|
||||
|
||||
const app = createServer(store as never, {
|
||||
...(primaryEngine ? { engine: primaryEngine } : {}),
|
||||
engineManager,
|
||||
centralCore,
|
||||
authStorage: wrappedAuthStorage,
|
||||
modelRegistry,
|
||||
...(pluginStore && pluginLoader ? { pluginStore: pluginStore as never, pluginLoader, pluginRunner: pluginLoader } : {}),
|
||||
onProjectFirstAccessed: (projectId: string) => engineManager.onProjectAccessed(projectId),
|
||||
});
|
||||
server = app.listen(0);
|
||||
|
||||
Reference in New Issue
Block a user