feat(FN-3182): scope plugin installs globally with project enablement

- Store plugin installation metadata in central DB while preserving per-project enable/disable state
- Update plugin CLI and dashboard Plugin Manager copy/behavior to distinguish global install from project enablement
- Expand plugin store and loader test coverage, including legacy migration and scoped install assertions
- Update runtime plugin e2e tests to use isolated central DB directories and document the new semantics
- Add a changeset for @runfusion/fusion describing the plugin scope behavior change

Fusion-Task-Id: FN-3182
This commit is contained in:
Fusion
2026-05-07 22:11:53 -07:00
committed by gsxdsm
parent 979cf115d9
commit b7f68d7dc0
20 changed files with 617 additions and 279 deletions

View File

@@ -74,7 +74,7 @@ describe("Droid runtime E2E pipeline", () => {
});
it("loads Droid plugin and creates sessions through Droid runtime without createFnAgent", async () => {
const pluginStore = new PluginStore(testRoot, { inMemoryDb: true });
const pluginStore = new PluginStore(testRoot, { inMemoryDb: true, centralGlobalDir: testRoot });
await pluginStore.init();
await pluginStore.registerPlugin({
@@ -133,7 +133,7 @@ describe("Droid runtime E2E pipeline", () => {
});
it("falls back to default pi runtime when Droid plugin is not installed", async () => {
const pluginStore = new PluginStore(testRoot, { inMemoryDb: true });
const pluginStore = new PluginStore(testRoot, { inMemoryDb: true, centralGlobalDir: testRoot });
await pluginStore.init();
const taskStore = createTaskStoreMock(testRoot);

View File

@@ -105,7 +105,7 @@ describe("Hermes runtime E2E pipeline", () => {
});
it("loads Hermes plugin and executes through Hermes runtime without createFnAgent", async () => {
const pluginStore = new PluginStore(testRoot, { inMemoryDb: true });
const pluginStore = new PluginStore(testRoot, { inMemoryDb: true, centralGlobalDir: testRoot });
await pluginStore.init();
await pluginStore.registerPlugin({
@@ -170,7 +170,7 @@ describe("Hermes runtime E2E pipeline", () => {
});
it("reuses Hermes adapter instance without compatibility wrapping when runtime is AgentRuntime-shaped", async () => {
const pluginStore = new PluginStore(testRoot, { inMemoryDb: true });
const pluginStore = new PluginStore(testRoot, { inMemoryDb: true, centralGlobalDir: testRoot });
await pluginStore.init();
await pluginStore.registerPlugin({
@@ -218,7 +218,7 @@ describe("Hermes runtime E2E pipeline", () => {
});
it("falls back to default pi runtime when Hermes plugin is not installed", async () => {
const pluginStore = new PluginStore(testRoot, { inMemoryDb: true });
const pluginStore = new PluginStore(testRoot, { inMemoryDb: true, centralGlobalDir: testRoot });
await pluginStore.init();
const taskStore = createTaskStoreMock(testRoot);
@@ -253,7 +253,7 @@ describe("Hermes runtime E2E pipeline", () => {
// attach the resolved runtime's promptWithFallback onto the session object.
// Without the fix, pi.promptWithFallback (pi.ts:175) would fall through to
// pi's own session.prompt() instead of dispatching to HermesRuntimeAdapter.
const pluginStore = new PluginStore(testRoot, { inMemoryDb: true });
const pluginStore = new PluginStore(testRoot, { inMemoryDb: true, centralGlobalDir: testRoot });
await pluginStore.init();
await pluginStore.registerPlugin({

View File

@@ -153,7 +153,7 @@ describe("OpenClaw runtime E2E pipeline", () => {
});
it("loads OpenClaw plugin and executes through OpenClaw runtime", async () => {
const pluginStore = new PluginStore(testRoot, { inMemoryDb: true });
const pluginStore = new PluginStore(testRoot, { inMemoryDb: true, centralGlobalDir: testRoot });
await pluginStore.init();
await pluginStore.registerPlugin({
@@ -240,7 +240,7 @@ describe("OpenClaw runtime E2E pipeline", () => {
});
it("falls back to default pi runtime when OpenClaw plugin is not installed", async () => {
const pluginStore = new PluginStore(testRoot, { inMemoryDb: true });
const pluginStore = new PluginStore(testRoot, { inMemoryDb: true, centralGlobalDir: testRoot });
await pluginStore.init();
const taskStore = createTaskStoreMock(testRoot);

View File

@@ -125,7 +125,7 @@ describe("Paperclip runtime E2E pipeline", () => {
});
it("loads Paperclip plugin and executes through Paperclip runtime", async () => {
const pluginStore = new PluginStore(testRoot, { inMemoryDb: true });
const pluginStore = new PluginStore(testRoot, { inMemoryDb: true, centralGlobalDir: testRoot });
await pluginStore.init();
await pluginStore.registerPlugin({
@@ -187,7 +187,7 @@ describe("Paperclip runtime E2E pipeline", () => {
});
it("falls back to default pi runtime when Paperclip plugin is not installed", async () => {
const pluginStore = new PluginStore(testRoot, { inMemoryDb: true });
const pluginStore = new PluginStore(testRoot, { inMemoryDb: true, centralGlobalDir: testRoot });
await pluginStore.init();
const taskStore = createTaskStoreMock(testRoot);