#!/usr/bin/env node
import { existsSync, readFileSync } from "node:fs";
import { createRequire } from "node:module";
import { basename, isAbsolute, join, resolve } from "node:path";
import { createLocalWorkspaceRegistry } from "./localWorkspaces.js";
import { resolveBoringUiCliPackageRoot } from "./pluginDiscovery.js";
const MODE_MAP = {
  "local": "direct",
  // no sandbox, full network access
  "local-sandbox": "local"
  // bwrap isolated, no network (Linux only)
};
const require2 = createRequire(import.meta.url);
const PLUGIN_CLI_PACKAGE_NAME = "@hachej/boring-ui-plugin-cli";
const CLI_VERSION = (() => {
  try {
    const pkg = require2("../../package.json");
    return pkg.version ?? "0.0.0";
  } catch {
    return "0.0.0";
  }
})();
function httpError(message, statusCode) {
  const error = new Error(message);
  error.statusCode = statusCode;
  return error;
}
function firstString(value) {
  if (typeof value === "string") return value;
  if (!Array.isArray(value)) return void 0;
  return value.find((item) => typeof item === "string");
}
function resolveWorkspaceIdFromRequest(request) {
  const headers = request.headers ?? {};
  const headerValue = headers["x-boring-workspace-id"] ?? Object.entries(headers).find(([key]) => key.toLowerCase() === "x-boring-workspace-id")?.[1];
  const query = request.query;
  const raw = firstString(headerValue) ?? firstString(query?.workspaceId) ?? "";
  const workspaceId = raw.trim();
  if (!workspaceId) throw httpError("workspace id is required", 400);
  if (workspaceId.includes("\0") || workspaceId.includes("/") || workspaceId.includes("\\") || workspaceId.includes("..") || isAbsolute(workspaceId)) {
    throw httpError("invalid workspace id", 400);
  }
  return workspaceId;
}
function toCoreWorkspace(workspace) {
  return {
    id: workspace.id,
    name: workspace.name,
    slug: workspace.id,
    isDefault: false,
    createdAt: workspace.createdAt,
    updatedAt: workspace.updatedAt,
    unavailable: !workspace.available,
    path: workspace.path
  };
}
function isUsableBoringUiPluginCliPackageRoot(candidate) {
  try {
    const pkg = JSON.parse(readFileSync(join(candidate, "package.json"), "utf8"));
    return pkg.name === PLUGIN_CLI_PACKAGE_NAME && existsSync(join(candidate, "dist", "bin.js"));
  } catch {
    return false;
  }
}
function resolveBoringUiPluginCliPackageRoot() {
  const cliRoot = resolveBoringUiCliPackageRoot();
  const candidate = resolve(cliRoot, "..", "plugin-cli");
  return isUsableBoringUiPluginCliPackageRoot(candidate) ? candidate : null;
}
function createBoringUiCliRuntimePlugin() {
  const useLocal = process.env.BORING_USE_LOCAL_PACKAGES === "1";
  const packageRoot = useLocal ? resolveBoringUiPluginCliPackageRoot() : null;
  return {
    id: "boring-ui-plugin-cli-runtime",
    provisioning: {
      nodePackages: [{
        id: "boring-ui-plugin-cli",
        packageName: PLUGIN_CLI_PACKAGE_NAME,
        ...packageRoot ? { packageRoot } : { version: CLI_VERSION },
        expectedBins: ["boring-ui-plugin"]
      }]
    }
  };
}
async function provisionCliWorkspaceRuntime(opts) {
  if (opts.provisionWorkspace === false) return void 0;
  const agent = await import("@hachej/boring-agent/server");
  const runtimeLayout = opts.runtimeLayout ?? agent.getBoringAgentRuntimePaths(opts.workspaceRoot);
  const adapter = opts.adapter ?? opts.modeAdapter?.createProvisioningAdapter?.(runtimeLayout) ?? agent.resolveMode(opts.mode).createProvisioningAdapter?.(runtimeLayout);
  if (!adapter) {
    throw new Error(`runtime mode ${opts.mode} does not support workspace provisioning`);
  }
  const result = await agent.provisionWorkspaceRuntime({
    plugins: [createBoringUiCliRuntimePlugin(), ...opts.plugins ?? []],
    adapter,
    runtimeLayout
  });
  return {
    ...result,
    env: {
      ...result.env,
      BORING_AGENT_WORKSPACE_LOCAL_PLUGIN_ROOTS: opts.mode === "direct" || opts.mode === "local" ? "1" : "0"
    }
  };
}
const FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID = "folder";
const RUNTIME_PLUGIN_TRUST_LABEL = "Trusted local runtime plugins";
const RUNTIME_PLUGIN_TRUST_DESCRIPTION = "Loads plugin UI code from trusted local Pi extension roots through the CLI-owned runtime module host.";
function createRuntimePluginDiagnosticsStore() {
  const byWorkspace = /* @__PURE__ */ new Map();
  const frontErrorsByWorkspace = /* @__PURE__ */ new Map();
  function upsert(workspaceId, pluginId) {
    const workspace = byWorkspace.get(workspaceId) ?? /* @__PURE__ */ new Map();
    byWorkspace.set(workspaceId, workspace);
    const existing = workspace.get(pluginId) ?? {
      workspaceId,
      pluginId,
      recent: []
    };
    workspace.set(pluginId, existing);
    return existing;
  }
  return {
    record(diagnostic) {
      if (!diagnostic.workspaceId || !diagnostic.pluginId) return;
      const entry = upsert(diagnostic.workspaceId, diagnostic.pluginId);
      const now = Date.now();
      entry.lastDiagnostic = diagnostic;
      entry.recent = [...entry.recent, diagnostic].slice(-12);
      if (diagnostic.revision !== void 0) entry.revision = diagnostic.revision;
      if (diagnostic.requestedPath) entry.lastRequestedPath = diagnostic.requestedPath;
      if (diagnostic.resolvedPath) entry.lastResolvedPath = diagnostic.resolvedPath;
      const details = diagnostic.details ?? {};
      if (typeof details.rootDir === "string") entry.rootDir = details.rootDir;
      if (typeof details.entryUrl === "string") entry.entryUrl = details.entryUrl;
      if (typeof diagnostic.requestedPath === "string") entry.frontEntrySubpath = diagnostic.requestedPath;
      if (diagnostic.stage === "track" && diagnostic.outcome === "tracked") {
        entry.lastErrorCode = void 0;
        entry.lastErrorMessage = void 0;
        entry.lastErrorStage = void 0;
        const storedFrontError = frontErrorsByWorkspace.get(diagnostic.workspaceId)?.get(diagnostic.pluginId);
        if (storedFrontError && diagnostic.revision !== void 0 && storedFrontError.revision < diagnostic.revision) {
          frontErrorsByWorkspace.get(diagnostic.workspaceId)?.delete(diagnostic.pluginId);
        }
      }
      if (diagnostic.stage === "cache") entry.lastRequestAt = now;
      if (diagnostic.stage === "transform" && diagnostic.outcome === "served") {
        entry.lastTransformAt = now;
        entry.lastTransformDurationMs = diagnostic.durationMs;
      }
      if (diagnostic.stage === "serve" && diagnostic.outcome === "served") {
        entry.lastServeAt = now;
        entry.lastServeDurationMs = diagnostic.durationMs;
        entry.lastErrorCode = void 0;
        entry.lastErrorMessage = void 0;
        entry.lastErrorStage = void 0;
      }
      if (diagnostic.outcome === "rejected") {
        entry.lastRejectedAt = now;
        entry.lastErrorCode = diagnostic.code;
        entry.lastErrorMessage = diagnostic.msg;
        entry.lastErrorStage = diagnostic.stage;
      }
      if (diagnostic.stage === "cleanup" && diagnostic.outcome === "disposed") {
        entry.lastDisposedAt = now;
      }
    },
    snapshot(workspaceId) {
      return [...byWorkspace.get(workspaceId)?.values() ?? []].map((entry) => ({ ...entry, recent: [...entry.recent] })).sort((a, b) => a.pluginId.localeCompare(b.pluginId));
    },
    recordFrontError(workspaceId, error) {
      const forWorkspace = frontErrorsByWorkspace.get(workspaceId) ?? /* @__PURE__ */ new Map();
      frontErrorsByWorkspace.set(workspaceId, forWorkspace);
      const existing = forWorkspace.get(error.pluginId);
      if (existing && existing.revision > error.revision) return;
      forWorkspace.set(error.pluginId, error);
    },
    clearFrontError(workspaceId, pluginId) {
      frontErrorsByWorkspace.get(workspaceId)?.delete(pluginId);
    },
    frontErrors(workspaceId) {
      return [...frontErrorsByWorkspace.get(workspaceId)?.values() ?? []].sort((a, b) => a.pluginId.localeCompare(b.pluginId));
    },
    disposeWorkspace(workspaceId) {
      byWorkspace.delete(workspaceId);
      frontErrorsByWorkspace.delete(workspaceId);
    }
  };
}
function syncRuntimeHostFromPluginEvents(runtimeHost, workspaceId, events) {
  for (const event of events) {
    if (event.type === "boring.plugin.unload" || event.type === "boring.plugin.load" && !event.frontTarget) {
      runtimeHost.untrackPlugin(workspaceId, event.id);
    }
  }
}
function buildRuntimePluginDiagnosticsResponse(args) {
  const byPlugin = /* @__PURE__ */ new Map();
  for (const plugin of args.loaded) {
    byPlugin.set(plugin.id, {
      id: plugin.id,
      ...plugin.version ? { version: plugin.version } : {},
      ...plugin.rootDir ? { rootDir: plugin.rootDir } : {},
      ...plugin.frontPath ? { frontPath: plugin.frontPath } : {},
      ...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {},
      ...plugin.revision !== void 0 ? { serverLoadedRevision: plugin.revision } : {}
    });
  }
  for (const error of args.errors) {
    const current = byPlugin.get(error.id) ?? { id: error.id };
    byPlugin.set(error.id, {
      ...current,
      serverError: error.message
    });
  }
  for (const hostEntry of args.host) {
    const current = byPlugin.get(hostEntry.pluginId) ?? { id: hostEntry.pluginId };
    byPlugin.set(hostEntry.pluginId, {
      ...current,
      ...current.rootDir ? {} : hostEntry.rootDir ? { rootDir: hostEntry.rootDir } : {},
      ...current.frontPath ? {} : hostEntry.frontEntrySubpath ? { frontPath: hostEntry.frontEntrySubpath } : {},
      ...current.frontTarget ? {} : hostEntry.entryUrl ? { frontTarget: { kind: "native", entryUrl: hostEntry.entryUrl, revision: hostEntry.revision ?? 0, trust: "local-trusted-native" } } : {},
      host: hostEntry
    });
  }
  for (const frontError of args.frontErrors ?? []) {
    const current = byPlugin.get(frontError.pluginId) ?? { id: frontError.pluginId };
    byPlugin.set(frontError.pluginId, {
      ...current,
      frontError
    });
  }
  return {
    workspaceId: args.workspaceId,
    plugins: [...byPlugin.values()].sort((a, b) => a.id.localeCompare(b.id))
  };
}
function parseFrontErrorReport(pluginId, body) {
  if (typeof body !== "object" || body === null) return null;
  const record = body;
  const message = typeof record.message === "string" ? record.message : "";
  if (!pluginId || !message) return null;
  const revisionRaw = record.revision;
  const revision = typeof revisionRaw === "number" && Number.isFinite(revisionRaw) ? revisionRaw : 0;
  return {
    pluginId,
    revision,
    message,
    ...typeof record.url === "string" ? { url: record.url } : {},
    reportedAt: Date.now()
  };
}
async function createFolderModeApp(opts) {
  const workspaceRoot = resolve(opts.workspaceRoot);
  const projectName = opts.projectName ?? (basename(workspaceRoot) || "workspace");
  const [{ createWorkspaceAgentServer, readWorkspacePluginPackageRuntimePlugins }, { createPluginFrontRuntimeHost }, pluginDiscovery] = await Promise.all([
    import("@hachej/boring-workspace/app/server"),
    import("./pluginFrontRuntime.js"),
    import("./pluginDiscovery.js")
  ]);
  const diagnosticsStore = createRuntimePluginDiagnosticsStore();
  const runtimeHost = await createPluginFrontRuntimeHost({
    onDiagnostic: (diagnostic) => diagnosticsStore.record(diagnostic)
  });
  const pluginDirs = pluginDiscovery.resolveCliBoringPluginDirs(workspaceRoot);
  const runtimeProvisioning = await provisionCliWorkspaceRuntime({
    workspaceRoot,
    mode: opts.mode,
    provisionWorkspace: opts.provisionWorkspace,
    plugins: readWorkspacePluginPackageRuntimePlugins(pluginDirs)
  });
  const app = await createWorkspaceAgentServer({
    workspaceRoot,
    mode: opts.mode,
    logger: false,
    provisionWorkspace: false,
    runtimeProvisioning,
    // CLI-bundled internal plugins, resolved to absolute package dirs. This
    // drives the server-side install array (boot-time routes/agentTools);
    // additionalBoringPluginDirs only feeds the asset-manager scan.
    defaultPluginPackages: pluginDiscovery.resolveCliDefaultPluginPackagePaths(),
    additionalBoringPluginDirs: pluginDirs,
    boringPluginFrontTargetResolver: runtimeHost.createFrontTargetResolver(FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID)
  });
  await runtimeHost.registerRoutes(app);
  const folderAssetManager = app.__boringAssetManager;
  const closeFolderRuntimeCleanup = folderAssetManager?.subscribe((event) => {
    syncRuntimeHostFromPluginEvents(runtimeHost, FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID, [event]);
  });
  if (closeFolderRuntimeCleanup) {
    app.addHook("onClose", async () => {
      closeFolderRuntimeCleanup();
    });
  }
  app.get("/api/v1/runtime-plugin-diagnostics", async () => {
    const manager = app.__boringAssetManager;
    return buildRuntimePluginDiagnosticsResponse({
      workspaceId: FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID,
      loaded: manager?.inspectLoaded() ?? [],
      errors: manager?.getErrors() ?? [],
      host: diagnosticsStore.snapshot(FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID),
      frontErrors: diagnosticsStore.frontErrors(FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID)
    });
  });
  app.post("/api/v1/agent-plugins/:id/front-error", async (request, reply) => {
    const { id } = request.params;
    const report = parseFrontErrorReport(id, request.body);
    if (!report) return reply.code(400).send({ error: "invalid_front_error_report" });
    diagnosticsStore.recordFrontError(FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID, report);
    return reply.code(204).send();
  });
  app.get("/api/v1/workspace/meta", async () => ({
    workspaceId: "default",
    workspaceRoot,
    projectName,
    version: CLI_VERSION,
    runtimePluginFrontLoadingEnabled: true,
    runtimePluginTrustLabel: RUNTIME_PLUGIN_TRUST_LABEL,
    runtimePluginTrustDescription: RUNTIME_PLUGIN_TRUST_DESCRIPTION,
    runtimePluginDiagnosticsEnabled: true
  }));
  return app;
}
async function createWorkspacesModeApp(opts) {
  const [workspaceAppServer, workspaceServer, agentServer, agentShared, fastifyModule, { createPluginFrontRuntimeHost }, pluginDiscovery] = await Promise.all([
    import("@hachej/boring-workspace/app/server"),
    import("@hachej/boring-workspace/server"),
    import("@hachej/boring-agent/server"),
    import("@hachej/boring-agent/shared"),
    import("fastify"),
    import("./pluginFrontRuntime.js"),
    import("./pluginDiscovery.js")
  ]);
  const registry = createLocalWorkspaceRegistry(opts.registryPath);
  const app = fastifyModule.default({ logger: false, bodyLimit: 16 * 1024 * 1024 });
  const diagnosticsStore = createRuntimePluginDiagnosticsStore();
  const runtimeHost = await createPluginFrontRuntimeHost({
    onDiagnostic: (diagnostic) => diagnosticsStore.record(diagnostic)
  });
  await runtimeHost.registerRoutes(app);
  await app.register(workspaceServer.runtimeBackendGateway, {
    registry: {
      dispatch: async (dispatchRequest) => {
        if (!dispatchRequest.workspaceId) {
          throw new workspaceServer.RuntimeBackendError(
            agentShared.ErrorCode.enum.RUNTIME_PLUGIN_NOT_FOUND,
            404,
            "runtime backend dispatch requires a workspace id (x-boring-workspace-id header)"
          );
        }
        const workspace = await registry.get(dispatchRequest.workspaceId);
        if (!workspace?.available) {
          throw new workspaceServer.RuntimeBackendError(
            agentShared.ErrorCode.enum.RUNTIME_PLUGIN_NOT_FOUND,
            404,
            `runtime backend dispatch: unknown workspace ${dispatchRequest.workspaceId}`
          );
        }
        const runtime = await getLoadedPluginRuntime(workspace);
        return runtime.backendRegistry.dispatch({ ...dispatchRequest, workspaceId: resolve(workspace.path) });
      }
    }
  });
  const bridges = /* @__PURE__ */ new Map();
  const workspaceEventClosers = /* @__PURE__ */ new Map();
  const pluginRuntimes = /* @__PURE__ */ new Map();
  const pluginPiSnapshots = /* @__PURE__ */ new Map();
  const runtimeProvisioningByWorkspace = /* @__PURE__ */ new Map();
  function getBridge(workspaceId) {
    let bridge = bridges.get(workspaceId);
    if (!bridge) {
      bridge = workspaceServer.createInMemoryBridge();
      bridges.set(workspaceId, bridge);
    }
    return bridge;
  }
  async function requireWorkspace(workspaceId) {
    const workspace = await registry.get(workspaceId);
    if (!workspace) throw httpError("unknown workspace", 404);
    if (!workspace.available) throw httpError("workspace folder unavailable", 409);
    return workspace;
  }
  async function workspaceFromRequest(request) {
    return await requireWorkspace(resolveWorkspaceIdFromRequest(request));
  }
  function pluginRuntimeKey(workspace) {
    return `${workspace.id}:${workspace.path}`;
  }
  function syncLoadedPluginPiSnapshot(workspace, manager) {
    pluginPiSnapshots.set(pluginRuntimeKey(workspace), manager.inspectLoadedPiSnapshot());
  }
  function getOrCreatePluginRuntime(workspace) {
    const key = pluginRuntimeKey(workspace);
    let runtime = pluginRuntimes.get(key);
    if (!runtime) {
      const manager = pluginDiscovery.createCliPluginAssetManager(workspace.path, {
        frontTargetResolver: runtimeHost.createFrontTargetResolver(workspace.id)
      });
      const backendRegistry = new workspaceServer.RuntimeBackendRegistry();
      runtime = {
        manager,
        backendRegistry,
        ensureLoaded: manager.load().then(async () => {
          syncLoadedPluginPiSnapshot(workspace, manager);
          await backendRegistry.reloadFromLoadedPlugins(manager.inspectLoaded());
          void Promise.resolve().then(() => runtimeHost.warmupWorkspace(workspace.id)).catch(() => void 0);
        })
      };
      pluginRuntimes.set(key, runtime);
    }
    return runtime;
  }
  async function getLoadedPluginRuntime(workspace) {
    const runtime = getOrCreatePluginRuntime(workspace);
    await runtime.ensureLoaded;
    return runtime;
  }
  function getLoadedPluginPiSnapshot(workspace) {
    return pluginPiSnapshots.get(pluginRuntimeKey(workspace)) ?? {
      additionalSkillPaths: [],
      packages: [],
      extensionPaths: []
    };
  }
  async function disposeWorkspaceRuntime(workspace) {
    for (const close of workspaceEventClosers.get(workspace.id) ?? []) {
      try {
        close();
      } catch {
      }
    }
    workspaceEventClosers.delete(workspace.id);
    const runtimeKey = pluginRuntimeKey(workspace);
    const runtime = pluginRuntimes.get(runtimeKey);
    if (runtime) {
      try {
        await runtime.backendRegistry.close();
      } catch {
      }
    }
    pluginRuntimes.delete(runtimeKey);
    pluginPiSnapshots.delete(runtimeKey);
    runtimeProvisioningByWorkspace.delete(workspace.id);
    bridges.delete(workspace.id);
    diagnosticsStore.disposeWorkspace(workspace.id);
    await runtimeHost.disposeWorkspace(workspace.id);
  }
  function reloadDiagnostics(scan) {
    return scan.errors.map((error) => ({
      source: "workspaces-plugin-manager",
      message: error.message,
      pluginId: error.id
    }));
  }
  app.get("/api/v1/local-workspaces", async () => ({
    workspaces: await registry.list()
  }));
  app.post("/api/v1/local-workspaces", async (request, reply) => {
    const body = request.body;
    if (typeof body?.path !== "string" || !body.path.trim()) {
      return reply.code(400).send({ error: "workspace path is required" });
    }
    try {
      const workspace = await registry.add(body.path, {
        name: typeof body.name === "string" ? body.name : void 0
      });
      return { workspace };
    } catch (error) {
      return reply.code(400).send({
        error: error instanceof Error ? error.message : "unable to add workspace"
      });
    }
  });
  app.delete("/api/v1/local-workspaces/:id", async (request, reply) => {
    const { id } = request.params;
    const workspace = await registry.get(id);
    await registry.remove(id);
    if (workspace) await disposeWorkspaceRuntime(workspace);
    return reply.send({ ok: true });
  });
  app.get("/api/v1/workspaces", async () => ({
    workspaces: (await registry.list()).map(toCoreWorkspace)
  }));
  app.get("/api/v1/workspaces/:id", async (request, reply) => {
    const { id } = request.params;
    const workspace = await registry.get(id);
    if (!workspace) return reply.code(404).send({ error: "workspace not found" });
    return { workspace: toCoreWorkspace(workspace), role: "owner" };
  });
  await app.register(agentServer.registerAgentRoutes, {
    mode: opts.mode,
    systemPromptAppend: workspaceAppServer.buildWorkspaceContextPrompt(),
    getSystemPromptDynamic: async ({ workspaceId }) => {
      const workspace = await requireWorkspace(workspaceId);
      await getLoadedPluginRuntime(workspace);
      return getLoadedPluginPiSnapshot(workspace).systemPromptAppend;
    },
    getWorkspaceId: async (request) => (await workspaceFromRequest(request)).id,
    getWorkspaceRoot: async (workspaceId) => (await requireWorkspace(workspaceId)).path,
    getSessionNamespace: async ({ workspaceId }) => `local-workspace-${workspaceId}`,
    provisionRuntime: async ({ workspaceId, workspaceRoot, runtimeMode, runtimeLayout, provisioningAdapter }) => {
      if (runtimeProvisioningByWorkspace.has(workspaceId)) {
        return runtimeProvisioningByWorkspace.get(workspaceId);
      }
      const provisioned = await provisionCliWorkspaceRuntime({
        workspaceRoot,
        mode: runtimeMode,
        provisionWorkspace: opts.provisionWorkspace,
        adapter: provisioningAdapter,
        runtimeLayout,
        plugins: workspaceAppServer.readWorkspacePluginPackageRuntimePlugins(pluginDiscovery.resolveCliBoringPluginDirs(workspaceRoot))
      });
      runtimeProvisioningByWorkspace.set(workspaceId, provisioned);
      return provisioned;
    },
    beforeReload: async ({ workspaceId }) => {
      const workspace = await requireWorkspace(workspaceId);
      const runtime = await getLoadedPluginRuntime(workspace);
      runtime.manager.setPluginDirs(pluginDiscovery.resolveCliBoringPluginDirs(workspace.path));
      const scan = await runtime.manager.load();
      syncLoadedPluginPiSnapshot(workspace, runtime.manager);
      syncRuntimeHostFromPluginEvents(runtimeHost, workspaceId, scan.events);
      const backendReload = await runtime.backendRegistry.reloadFromLoadedPlugins(runtime.manager.inspectLoaded());
      return {
        restart_warnings: workspaceServer.collectRestartWarnings(scan.events),
        diagnostics: [
          ...reloadDiagnostics(scan),
          ...backendReload.diagnostics.map((diagnostic) => ({
            source: diagnostic.source,
            message: diagnostic.message,
            ...diagnostic.pluginId ? { pluginId: diagnostic.pluginId } : {}
          }))
        ]
      };
    },
    getPluginDiagnostics: async ({ workspaceId }) => {
      const workspace = await requireWorkspace(workspaceId);
      const runtime = await getLoadedPluginRuntime(workspace);
      return [
        ...runtime.manager.getErrors().map((error) => ({
          source: "plugin-load",
          message: error.message,
          ...error.id ? { pluginId: error.id } : {}
        })),
        ...runtime.manager.preflight().errors.map((error) => ({
          source: "plugin-preflight",
          message: `${error.code}: ${error.message} (${error.pluginDir})`,
          ...error.pluginId ? { pluginId: error.pluginId } : {}
        })),
        // Browser-reported front import failures: the server scan/transform is
        // green, so without these a plugin that never renders looks healthy to
        // the agent. The plugin_diagnostics tool consumes this array.
        ...diagnosticsStore.frontErrors(workspace.id).map((error) => ({
          source: "plugin-front",
          message: error.message,
          pluginId: error.pluginId
        }))
      ];
    },
    getPi: async ({ workspaceId, workspaceRoot }) => {
      const workspace = await requireWorkspace(workspaceId);
      await getLoadedPluginRuntime(workspace);
      return {
        additionalSkillPaths: [join(workspaceRoot, ".agents", "skills")],
        packages: [],
        extensionPaths: [],
        getHotReloadableResources: () => getLoadedPluginPiSnapshot(workspace)
      };
    },
    getExtraTools: async ({ workspaceId, workspaceRoot, workspaceFsCapability }) => [
      ...workspaceServer.createWorkspaceUiTools(getBridge(workspaceId), {
        workspaceRoot: workspaceFsCapability === "strong" ? workspaceRoot : void 0
      })
    ]
  });
  await app.register(workspaceServer.uiRoutes, {
    getWorkspaceId: async (request) => (await workspaceFromRequest(request)).id,
    getBridge: async (request) => getBridge((await workspaceFromRequest(request)).id)
  });
  app.get("/api/v1/runtime-plugin-diagnostics", async (request) => {
    const workspace = await workspaceFromRequest(request);
    const runtime = await getLoadedPluginRuntime(workspace);
    return buildRuntimePluginDiagnosticsResponse({
      workspaceId: workspace.id,
      loaded: runtime.manager.inspectLoaded(),
      errors: runtime.manager.getErrors(),
      host: diagnosticsStore.snapshot(workspace.id),
      frontErrors: diagnosticsStore.frontErrors(workspace.id)
    });
  });
  app.post("/api/v1/agent-plugins/:id/front-error", async (request, reply) => {
    const workspace = await workspaceFromRequest(request);
    const { id } = request.params;
    const report = parseFrontErrorReport(id, request.body);
    if (!report) return reply.code(400).send({ error: "invalid_front_error_report" });
    diagnosticsStore.recordFrontError(workspace.id, report);
    return reply.code(204).send();
  });
  app.get("/api/v1/agent-plugins", async (request) => {
    const workspace = await workspaceFromRequest(request);
    const runtime = await getLoadedPluginRuntime(workspace);
    return runtime.manager.list();
  });
  app.get("/api/v1/agent-plugins/:id/error", async (request, reply) => {
    const workspace = await workspaceFromRequest(request);
    const runtime = await getLoadedPluginRuntime(workspace);
    const { id } = request.params;
    const error = runtime.manager.getError(id);
    if (error == null) return reply.code(404).send({ error: "not_found" });
    return reply.type("text/plain").send(error);
  });
  app.get("/api/v1/agent-plugins/events", async (request, reply) => {
    const workspace = await workspaceFromRequest(request);
    const runtime = await getLoadedPluginRuntime(workspace);
    const manager = runtime.manager;
    reply.hijack();
    const res = reply.raw;
    res.statusCode = 200;
    res.setHeader("Content-Type", "text/event-stream");
    res.setHeader("Cache-Control", "no-cache, no-transform");
    res.setHeader("Connection", "keep-alive");
    res.setHeader("X-Accel-Buffering", "no");
    res.flushHeaders?.();
    const write = (eventName, payload) => {
      try {
        res.write(`event: ${eventName}
`);
        res.write(`data: ${JSON.stringify(payload)}

`);
      } catch {
      }
    };
    const liveQueue = [];
    let replaying = true;
    const unsubscribe = manager.subscribe((event) => {
      if (event.type === "boring.plugin.unload" || event.type === "boring.plugin.load" && !event.frontTarget) {
        runtimeHost.untrackPlugin(workspace.id, event.id);
      }
      const payload = {
        ...event,
        workspaceId: workspace.id,
        replay: false
      };
      if (replaying) {
        liveQueue.push({ eventName: event.type, payload });
        return;
      }
      write(event.type, payload);
    });
    for (const plugin of manager.listExternal()) {
      write("boring.plugin.load", {
        type: "boring.plugin.load",
        id: plugin.id,
        boring: plugin.boring,
        version: plugin.version,
        revision: plugin.revision,
        ...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {},
        workspaceId: workspace.id,
        replay: true
      });
    }
    write("boring.plugin.replay-complete", {
      type: "boring.plugin.replay-complete",
      workspaceId: workspace.id,
      replay: true
    });
    replaying = false;
    for (const event of liveQueue) write(event.eventName, event.payload);
    const heartbeat = setInterval(() => {
      try {
        res.write(": heartbeat\n\n");
      } catch {
      }
    }, 25e3);
    const closeStream = () => {
      clearInterval(heartbeat);
      unsubscribe();
      workspaceEventClosers.get(workspace.id)?.delete(closeStream);
      try {
        res.end();
      } catch {
      }
    };
    const closers = workspaceEventClosers.get(workspace.id) ?? /* @__PURE__ */ new Set();
    closers.add(closeStream);
    workspaceEventClosers.set(workspace.id, closers);
    request.raw.on("close", closeStream);
  });
  app.get("/api/v1/workspace/meta", async () => ({
    projectName: "Boring UI",
    workspacesMode: true,
    version: CLI_VERSION,
    runtimePluginFrontLoadingEnabled: true,
    runtimePluginTrustLabel: RUNTIME_PLUGIN_TRUST_LABEL,
    runtimePluginTrustDescription: RUNTIME_PLUGIN_TRUST_DESCRIPTION,
    runtimePluginDiagnosticsEnabled: true
  }));
  void (async () => {
    await new Promise((resolve2) => setTimeout(resolve2, 250));
    try {
      const byRecency = [...await registry.list()].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
      for (const workspace of byRecency) {
        if (!workspace.available) continue;
        try {
          await getLoadedPluginRuntime(workspace);
        } catch (error) {
          app.log.warn({ err: error, workspaceId: workspace.id }, "[cli] workspace plugin prewarm failed");
        }
      }
    } catch (error) {
      app.log.warn({ err: error }, "[cli] workspace plugin prewarm skipped");
    }
  })();
  return app;
}
export {
  CLI_VERSION,
  MODE_MAP,
  createBoringUiCliRuntimePlugin,
  createFolderModeApp,
  createWorkspacesModeApp,
  provisionCliWorkspaceRuntime,
  resolveBoringUiPluginCliPackageRoot
};
