@hachej/boring-ui-cli 0.1.36 → 0.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/server/cli.js +19 -580
  2. package/dist/server/modeApps.js +642 -0
  3. package/dist/server/pluginDiscovery.js +25 -4
  4. package/dist/server/pluginFrontRuntime.js +67 -17
  5. package/package.json +6 -5
  6. package/public/assets/{DebugDrawer-DC7YYrof.js → DebugDrawer-DnJihn7d.js} +1 -1
  7. package/public/assets/{_baseUniq-DWcImnmH.js → _baseUniq-Css5uykF.js} +1 -1
  8. package/public/assets/{arc-DDLmX1AG.js → arc-CnfZXaWe.js} +1 -1
  9. package/public/assets/{architectureDiagram-Q4EWVU46-B38ghbjL.js → architectureDiagram-Q4EWVU46-BANSd98t.js} +1 -1
  10. package/public/assets/{blockDiagram-DXYQGD6D-Bp07F26G.js → blockDiagram-DXYQGD6D-Cd6iEbZF.js} +1 -1
  11. package/public/assets/{c4Diagram-AHTNJAMY-Dcu6Qx6c.js → c4Diagram-AHTNJAMY-9WJupXFB.js} +1 -1
  12. package/public/assets/channel-Cn1SBioe.js +1 -0
  13. package/public/assets/{chunk-4BX2VUAB-DC5-ge26.js → chunk-4BX2VUAB-DEE8UItM.js} +1 -1
  14. package/public/assets/{chunk-4TB4RGXK-WI3HSrJ9.js → chunk-4TB4RGXK-Bf4S3aFF.js} +1 -1
  15. package/public/assets/{chunk-55IACEB6-DphRtKl9.js → chunk-55IACEB6-rumvIfCQ.js} +1 -1
  16. package/public/assets/{chunk-EDXVE4YY-VsnZ7w9d.js → chunk-EDXVE4YY-B41LsR8o.js} +1 -1
  17. package/public/assets/{chunk-FMBD7UC4-DU-cVjgu.js → chunk-FMBD7UC4-Cq0MvVHl.js} +1 -1
  18. package/public/assets/{chunk-OYMX7WX6-B1l0Dpt4.js → chunk-OYMX7WX6-AEfKb-L7.js} +1 -1
  19. package/public/assets/{chunk-QZHKN3VN-38IXLFlx.js → chunk-QZHKN3VN-BnF9Mgjw.js} +1 -1
  20. package/public/assets/{chunk-YZCP3GAM-eY6KeIEv.js → chunk-YZCP3GAM-CVhtG4Yd.js} +1 -1
  21. package/public/assets/classDiagram-6PBFFD2Q-B7iAGzAt.js +1 -0
  22. package/public/assets/classDiagram-v2-HSJHXN6E-B7iAGzAt.js +1 -0
  23. package/public/assets/clone-CKRPa4Cx.js +1 -0
  24. package/public/assets/{cose-bilkent-S5V4N54A-YEoqfW_D.js → cose-bilkent-S5V4N54A-BAlza9SS.js} +1 -1
  25. package/public/assets/{dagre-KV5264BT-B45m1NxI.js → dagre-KV5264BT-DXC-wg6w.js} +1 -1
  26. package/public/assets/{diagram-5BDNPKRD-B5ZXQlWh.js → diagram-5BDNPKRD-DQ_JZ2v6.js} +1 -1
  27. package/public/assets/{diagram-G4DWMVQ6-C1ynjN78.js → diagram-G4DWMVQ6-BV1_oXdH.js} +1 -1
  28. package/public/assets/{diagram-MMDJMWI5-D4hAUvca.js → diagram-MMDJMWI5-CBT9iuoD.js} +1 -1
  29. package/public/assets/{diagram-TYMM5635-B3agZWj-.js → diagram-TYMM5635-2gLQa-5c.js} +1 -1
  30. package/public/assets/{erDiagram-SMLLAGMA-CH2AxsAT.js → erDiagram-SMLLAGMA-BlACgWIF.js} +1 -1
  31. package/public/assets/{flowDiagram-DWJPFMVM-DDgdNECT.js → flowDiagram-DWJPFMVM-C3a4Cakw.js} +1 -1
  32. package/public/assets/{ganttDiagram-T4ZO3ILL-CzvQW97t.js → ganttDiagram-T4ZO3ILL-BvO6eI9n.js} +1 -1
  33. package/public/assets/{gitGraphDiagram-UUTBAWPF-DcQZPPMD.js → gitGraphDiagram-UUTBAWPF-CNRpcuUc.js} +1 -1
  34. package/public/assets/{graph-CD4bwX_-.js → graph-BGcM9Vra.js} +1 -1
  35. package/public/assets/{highlighted-body-OFNGDK62-CiM51uH6.js → highlighted-body-OFNGDK62-DFAlA8ty.js} +1 -1
  36. package/public/assets/{index-BXEu5C8e.js → index-DuQz_ooq.js} +471 -466
  37. package/public/assets/index-o6MvaI3V.css +1 -0
  38. package/public/assets/{infoDiagram-42DDH7IO-nwmufYgw.js → infoDiagram-42DDH7IO-tqWZWhv9.js} +1 -1
  39. package/public/assets/{ishikawaDiagram-UXIWVN3A-CAAuAFim.js → ishikawaDiagram-UXIWVN3A-BSrhE5rT.js} +1 -1
  40. package/public/assets/{journeyDiagram-VCZTEJTY-jPt0NUc6.js → journeyDiagram-VCZTEJTY-p4zWU15s.js} +1 -1
  41. package/public/assets/{kanban-definition-6JOO6SKY-BkWE1oIv.js → kanban-definition-6JOO6SKY-Bh1g0QjZ.js} +1 -1
  42. package/public/assets/{layout-DVywqgnM.js → layout-gyEZgedN.js} +1 -1
  43. package/public/assets/{linear-D5rTUKEy.js → linear-D_hzHorE.js} +1 -1
  44. package/public/assets/{min-CGxxs3H9.js → min-DuaweV_G.js} +1 -1
  45. package/public/assets/{mindmap-definition-QFDTVHPH-C5Bv0ezi.js → mindmap-definition-QFDTVHPH-Bd9cPaCZ.js} +1 -1
  46. package/public/assets/{pieDiagram-DEJITSTG-C4KdtVZ8.js → pieDiagram-DEJITSTG-BtC28nvk.js} +1 -1
  47. package/public/assets/{quadrantDiagram-34T5L4WZ-Cz9vL8Q6.js → quadrantDiagram-34T5L4WZ-D4l6_MzD.js} +1 -1
  48. package/public/assets/{requirementDiagram-MS252O5E-B80rbkJK.js → requirementDiagram-MS252O5E-C1iUHr18.js} +1 -1
  49. package/public/assets/{sankeyDiagram-XADWPNL6-Cn-nZLaG.js → sankeyDiagram-XADWPNL6-OVDB_8ZR.js} +1 -1
  50. package/public/assets/{sequenceDiagram-FGHM5R23-A6IunKM0.js → sequenceDiagram-FGHM5R23-BDavACVC.js} +1 -1
  51. package/public/assets/{stateDiagram-FHFEXIEX-BR6MAf9n.js → stateDiagram-FHFEXIEX-q8RwQFTP.js} +1 -1
  52. package/public/assets/stateDiagram-v2-QKLJ7IA2-BoiN-2KM.js +1 -0
  53. package/public/assets/{timeline-definition-GMOUNBTQ-4cDx4FT9.js → timeline-definition-GMOUNBTQ-y0_Knxi9.js} +1 -1
  54. package/public/assets/{vennDiagram-DHZGUBPP-_CpmLhQd.js → vennDiagram-DHZGUBPP-DbnSIyoN.js} +1 -1
  55. package/public/assets/{wardley-RL74JXVD-CSa4wbmw.js → wardley-RL74JXVD-CIj9kuqP.js} +1 -1
  56. package/public/assets/{wardleyDiagram-NUSXRM2D-DY8-GDoy.js → wardleyDiagram-NUSXRM2D--fILgFoZ.js} +1 -1
  57. package/public/assets/{xychartDiagram-5P7HB3ND-Dbdvqxtm.js → xychartDiagram-5P7HB3ND-YFiXEsiS.js} +1 -1
  58. package/public/index.html +2 -2
  59. package/public/assets/channel-DqOmgrq9.js +0 -1
  60. package/public/assets/classDiagram-6PBFFD2Q-Fjgbctha.js +0 -1
  61. package/public/assets/classDiagram-v2-HSJHXN6E-Fjgbctha.js +0 -1
  62. package/public/assets/clone-CpoJL_n1.js +0 -1
  63. package/public/assets/index-hl1JXpgN.css +0 -1
  64. package/public/assets/stateDiagram-v2-QKLJ7IA2-75t4kRUE.js +0 -1
