feat(FN-3239): auto-install dependency graph plugin in CLI daemon, dashboar
Merged the three-step FN-3239 feature: auto-installing the dependency graph plugin on first run across the `daemon`, `dashboard`, and `serve` commands, with the core logic centralized in `bundled-plugin-install.ts`, bundling configuration in `tsup.config.ts`, and a documentation file for the default Fusion-Task-Id: FN-3239
This commit is contained in:
@@ -58,6 +58,7 @@ import { resolveSelfExtension } from "./self-extension.js";
|
||||
import { createReadOnlyAuthFileStorage, mergeAuthStorageReads, wrapAuthStorageWithApiKeyProviders } from "./provider-auth.js";
|
||||
import { getFusionAuthPath, getLegacyAuthPaths, getModelRegistryModelsPath, getPackageManagerAgentDir } from "./auth-paths.js";
|
||||
import { resolveProject } from "../project-context.js";
|
||||
import { ensureBundledDependencyGraphPluginInstalled } from "../plugins/bundled-plugin-install.js";
|
||||
|
||||
const DIAGNOSTIC_INTERVAL_MS = 30 * 60 * 1000; // 30 minutes
|
||||
let daemonStartTime = 0;
|
||||
@@ -374,6 +375,17 @@ export async function runDaemon(opts: DaemonOptions = {}) {
|
||||
taskStore: store,
|
||||
});
|
||||
|
||||
try {
|
||||
const installStatus = await ensureBundledDependencyGraphPluginInstalled(pluginStore, pluginLoader);
|
||||
if (installStatus === "installed") {
|
||||
console.log("[plugins] Installed bundled Dependency Graph plugin");
|
||||
} else if (installStatus === "missing-bundle") {
|
||||
console.warn("[plugins] Bundled Dependency Graph plugin was not found in this build");
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`[plugins] Failed to auto-install bundled Dependency Graph plugin: ${err instanceof Error ? err.message : err}`);
|
||||
}
|
||||
|
||||
// Auto-load all enabled plugins so runtime UI (NewAgentDialog, AgentDetailView)
|
||||
// can discover installed runtimes like Hermes and OpenClaw.
|
||||
try {
|
||||
|
||||
@@ -54,6 +54,7 @@ import {
|
||||
} from "./droid-cli-extension.js";
|
||||
import { getCachedUpdateStatus, isUpdateCheckEnabled } from "../update-cache.js";
|
||||
import { resolveSelfExtension } from "./self-extension.js";
|
||||
import { ensureBundledDependencyGraphPluginInstalled } from "../plugins/bundled-plugin-install.js";
|
||||
import { registerCustomProviders, reregisterCustomProviders } from "./custom-provider-registry.js";
|
||||
import { DashboardTUI, DashboardLogSink, isTTYAvailable, type SystemInfo, type GitStatus, type GitCommit, type GitCommitDetail, type GitBranch, type GitWorktree, type FileEntry, type FileReadResult, type TaskStep as TUITaskStep, type TaskLogEntry as TUITaskLogEntry, type TaskDetailData, type TaskEvent } from "./dashboard-tui/index.js";
|
||||
|
||||
@@ -1077,6 +1078,20 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
taskStore: store,
|
||||
});
|
||||
|
||||
try {
|
||||
const installStatus = await ensureBundledDependencyGraphPluginInstalled(pluginStore, pluginLoader);
|
||||
if (installStatus === "installed") {
|
||||
logSink.log("Installed bundled Dependency Graph plugin", "plugins");
|
||||
} else if (installStatus === "missing-bundle") {
|
||||
logSink.log("Bundled Dependency Graph plugin was not found in this build", "plugins");
|
||||
}
|
||||
} catch (err) {
|
||||
logSink.log(
|
||||
`Failed to auto-install bundled Dependency Graph plugin: ${err instanceof Error ? err.message : err}`,
|
||||
"plugins",
|
||||
);
|
||||
}
|
||||
|
||||
// Auto-load all enabled plugins so runtime UI (NewAgentDialog, AgentDetailView)
|
||||
// can discover installed runtimes like Hermes and OpenClaw.
|
||||
try {
|
||||
|
||||
@@ -59,6 +59,7 @@ import {
|
||||
} from "./droid-cli-extension.js";
|
||||
import { resolveSelfExtension } from "./self-extension.js";
|
||||
import { registerCustomProviders, reregisterCustomProviders } from "./custom-provider-registry.js";
|
||||
import { ensureBundledDependencyGraphPluginInstalled } from "../plugins/bundled-plugin-install.js";
|
||||
|
||||
const DIAGNOSTIC_INTERVAL_MS = 30 * 60 * 1000; // 30 minutes
|
||||
let diagnosticIntervalHandle: ReturnType<typeof setInterval> | null = null;
|
||||
@@ -428,6 +429,17 @@ export async function runServe(
|
||||
taskStore: store,
|
||||
});
|
||||
|
||||
try {
|
||||
const installStatus = await ensureBundledDependencyGraphPluginInstalled(pluginStore, pluginLoader);
|
||||
if (installStatus === "installed") {
|
||||
console.log("[plugins] Installed bundled Dependency Graph plugin");
|
||||
} else if (installStatus === "missing-bundle") {
|
||||
console.warn("[plugins] Bundled Dependency Graph plugin was not found in this build");
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`[plugins] Failed to auto-install bundled Dependency Graph plugin: ${err instanceof Error ? err.message : err}`);
|
||||
}
|
||||
|
||||
// Auto-load all enabled plugins so runtime UI (NewAgentDialog, AgentDetailView)
|
||||
// can discover installed runtimes like Hermes and OpenClaw.
|
||||
try {
|
||||
|
||||
67
packages/cli/src/plugins/bundled-plugin-install.ts
Normal file
67
packages/cli/src/plugins/bundled-plugin-install.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { validatePluginManifest, type PluginLoader, type PluginManifest, type PluginStore } from "@fusion/core";
|
||||
|
||||
const DEPENDENCY_GRAPH_PLUGIN_ID = "fusion-plugin-dependency-graph";
|
||||
|
||||
function getCandidatePluginPaths(): string[] {
|
||||
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
||||
const cliPackageRoot = resolve(moduleDir, "..", "..");
|
||||
|
||||
return [
|
||||
join(cliPackageRoot, "dist", "plugins", DEPENDENCY_GRAPH_PLUGIN_ID),
|
||||
join(cliPackageRoot, "plugins", DEPENDENCY_GRAPH_PLUGIN_ID),
|
||||
join(cliPackageRoot, "..", "..", "plugins", DEPENDENCY_GRAPH_PLUGIN_ID),
|
||||
];
|
||||
}
|
||||
|
||||
async function loadManifest(pluginDir: string): Promise<PluginManifest> {
|
||||
const manifestPath = join(pluginDir, "manifest.json");
|
||||
const content = await readFile(manifestPath, "utf-8");
|
||||
const manifest = JSON.parse(content);
|
||||
const validation = validatePluginManifest(manifest);
|
||||
if (!validation.valid) {
|
||||
throw new Error(`Invalid plugin manifest: ${validation.errors.join(", ")}`);
|
||||
}
|
||||
return manifest;
|
||||
}
|
||||
|
||||
function resolveBundledDependencyGraphPath(): string | null {
|
||||
for (const path of getCandidatePluginPaths()) {
|
||||
if (existsSync(join(path, "manifest.json"))) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function ensureBundledDependencyGraphPluginInstalled(
|
||||
pluginStore: PluginStore,
|
||||
pluginLoader: PluginLoader,
|
||||
): Promise<"installed" | "already-installed" | "missing-bundle"> {
|
||||
try {
|
||||
await pluginStore.getPlugin(DEPENDENCY_GRAPH_PLUGIN_ID);
|
||||
return "already-installed";
|
||||
} catch {
|
||||
// Continue; plugin not installed yet.
|
||||
}
|
||||
|
||||
const bundledPath = resolveBundledDependencyGraphPath();
|
||||
if (!bundledPath) {
|
||||
return "missing-bundle";
|
||||
}
|
||||
|
||||
const manifest = await loadManifest(bundledPath);
|
||||
const plugin = await pluginStore.registerPlugin({
|
||||
manifest,
|
||||
path: bundledPath,
|
||||
});
|
||||
|
||||
if (plugin.enabled) {
|
||||
await pluginLoader.loadPlugin(plugin.id);
|
||||
}
|
||||
|
||||
return "installed";
|
||||
}
|
||||
Reference in New Issue
Block a user