@@ -0,0 +1,642 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import { basename, isAbsolute, join, resolve } from "node:path";
5
+ import { createLocalWorkspaceRegistry } from "./localWorkspaces.js";
6
+ import { resolveBoringUiCliPackageRoot } from "./pluginDiscovery.js";
7
+ const MODE_MAP = {
8
+ "local": "direct",
9
+ // no sandbox, full network access
10
+ "local-sandbox": "local"
11
+ // bwrap isolated, no network (Linux only)
12
+ };
13
+ const require2 = createRequire(import.meta.url);
14
+ const PLUGIN_CLI_PACKAGE_NAME = "@hachej/boring-ui-plugin-cli";
15
+ const CLI_VERSION = (() => {
16
+ try {
17
+ const pkg = require2("../../package.json");
18
+ return pkg.version ?? "0.0.0";
19
+ } catch {
20
+ return "0.0.0";
21
+ }
22
+ })();
23
+ function httpError(message, statusCode) {
24
+ const error = new Error(message);
25
+ error.statusCode = statusCode;
26
+ return error;
27
+ }
28
+ function firstString(value) {
29
+ if (typeof value === "string") return value;
30
+ if (!Array.isArray(value)) return void 0;
31
+ return value.find((item) => typeof item === "string");
32
+ }
33
+ function resolveWorkspaceIdFromRequest(request) {
34
+ const headers = request.headers ?? {};
35
+ const headerValue = headers["x-boring-workspace-id"] ?? Object.entries(headers).find(([key]) => key.toLowerCase() === "x-boring-workspace-id")?.[1];
36
+ const query = request.query;
37
+ const raw = firstString(headerValue) ?? firstString(query?.workspaceId) ?? "";
38
+ const workspaceId = raw.trim();
39
+ if (!workspaceId) throw httpError("workspace id is required", 400);
40
+ if (workspaceId.includes("\0") || workspaceId.includes("/") || workspaceId.includes("\\") || workspaceId.includes("..") || isAbsolute(workspaceId)) {
41
+ throw httpError("invalid workspace id", 400);
42
+ }
43
+ return workspaceId;
44
+ }
45
+ function toCoreWorkspace(workspace) {
46
+ return {
47
+ id: workspace.id,
48
+ name: workspace.name,
49
+ slug: workspace.id,
50
+ isDefault: false,
51
+ createdAt: workspace.createdAt,
52
+ updatedAt: workspace.updatedAt,
53
+ unavailable: !workspace.available,
54
+ path: workspace.path
55
+ };
56
+ }
57
+ function isUsableBoringUiPluginCliPackageRoot(candidate) {
58
+ try {
59
+ const pkg = JSON.parse(readFileSync(join(candidate, "package.json"), "utf8"));
60
+ return pkg.name === PLUGIN_CLI_PACKAGE_NAME && existsSync(join(candidate, "dist", "bin.js"));
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+ function resolveBoringUiPluginCliPackageRoot() {
66
+ const cliRoot = resolveBoringUiCliPackageRoot();
67
+ const candidate = resolve(cliRoot, "..", "plugin-cli");
68
+ return isUsableBoringUiPluginCliPackageRoot(candidate) ? candidate : null;
69
+ }
70
+ function createBoringUiCliRuntimePlugin() {
71
+ const useLocal = process.env.BORING_USE_LOCAL_PACKAGES === "1";
72
+ const packageRoot = useLocal ? resolveBoringUiPluginCliPackageRoot() : null;
73
+ return {
74
+ id: "boring-ui-plugin-cli-runtime",
75
+ provisioning: {
76
+ nodePackages: [{
77
+ id: "boring-ui-plugin-cli",
78
+ packageName: PLUGIN_CLI_PACKAGE_NAME,
79
+ ...packageRoot ? { packageRoot } : { version: CLI_VERSION },
80
+ expectedBins: ["boring-ui-plugin"]
81
+ }]
82
+ }
83
+ };
84
+ }
85
+ async function provisionCliWorkspaceRuntime(opts) {
86
+ if (opts.provisionWorkspace === false) return void 0;
87
+ const agent = await import("@hachej/boring-agent/server");
88
+ const runtimeLayout = opts.runtimeLayout ?? agent.getBoringAgentRuntimePaths(opts.workspaceRoot);
89
+ const adapter = opts.adapter ?? opts.modeAdapter?.createProvisioningAdapter?.(runtimeLayout) ?? agent.resolveMode(opts.mode).createProvisioningAdapter?.(runtimeLayout);
90
+ if (!adapter) {
91
+ throw new Error(`runtime mode ${opts.mode} does not support workspace provisioning`);
92
+ }
93
+ const result = await agent.provisionWorkspaceRuntime({
94
+ plugins: [createBoringUiCliRuntimePlugin(), ...opts.plugins ?? []],
95
+ adapter,
96
+ runtimeLayout
97
+ });
98
+ return {
99
+ ...result,
100
+ env: {
101
+ ...result.env,
102
+ BORING_AGENT_WORKSPACE_LOCAL_PLUGIN_ROOTS: opts.mode === "direct" || opts.mode === "local" ? "1" : "0"
103
+ }
104
+ };
105
+ }
106
+ const FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID = "folder";
107
+ const RUNTIME_PLUGIN_TRUST_LABEL = "Trusted local runtime plugins";
108
+ const RUNTIME_PLUGIN_TRUST_DESCRIPTION = "Loads plugin UI code from trusted local Pi extension roots through the CLI-owned runtime module host.";
109
+ function createRuntimePluginDiagnosticsStore() {
110
+ const byWorkspace = /* @__PURE__ */ new Map();
111
+ function upsert(workspaceId, pluginId) {
112
+ const workspace = byWorkspace.get(workspaceId) ?? /* @__PURE__ */ new Map();
113
+ byWorkspace.set(workspaceId, workspace);
114
+ const existing = workspace.get(pluginId) ?? {
115
+ workspaceId,
116
+ pluginId,
117
+ recent: []
118
+ };
119
+ workspace.set(pluginId, existing);
120
+ return existing;
121
+ }
122
+ return {
123
+ record(diagnostic) {
124
+ if (!diagnostic.workspaceId || !diagnostic.pluginId) return;
125
+ const entry = upsert(diagnostic.workspaceId, diagnostic.pluginId);
126
+ const now = Date.now();
127
+ entry.lastDiagnostic = diagnostic;
128
+ entry.recent = [...entry.recent, diagnostic].slice(-12);
129
+ if (diagnostic.revision !== void 0) entry.revision = diagnostic.revision;
130
+ if (diagnostic.requestedPath) entry.lastRequestedPath = diagnostic.requestedPath;
131
+ if (diagnostic.resolvedPath) entry.lastResolvedPath = diagnostic.resolvedPath;
132
+ const details = diagnostic.details ?? {};
133
+ if (typeof details.rootDir === "string") entry.rootDir = details.rootDir;
134
+ if (typeof details.entryUrl === "string") entry.entryUrl = details.entryUrl;
135
+ if (typeof diagnostic.requestedPath === "string") entry.frontEntrySubpath = diagnostic.requestedPath;
136
+ if (diagnostic.stage === "track" && diagnostic.outcome === "tracked") {
137
+ entry.lastErrorCode = void 0;
138
+ entry.lastErrorMessage = void 0;
139
+ entry.lastErrorStage = void 0;
140
+ }
141
+ if (diagnostic.stage === "cache") entry.lastRequestAt = now;
142
+ if (diagnostic.stage === "transform" && diagnostic.outcome === "served") {
143
+ entry.lastTransformAt = now;
144
+ entry.lastTransformDurationMs = diagnostic.durationMs;
145
+ }
146
+ if (diagnostic.stage === "serve" && diagnostic.outcome === "served") {
147
+ entry.lastServeAt = now;
148
+ entry.lastServeDurationMs = diagnostic.durationMs;
149
+ entry.lastErrorCode = void 0;
150
+ entry.lastErrorMessage = void 0;
151
+ entry.lastErrorStage = void 0;
152
+ }
153
+ if (diagnostic.outcome === "rejected") {
154
+ entry.lastRejectedAt = now;
155
+ entry.lastErrorCode = diagnostic.code;
156
+ entry.lastErrorMessage = diagnostic.msg;
157
+ entry.lastErrorStage = diagnostic.stage;
158
+ }
159
+ if (diagnostic.stage === "cleanup" && diagnostic.outcome === "disposed") {
160
+ entry.lastDisposedAt = now;
161
+ }
162
+ },
163
+ snapshot(workspaceId) {
164
+ return [...byWorkspace.get(workspaceId)?.values() ?? []].map((entry) => ({ ...entry, recent: [...entry.recent] })).sort((a, b) => a.pluginId.localeCompare(b.pluginId));
165
+ },
166
+ disposeWorkspace(workspaceId) {
167
+ byWorkspace.delete(workspaceId);
168
+ }
169
+ };
170
+ }
171
+ function syncRuntimeHostFromPluginEvents(runtimeHost, workspaceId, events) {
172
+ for (const event of events) {
173
+ if (event.type === "boring.plugin.unload" || event.type === "boring.plugin.load" && !event.frontTarget) {
174
+ runtimeHost.untrackPlugin(workspaceId, event.id);
175
+ }
176
+ }
177
+ }
178
+ function buildRuntimePluginDiagnosticsResponse(args) {
179
+ const byPlugin = /* @__PURE__ */ new Map();
180
+ for (const plugin of args.loaded) {
181
+ byPlugin.set(plugin.id, {
182
+ id: plugin.id,
183
+ ...plugin.version ? { version: plugin.version } : {},
184
+ ...plugin.rootDir ? { rootDir: plugin.rootDir } : {},
185
+ ...plugin.frontPath ? { frontPath: plugin.frontPath } : {},
186
+ ...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {},
187
+ ...plugin.revision !== void 0 ? { serverLoadedRevision: plugin.revision } : {}
188
+ });
189
+ }
190
+ for (const error of args.errors) {
191
+ const current = byPlugin.get(error.id) ?? { id: error.id };
192
+ byPlugin.set(error.id, {
193
+ ...current,
194
+ serverError: error.message
195
+ });
196
+ }
197
+ for (const hostEntry of args.host) {
198
+ const current = byPlugin.get(hostEntry.pluginId) ?? { id: hostEntry.pluginId };
199
+ byPlugin.set(hostEntry.pluginId, {
200
+ ...current,
201
+ ...current.rootDir ? {} : hostEntry.rootDir ? { rootDir: hostEntry.rootDir } : {},
202
+ ...current.frontPath ? {} : hostEntry.frontEntrySubpath ? { frontPath: hostEntry.frontEntrySubpath } : {},
203
+ ...current.frontTarget ? {} : hostEntry.entryUrl ? { frontTarget: { kind: "native", entryUrl: hostEntry.entryUrl, revision: hostEntry.revision ?? 0, trust: "local-trusted-native" } } : {},
204
+ host: hostEntry
205
+ });
206
+ }
207
+ return {
208
+ workspaceId: args.workspaceId,
209
+ plugins: [...byPlugin.values()].sort((a, b) => a.id.localeCompare(b.id))
210
+ };
211
+ }
212
+ async function createFolderModeApp(opts) {
213
+ const workspaceRoot = resolve(opts.workspaceRoot);
214
+ const projectName = opts.projectName ?? (basename(workspaceRoot) || "workspace");
215
+ const [{ createWorkspaceAgentServer, readWorkspacePluginPackageRuntimePlugins }, { createPluginFrontRuntimeHost }, pluginDiscovery] = await Promise.all([
216
+ import("@hachej/boring-workspace/app/server"),
217
+ import("./pluginFrontRuntime.js"),
218
+ import("./pluginDiscovery.js")
219
+ ]);
220
+ const diagnosticsStore = createRuntimePluginDiagnosticsStore();
221
+ const runtimeHost = await createPluginFrontRuntimeHost({
222
+ onDiagnostic: (diagnostic) => diagnosticsStore.record(diagnostic)
223
+ });
224
+ const pluginDirs = pluginDiscovery.resolveCliBoringPluginDirs(workspaceRoot);
225
+ const runtimeProvisioning = await provisionCliWorkspaceRuntime({
226
+ workspaceRoot,
227
+ mode: opts.mode,
228
+ provisionWorkspace: opts.provisionWorkspace,
229
+ plugins: readWorkspacePluginPackageRuntimePlugins(pluginDirs)
230
+ });
231
+ const app = await createWorkspaceAgentServer({
232
+ workspaceRoot,
233
+ mode: opts.mode,
234
+ logger: false,
235
+ provisionWorkspace: false,
236
+ runtimeProvisioning,
237
+ // CLI-bundled internal plugins, resolved to absolute package dirs. This
238
+ // drives the server-side install array (boot-time routes/agentTools);
239
+ // additionalBoringPluginDirs only feeds the asset-manager scan.
240
+ defaultPluginPackages: pluginDiscovery.resolveCliDefaultPluginPackagePaths(),
241
+ additionalBoringPluginDirs: pluginDirs,
242
+ boringPluginFrontTargetResolver: runtimeHost.createFrontTargetResolver(FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID)
243
+ });
244
+ await runtimeHost.registerRoutes(app);
245
+ const folderAssetManager = app.__boringAssetManager;
246
+ const closeFolderRuntimeCleanup = folderAssetManager?.subscribe((event) => {
247
+ syncRuntimeHostFromPluginEvents(runtimeHost, FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID, [event]);
248
+ });
249
+ if (closeFolderRuntimeCleanup) {
250
+ app.addHook("onClose", async () => {
251
+ closeFolderRuntimeCleanup();
252
+ });
253
+ }
254
+ app.get("/api/v1/runtime-plugin-diagnostics", async () => {
255
+ const manager = app.__boringAssetManager;
256
+ return buildRuntimePluginDiagnosticsResponse({
257
+ workspaceId: FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID,
258
+ loaded: manager?.inspectLoaded() ?? [],
259
+ errors: manager?.getErrors() ?? [],
260
+ host: diagnosticsStore.snapshot(FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID)
261
+ });
262
+ });
263
+ app.get("/api/v1/workspace/meta", async () => ({
264
+ workspaceId: "default",
265
+ workspaceRoot,
266
+ projectName,
267
+ version: CLI_VERSION,
268
+ runtimePluginFrontLoadingEnabled: true,
269
+ runtimePluginTrustLabel: RUNTIME_PLUGIN_TRUST_LABEL,
270
+ runtimePluginTrustDescription: RUNTIME_PLUGIN_TRUST_DESCRIPTION,
271
+ runtimePluginDiagnosticsEnabled: true
272
+ }));
273
+ return app;
274
+ }
275
+ async function createWorkspacesModeApp(opts) {
276
+ const [workspaceAppServer, workspaceServer, agentServer, agentShared, fastifyModule, { createPluginFrontRuntimeHost }, pluginDiscovery] = await Promise.all([
277
+ import("@hachej/boring-workspace/app/server"),
278
+ import("@hachej/boring-workspace/server"),
279
+ import("@hachej/boring-agent/server"),
280
+ import("@hachej/boring-agent/shared"),
281
+ import("fastify"),
282
+ import("./pluginFrontRuntime.js"),
283
+ import("./pluginDiscovery.js")
284
+ ]);
285
+ const registry = createLocalWorkspaceRegistry(opts.registryPath);
286
+ const app = fastifyModule.default({ logger: false, bodyLimit: 16 * 1024 * 1024 });
287
+ const diagnosticsStore = createRuntimePluginDiagnosticsStore();
288
+ const runtimeHost = await createPluginFrontRuntimeHost({
289
+ onDiagnostic: (diagnostic) => diagnosticsStore.record(diagnostic)
290
+ });
291
+ await runtimeHost.registerRoutes(app);
292
+ await app.register(workspaceServer.runtimeBackendGateway, {
293
+ registry: {
294
+ dispatch: async (dispatchRequest) => {
295
+ if (!dispatchRequest.workspaceId) {
296
+ throw new workspaceServer.RuntimeBackendError(
297
+ agentShared.ErrorCode.enum.RUNTIME_PLUGIN_NOT_FOUND,
298
+ 404,
299
+ "runtime backend dispatch requires a workspace id (x-boring-workspace-id header)"
300
+ );
301
+ }
302
+ const workspace = await registry.get(dispatchRequest.workspaceId);
303
+ if (!workspace?.available) {
304
+ throw new workspaceServer.RuntimeBackendError(
305
+ agentShared.ErrorCode.enum.RUNTIME_PLUGIN_NOT_FOUND,
306
+ 404,
307
+ `runtime backend dispatch: unknown workspace ${dispatchRequest.workspaceId}`
308
+ );
309
+ }
310
+ const runtime = await getLoadedPluginRuntime(workspace);
311
+ return runtime.backendRegistry.dispatch({ ...dispatchRequest, workspaceId: resolve(workspace.path) });
312
+ }
313
+ }
314
+ });
315
+ const bridges = /* @__PURE__ */ new Map();
316
+ const workspaceEventClosers = /* @__PURE__ */ new Map();
317
+ const pluginRuntimes = /* @__PURE__ */ new Map();
318
+ const pluginPiSnapshots = /* @__PURE__ */ new Map();
319
+ const runtimeProvisioningByWorkspace = /* @__PURE__ */ new Map();
320
+ function getBridge(workspaceId) {
321
+ let bridge = bridges.get(workspaceId);
322
+ if (!bridge) {
323
+ bridge = workspaceServer.createInMemoryBridge();
324
+ bridges.set(workspaceId, bridge);
325
+ }
326
+ return bridge;
327
+ }
328
+ async function requireWorkspace(workspaceId) {
329
+ const workspace = await registry.get(workspaceId);
330
+ if (!workspace) throw httpError("unknown workspace", 404);
331
+ if (!workspace.available) throw httpError("workspace folder unavailable", 409);
332
+ return workspace;
333
+ }
334
+ async function workspaceFromRequest(request) {
335
+ return await requireWorkspace(resolveWorkspaceIdFromRequest(request));
336
+ }
337
+ function pluginRuntimeKey(workspace) {
338
+ return `${workspace.id}:${workspace.path}`;
339
+ }
340
+ function syncLoadedPluginPiSnapshot(workspace, manager) {
341
+ pluginPiSnapshots.set(pluginRuntimeKey(workspace), manager.inspectLoadedPiSnapshot());
342
+ }
343
+ function getOrCreatePluginRuntime(workspace) {
344
+ const key = pluginRuntimeKey(workspace);
345
+ let runtime = pluginRuntimes.get(key);
346
+ if (!runtime) {
347
+ const manager = pluginDiscovery.createCliPluginAssetManager(workspace.path, {
348
+ frontTargetResolver: runtimeHost.createFrontTargetResolver(workspace.id)
349
+ });
350
+ const backendRegistry = new workspaceServer.RuntimeBackendRegistry();
351
+ runtime = {
352
+ manager,
353
+ backendRegistry,
354
+ ensureLoaded: manager.load().then(async () => {
355
+ syncLoadedPluginPiSnapshot(workspace, manager);
356
+ await backendRegistry.reloadFromLoadedPlugins(manager.inspectLoaded());
357
+ })
358
+ };
359
+ pluginRuntimes.set(key, runtime);
360
+ }
361
+ return runtime;
362
+ }
363
+ async function getLoadedPluginRuntime(workspace) {
364
+ const runtime = getOrCreatePluginRuntime(workspace);
365
+ await runtime.ensureLoaded;
366
+ return runtime;
367
+ }
368
+ function getLoadedPluginPiSnapshot(workspace) {
369
+ return pluginPiSnapshots.get(pluginRuntimeKey(workspace)) ?? {
370
+ additionalSkillPaths: [],
371
+ packages: [],
372
+ extensionPaths: []
373
+ };
374
+ }
375
+ async function disposeWorkspaceRuntime(workspace) {
376
+ for (const close of workspaceEventClosers.get(workspace.id) ?? []) {
377
+ try {
378
+ close();
379
+ } catch {
380
+ }
381
+ }
382
+ workspaceEventClosers.delete(workspace.id);
383
+ const runtimeKey = pluginRuntimeKey(workspace);
384
+ const runtime = pluginRuntimes.get(runtimeKey);
385
+ if (runtime) {
386
+ try {
387
+ await runtime.backendRegistry.close();
388
+ } catch {
389
+ }
390
+ }
391
+ pluginRuntimes.delete(runtimeKey);
392
+ pluginPiSnapshots.delete(runtimeKey);
393
+ runtimeProvisioningByWorkspace.delete(workspace.id);
394
+ bridges.delete(workspace.id);
395
+ diagnosticsStore.disposeWorkspace(workspace.id);
396
+ await runtimeHost.disposeWorkspace(workspace.id);
397
+ }
398
+ function reloadDiagnostics(scan) {
399
+ return scan.errors.map((error) => ({
400
+ source: "workspaces-plugin-manager",
401
+ message: error.message,
402
+ pluginId: error.id
403
+ }));
404
+ }
405
+ app.get("/api/v1/local-workspaces", async () => ({
406
+ workspaces: await registry.list()
407
+ }));
408
+ app.post("/api/v1/local-workspaces", async (request, reply) => {
409
+ const body = request.body;
410
+ if (typeof body?.path !== "string" || !body.path.trim()) {
411
+ return reply.code(400).send({ error: "workspace path is required" });
412
+ }
413
+ try {
414
+ const workspace = await registry.add(body.path, {
415
+ name: typeof body.name === "string" ? body.name : void 0
416
+ });
417
+ return { workspace };
418
+ } catch (error) {
419
+ return reply.code(400).send({
420
+ error: error instanceof Error ? error.message : "unable to add workspace"
421
+ });
422
+ }
423
+ });
424
+ app.delete("/api/v1/local-workspaces/:id", async (request, reply) => {
425
+ const { id } = request.params;
426
+ const workspace = await registry.get(id);
427
+ await registry.remove(id);
428
+ if (workspace) await disposeWorkspaceRuntime(workspace);
429
+ return reply.send({ ok: true });
430
+ });
431
+ app.get("/api/v1/workspaces", async () => ({
432
+ workspaces: (await registry.list()).map(toCoreWorkspace)
433
+ }));
434
+ app.get("/api/v1/workspaces/:id", async (request, reply) => {
435
+ const { id } = request.params;
436
+ const workspace = await registry.get(id);
437
+ if (!workspace) return reply.code(404).send({ error: "workspace not found" });
438
+ return { workspace: toCoreWorkspace(workspace), role: "owner" };
439
+ });
440
+ await app.register(agentServer.registerAgentRoutes, {
441
+ mode: opts.mode,
442
+ systemPromptAppend: workspaceAppServer.buildWorkspaceContextPrompt(),
443
+ getSystemPromptDynamic: async ({ workspaceId }) => {
444
+ const workspace = await requireWorkspace(workspaceId);
445
+ await getLoadedPluginRuntime(workspace);
446
+ return getLoadedPluginPiSnapshot(workspace).systemPromptAppend;
447
+ },
448
+ getWorkspaceId: async (request) => (await workspaceFromRequest(request)).id,
449
+ getWorkspaceRoot: async (workspaceId) => (await requireWorkspace(workspaceId)).path,
450
+ getSessionNamespace: async ({ workspaceId }) => `local-workspace-${workspaceId}`,
451
+ provisionRuntime: async ({ workspaceId, workspaceRoot, runtimeMode, runtimeLayout, provisioningAdapter }) => {
452
+ if (runtimeProvisioningByWorkspace.has(workspaceId)) {
453
+ return runtimeProvisioningByWorkspace.get(workspaceId);
454
+ }
455
+ const provisioned = await provisionCliWorkspaceRuntime({
456
+ workspaceRoot,
457
+ mode: runtimeMode,
458
+ provisionWorkspace: opts.provisionWorkspace,
459
+ adapter: provisioningAdapter,
460
+ runtimeLayout,
461
+ plugins: workspaceAppServer.readWorkspacePluginPackageRuntimePlugins(pluginDiscovery.resolveCliBoringPluginDirs(workspaceRoot))
462
+ });
463
+ runtimeProvisioningByWorkspace.set(workspaceId, provisioned);
464
+ return provisioned;
465
+ },
466
+ beforeReload: async ({ workspaceId }) => {
467
+ const workspace = await requireWorkspace(workspaceId);
468
+ const runtime = await getLoadedPluginRuntime(workspace);
469
+ runtime.manager.setPluginDirs(pluginDiscovery.resolveCliBoringPluginDirs(workspace.path));
470
+ const scan = await runtime.manager.load();
471
+ syncLoadedPluginPiSnapshot(workspace, runtime.manager);
472
+ syncRuntimeHostFromPluginEvents(runtimeHost, workspaceId, scan.events);
473
+ const backendReload = await runtime.backendRegistry.reloadFromLoadedPlugins(runtime.manager.inspectLoaded());
474
+ return {
475
+ restart_warnings: workspaceServer.collectRestartWarnings(scan.events),
476
+ diagnostics: [
477
+ ...reloadDiagnostics(scan),
478
+ ...backendReload.diagnostics.map((diagnostic) => ({
479
+ source: diagnostic.source,
480
+ message: diagnostic.message,
481
+ ...diagnostic.pluginId ? { pluginId: diagnostic.pluginId } : {}
482
+ }))
483
+ ]
484
+ };
485
+ },
486
+ getPluginDiagnostics: async ({ workspaceId }) => {
487
+ const workspace = await requireWorkspace(workspaceId);
488
+ const runtime = await getLoadedPluginRuntime(workspace);
489
+ return [
490
+ ...runtime.manager.getErrors().map((error) => ({
491
+ source: "plugin-load",
492
+ message: error.message,
493
+ ...error.id ? { pluginId: error.id } : {}
494
+ })),
495
+ ...runtime.manager.preflight().errors.map((error) => ({
496
+ source: "plugin-preflight",
497
+ message: `${error.code}: ${error.message} (${error.pluginDir})`,
498
+ ...error.pluginId ? { pluginId: error.pluginId } : {}
499
+ }))
500
+ ];
501
+ },
502
+ getPi: async ({ workspaceId, workspaceRoot }) => {
503
+ const workspace = await requireWorkspace(workspaceId);
504
+ await getLoadedPluginRuntime(workspace);
505
+ return {
506
+ additionalSkillPaths: [join(workspaceRoot, ".agents", "skills")],
507
+ packages: [],
508
+ extensionPaths: [],
509
+ getHotReloadableResources: () => getLoadedPluginPiSnapshot(workspace)
510
+ };
511
+ },
512
+ getExtraTools: async ({ workspaceId, workspaceRoot, workspaceFsCapability }) => [
513
+ ...workspaceServer.createWorkspaceUiTools(getBridge(workspaceId), {
514
+ workspaceRoot: workspaceFsCapability === "strong" ? workspaceRoot : void 0
515
+ })
516
+ ]
517
+ });
518
+ await app.register(workspaceServer.uiRoutes, {
519
+ getWorkspaceId: async (request) => (await workspaceFromRequest(request)).id,
520
+ getBridge: async (request) => getBridge((await workspaceFromRequest(request)).id)
521
+ });
522
+ app.get("/api/v1/runtime-plugin-diagnostics", async (request) => {
523
+ const workspace = await workspaceFromRequest(request);
524
+ const runtime = await getLoadedPluginRuntime(workspace);
525
+ return buildRuntimePluginDiagnosticsResponse({
526
+ workspaceId: workspace.id,
527
+ loaded: runtime.manager.inspectLoaded(),
528
+ errors: runtime.manager.getErrors(),
529
+ host: diagnosticsStore.snapshot(workspace.id)
530
+ });
531
+ });
532
+ app.get("/api/v1/agent-plugins", async (request) => {
533
+ const workspace = await workspaceFromRequest(request);
534
+ const runtime = await getLoadedPluginRuntime(workspace);
535
+ return runtime.manager.list();
536
+ });
537
+ app.get("/api/v1/agent-plugins/:id/error", async (request, reply) => {
538
+ const workspace = await workspaceFromRequest(request);
539
+ const runtime = await getLoadedPluginRuntime(workspace);
540
+ const { id } = request.params;
541
+ const error = runtime.manager.getError(id);
542
+ if (error == null) return reply.code(404).send({ error: "not_found" });
543
+ return reply.type("text/plain").send(error);
544
+ });
545
+ app.get("/api/v1/agent-plugins/events", async (request, reply) => {
546
+ const workspace = await workspaceFromRequest(request);
547
+ const runtime = await getLoadedPluginRuntime(workspace);
548
+ const manager = runtime.manager;
549
+ reply.hijack();
550
+ const res = reply.raw;
551
+ res.statusCode = 200;
552
+ res.setHeader("Content-Type", "text/event-stream");
553
+ res.setHeader("Cache-Control", "no-cache, no-transform");
554
+ res.setHeader("Connection", "keep-alive");
555
+ res.setHeader("X-Accel-Buffering", "no");
556
+ res.flushHeaders?.();
557
+ const write = (eventName, payload) => {
558
+ try {
559
+ res.write(`event: ${eventName}
560
+ `);
561
+ res.write(`data: ${JSON.stringify(payload)}
562
+
563
+ `);
564
+ } catch {
565
+ }
566
+ };
567
+ const liveQueue = [];
568
+ let replaying = true;
569
+ const unsubscribe = manager.subscribe((event) => {
570
+ if (event.type === "boring.plugin.unload" || event.type === "boring.plugin.load" && !event.frontTarget) {
571
+ runtimeHost.untrackPlugin(workspace.id, event.id);
572
+ }
573
+ const payload = {
574
+ ...event,
575
+ workspaceId: workspace.id,
576
+ replay: false
577
+ };
578
+ if (replaying) {
579
+ liveQueue.push({ eventName: event.type, payload });
580
+ return;
581
+ }
582
+ write(event.type, payload);
583
+ });
584
+ for (const plugin of manager.listExternal()) {
585
+ write("boring.plugin.load", {
586
+ type: "boring.plugin.load",
587
+ id: plugin.id,
588
+ boring: plugin.boring,
589
+ version: plugin.version,
590
+ revision: plugin.revision,
591
+ ...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {},
592
+ workspaceId: workspace.id,
593
+ replay: true
594
+ });
595
+ }
596
+ write("boring.plugin.replay-complete", {
597
+ type: "boring.plugin.replay-complete",
598
+ workspaceId: workspace.id,
599
+ replay: true
600
+ });
601
+ replaying = false;
602
+ for (const event of liveQueue) write(event.eventName, event.payload);
603
+ const heartbeat = setInterval(() => {
604
+ try {
605
+ res.write(": heartbeat\n\n");
606
+ } catch {
607
+ }
608
+ }, 25e3);
609
+ const closeStream = () => {
610
+ clearInterval(heartbeat);
611
+ unsubscribe();
612
+ workspaceEventClosers.get(workspace.id)?.delete(closeStream);
613
+ try {
614
+ res.end();
615
+ } catch {
616
+ }
617
+ };
618
+ const closers = workspaceEventClosers.get(workspace.id) ?? /* @__PURE__ */ new Set();
619
+ closers.add(closeStream);
620
+ workspaceEventClosers.set(workspace.id, closers);
621
+ request.raw.on("close", closeStream);
622
+ });
623
+ app.get("/api/v1/workspace/meta", async () => ({
624
+ projectName: "Boring UI",
625
+ workspacesMode: true,
626
+ version: CLI_VERSION,
627
+ runtimePluginFrontLoadingEnabled: true,
628
+ runtimePluginTrustLabel: RUNTIME_PLUGIN_TRUST_LABEL,
629
+ runtimePluginTrustDescription: RUNTIME_PLUGIN_TRUST_DESCRIPTION,
630
+ runtimePluginDiagnosticsEnabled: true
631
+ }));
632
+ return app;
633
+ }
634
+ export {
635
+ CLI_VERSION,
636
+ MODE_MAP,
637
+ createBoringUiCliRuntimePlugin,
638
+ createFolderModeApp,
639
+ createWorkspacesModeApp,
640
+ provisionCliWorkspaceRuntime,
641
+ resolveBoringUiPluginCliPackageRoot
642
+ };