@hachej/boring-workspace 0.1.33 → 0.1.35
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.
- package/dist/{FileTree-SmsE0Raq.js → FileTree-CkCxHBDu.js} +1 -1
- package/dist/MarkdownEditor-CtPphEVP.js +549 -0
- package/dist/{WorkspaceLoadingState-BlvZXmFg.js → WorkspaceLoadingState-Bl-92Dly.js} +287 -252
- package/dist/WorkspaceProvider-BhRPFy5R.js +7508 -0
- package/dist/app-front.d.ts +50 -3
- package/dist/app-front.js +893 -756
- package/dist/app-server.d.ts +11 -8
- package/dist/app-server.js +609 -79
- package/dist/boring-workspace.css +1 -1
- package/dist/{createInMemoryBridge-HJopAIbo.d.ts → createInMemoryBridge-zb8MpO60.d.ts} +11 -1
- package/dist/plugin.d.ts +8 -0
- package/dist/plugin.js +8 -1
- package/dist/runtime-server.d.ts +47 -0
- package/dist/runtime-server.js +32 -0
- package/dist/server.d.ts +63 -25
- package/dist/server.js +555 -46
- package/dist/testing.js +1 -1
- package/dist/workspace.css +328 -30
- package/dist/workspace.d.ts +68 -2
- package/dist/workspace.js +31 -30
- package/docs/PLUGIN_SYSTEM.md +5 -6
- package/package.json +10 -4
- package/dist/MarkdownEditor-zbp8ezds.js +0 -540
- package/dist/WorkspaceProvider-CuIZx1ua.js +0 -6553
package/dist/server.js
CHANGED
|
@@ -1019,13 +1019,19 @@ function buildBoringSystemPrompt(opts) {
|
|
|
1019
1019
|
"- Imperative method names: `registerComponent`, `addPanel`, `registerCommand` (no `Panel`), `registerTab` \u2014 the actual names are `registerPanel`, `registerPanelCommand`, `registerLeftTab`, `registerSurfaceResolver` (and you usually express these declaratively, not as method calls).",
|
|
1020
1020
|
"- Import paths: `@hachej/boring-pi` (it's a skills package, not for code), `@boring-ui/*`, `@hachej/pi-sdk` \u2014 use `@hachej/boring-workspace/plugin` for front and `@hachej/boring-workspace/server` for server.",
|
|
1021
1021
|
'- File visualizers: import `WORKSPACE_OPEN_PATH_SURFACE_KIND`/`PaneProps` from `@hachej/boring-workspace/plugin`; import `useApiBaseUrl`/`useWorkspaceRequestId` from `@hachej/boring-workspace`; read `request.target`; fetch `${apiBaseUrl}/api/v1/files/raw?...` with `credentials: "include"` and `x-boring-workspace-id` when present. Never use `/workspace/read` or string kind `"WORKSPACE_OPEN_PATH_SURFACE_KIND"`.',
|
|
1022
|
-
|
|
1022
|
+
'- Pi extension tools: `defineTool` and `export const tools` do NOT exist. Export `default function (pi) { pi.registerTool({ name, description, parameters: { type: "object", properties: {} }, execute }) }`. `parameters` is mandatory even for no-arg tools; omitting it breaks tool execution.',
|
|
1023
1023
|
'- Server/Pi tool method: `handler` \u2014 use `execute`. Return shape: `{ content: [{ type: "text", text }] }` (NEVER a bare string).',
|
|
1024
1024
|
"- Manifest values: `boring.server: true` \u2014 use `false`/omit for hot-reload user plugins, or a relative path string only for advanced boot-time/static server integration.",
|
|
1025
1025
|
"- File layout: files at the package root, or `src/` / `dist/` / `lib/` subdirectories \u2014 the scaffold's hot-reload layout (`front/index.tsx`, optional `agent/index.ts` declared in `pi.extensions`) is the one the workspace refreshes on `/reload`.",
|
|
1026
1026
|
"- Dependency installs: do NOT install plugin UI dependencies at the workspace root. Install them inside `.pi/extensions/<name>/` and keep React/workspace/boring-ui-kit imports as host singletons, not plugin dependencies.",
|
|
1027
1027
|
"- Hot-reload agent tools: do NOT put them in `.pi/extensions/<name>/server/index.ts`; use `pi.extensions` instead. `boring.server` requires static composition plus process restart."
|
|
1028
1028
|
].join("\n"),
|
|
1029
|
+
[
|
|
1030
|
+
"## Installing an existing or published plugin",
|
|
1031
|
+
"To ADD an existing or published plugin (not author a new one), use `boring-ui-plugin install <source>` via bash \u2014 `<source>` is `npm:<package>`, `git:<repo>`, `github:<owner>/<repo>`, an `http(s)` git URL, or a local path; add `--global` for all workspaces (default is this workspace).",
|
|
1032
|
+
"A bare `npm install <package>` does NOT register it as a plugin (no `.pi/settings.json` package source), so it will NOT load \u2014 always use `boring-ui-plugin install`, then ask the user to `/reload` (a `boring.server` backend also needs a process restart).",
|
|
1033
|
+
"Inspect with `boring-ui-plugin list [--json]`; remove with `boring-ui-plugin remove <id-or-source>`."
|
|
1034
|
+
].join("\n"),
|
|
1029
1035
|
docsBlock
|
|
1030
1036
|
].join("\n\n");
|
|
1031
1037
|
}
|
|
@@ -1227,6 +1233,14 @@ function resolveContainedPluginPath(rootDir, value, options = {}) {
|
|
|
1227
1233
|
}
|
|
1228
1234
|
|
|
1229
1235
|
// src/server/agentPlugins/scan.ts
|
|
1236
|
+
function normalizeBoringPluginSource(input) {
|
|
1237
|
+
if (typeof input === "string") return { rootDir: resolve3(input), kind: "internal" };
|
|
1238
|
+
return {
|
|
1239
|
+
rootDir: resolve3(input.rootDir),
|
|
1240
|
+
kind: input.kind,
|
|
1241
|
+
...input.workspaceId ? { workspaceId: input.workspaceId } : {}
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1230
1244
|
function pluginIdFromPackageJson(pkg, rootDir) {
|
|
1231
1245
|
const explicitId = typeof pkg.boring?.id === "string" && pkg.boring.id.trim() ? pkg.boring.id.trim() : void 0;
|
|
1232
1246
|
if (explicitId) return explicitId;
|
|
@@ -1285,10 +1299,11 @@ function packagePathContainmentIssues(rootDir, pkg) {
|
|
|
1285
1299
|
return issues;
|
|
1286
1300
|
}
|
|
1287
1301
|
function discoverBoringPluginDirs(pluginDirs) {
|
|
1288
|
-
const out = /* @__PURE__ */ new
|
|
1302
|
+
const out = /* @__PURE__ */ new Map();
|
|
1289
1303
|
const missingPackageJson = [];
|
|
1290
1304
|
for (const raw of pluginDirs) {
|
|
1291
|
-
const
|
|
1305
|
+
const source = normalizeBoringPluginSource(raw);
|
|
1306
|
+
const dir = source.rootDir;
|
|
1292
1307
|
if (!existsSync2(dir)) continue;
|
|
1293
1308
|
const info = statSync(dir);
|
|
1294
1309
|
if (!info.isDirectory()) continue;
|
|
@@ -1299,13 +1314,19 @@ function discoverBoringPluginDirs(pluginDirs) {
|
|
|
1299
1314
|
const child = join3(dir, entry.name);
|
|
1300
1315
|
if (existsSync2(join3(child, "package.json"))) childPackageDirs.push(child);
|
|
1301
1316
|
}
|
|
1302
|
-
if (hasPackageJson) out.
|
|
1303
|
-
for (const child of childPackageDirs)
|
|
1304
|
-
|
|
1317
|
+
if (hasPackageJson && !out.has(dir)) out.set(dir, source);
|
|
1318
|
+
for (const child of childPackageDirs) {
|
|
1319
|
+
if (!out.has(child)) out.set(child, { ...source, rootDir: child });
|
|
1320
|
+
}
|
|
1321
|
+
const collectionDirNames = /* @__PURE__ */ new Set(["extensions", "npm", "git"]);
|
|
1322
|
+
if (!hasPackageJson && childPackageDirs.length === 0 && !collectionDirNames.has(basename(dir))) {
|
|
1305
1323
|
missingPackageJson.push(dir);
|
|
1306
1324
|
}
|
|
1307
1325
|
}
|
|
1308
|
-
return {
|
|
1326
|
+
return {
|
|
1327
|
+
sources: [...out.values()].sort((a, b) => a.rootDir.localeCompare(b.rootDir)),
|
|
1328
|
+
missingPackageJson: [...new Set(missingPackageJson)].sort()
|
|
1329
|
+
};
|
|
1309
1330
|
}
|
|
1310
1331
|
function scanBoringPlugins(pluginDirs) {
|
|
1311
1332
|
const errors = [];
|
|
@@ -1315,7 +1336,8 @@ function scanBoringPlugins(pluginDirs) {
|
|
|
1315
1336
|
for (const pluginDir of discovered.missingPackageJson) {
|
|
1316
1337
|
errors.push({ pluginDir, code: "MISSING_PACKAGE_JSON", message: "package.json is missing" });
|
|
1317
1338
|
}
|
|
1318
|
-
for (const
|
|
1339
|
+
for (const source of discovered.sources) {
|
|
1340
|
+
const rootDir = source.rootDir;
|
|
1319
1341
|
let raw;
|
|
1320
1342
|
try {
|
|
1321
1343
|
raw = parsePackageJson(rootDir);
|
|
@@ -1353,15 +1375,26 @@ function scanBoringPlugins(pluginDirs) {
|
|
|
1353
1375
|
} else {
|
|
1354
1376
|
const previous = seenIds.get(id);
|
|
1355
1377
|
if (previous) {
|
|
1356
|
-
errors.push({
|
|
1357
|
-
pluginDir: rootDir,
|
|
1358
|
-
pluginId: id,
|
|
1359
|
-
code: "INVALID_PLUGIN_METADATA",
|
|
1360
|
-
message: `duplicate plugin id "${id}" also declared by ${previous}`
|
|
1361
|
-
});
|
|
1362
1378
|
const previousPluginIndex = plugins.findIndex((plugin) => plugin.id === id);
|
|
1363
|
-
|
|
1364
|
-
|
|
1379
|
+
const previousPlugin = previousPluginIndex >= 0 ? plugins[previousPluginIndex] : void 0;
|
|
1380
|
+
const currentIsWorkspaceLocal = Boolean(source.workspaceId);
|
|
1381
|
+
const previousIsWorkspaceLocal = Boolean(previousPlugin?.source.workspaceId);
|
|
1382
|
+
const currentMayShadowPrevious = source.kind === "external" && currentIsWorkspaceLocal && previousPlugin?.source.kind === "external" && !previousIsWorkspaceLocal;
|
|
1383
|
+
if (currentMayShadowPrevious) {
|
|
1384
|
+
if (previousPluginIndex >= 0) plugins.splice(previousPluginIndex, 1);
|
|
1385
|
+
seenIds.set(id, rootDir);
|
|
1386
|
+
} else if (!currentIsWorkspaceLocal && previousIsWorkspaceLocal) {
|
|
1387
|
+
canAddPlugin = false;
|
|
1388
|
+
} else {
|
|
1389
|
+
errors.push({
|
|
1390
|
+
pluginDir: rootDir,
|
|
1391
|
+
pluginId: id,
|
|
1392
|
+
code: "INVALID_PLUGIN_METADATA",
|
|
1393
|
+
message: `duplicate plugin id "${id}" also declared by ${previous}`
|
|
1394
|
+
});
|
|
1395
|
+
if (previousPluginIndex >= 0) plugins.splice(previousPluginIndex, 1);
|
|
1396
|
+
canAddPlugin = false;
|
|
1397
|
+
}
|
|
1365
1398
|
} else {
|
|
1366
1399
|
seenIds.set(id, rootDir);
|
|
1367
1400
|
}
|
|
@@ -1373,6 +1406,7 @@ function scanBoringPlugins(pluginDirs) {
|
|
|
1373
1406
|
}
|
|
1374
1407
|
if (!canAddPlugin) continue;
|
|
1375
1408
|
const pkg = result.packageJson;
|
|
1409
|
+
const hasBoring = pkg.boring !== void 0;
|
|
1376
1410
|
const boring = pkg.boring ?? {};
|
|
1377
1411
|
const pi = pkg.pi;
|
|
1378
1412
|
const frontPath = resolvePluginPath(rootDir, boring.front, { mustExist: true });
|
|
@@ -1385,11 +1419,13 @@ function scanBoringPlugins(pluginDirs) {
|
|
|
1385
1419
|
rootDir,
|
|
1386
1420
|
version,
|
|
1387
1421
|
boring,
|
|
1422
|
+
hasBoring,
|
|
1388
1423
|
...pi ? { pi } : {},
|
|
1389
1424
|
...frontPath ? { frontPath, frontUrl: `/@fs/${frontPath}` } : {},
|
|
1390
1425
|
...serverPath ? { serverPath } : {},
|
|
1391
1426
|
...extensionPaths.length > 0 ? { extensionPaths } : {},
|
|
1392
|
-
...skillPaths.length > 0 ? { skillPaths } : {}
|
|
1427
|
+
...skillPaths.length > 0 ? { skillPaths } : {},
|
|
1428
|
+
source
|
|
1393
1429
|
});
|
|
1394
1430
|
}
|
|
1395
1431
|
const preflight = { ok: errors.length === 0, errors };
|
|
@@ -1400,7 +1436,7 @@ function preflightBoringPlugins(pluginDirs) {
|
|
|
1400
1436
|
}
|
|
1401
1437
|
function readBoringPlugins(pluginDirs) {
|
|
1402
1438
|
const scan = scanBoringPlugins(pluginDirs);
|
|
1403
|
-
return scan.preflight.ok ? scan.plugins : [];
|
|
1439
|
+
return scan.preflight.ok ? scan.plugins.filter((plugin) => plugin.hasBoring) : [];
|
|
1404
1440
|
}
|
|
1405
1441
|
|
|
1406
1442
|
// src/server/agentPlugins/signatureCache.ts
|
|
@@ -1564,10 +1600,11 @@ function frontSignatureRoot(plugin) {
|
|
|
1564
1600
|
return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel) ? frontRoot : dirname6(plugin.frontPath);
|
|
1565
1601
|
}
|
|
1566
1602
|
function pluginSignature(plugin) {
|
|
1567
|
-
return createHash("sha256").update(JSON.stringify(plugin.boring)).update(JSON.stringify(plugin.pi ?? {})).update(plugin.version).update(plugin.frontPath ?? "").update(pluginFileSignature(plugin.frontPath)).update(directorySignature(frontSignatureRoot(plugin))).update(directorySignature(join5(plugin.rootDir, "shared"))).update(plugin.serverPath ?? "").update(pluginFileSignature(plugin.serverPath)).update(directorySignature(plugin.serverPath ? dirname6(plugin.serverPath) : void 0)).update((plugin.extensionPaths ?? []).join("\0")).update((plugin.skillPaths ?? []).join("\0")).digest("hex");
|
|
1603
|
+
return createHash("sha256").update(JSON.stringify(plugin.boring)).update(JSON.stringify(plugin.pi ?? {})).update(plugin.version).update(JSON.stringify(plugin.source)).update(plugin.frontPath ?? "").update(pluginFileSignature(plugin.frontPath)).update(directorySignature(frontSignatureRoot(plugin))).update(directorySignature(join5(plugin.rootDir, "shared"))).update(plugin.serverPath ?? "").update(pluginFileSignature(plugin.serverPath)).update(directorySignature(plugin.serverPath ? dirname6(plugin.serverPath) : void 0)).update((plugin.extensionPaths ?? []).join("\0")).update((plugin.skillPaths ?? []).join("\0")).digest("hex");
|
|
1568
1604
|
}
|
|
1569
1605
|
function computeRequiresRestart(previous, next) {
|
|
1570
1606
|
if (!previous) return [];
|
|
1607
|
+
if (previous.source.kind === "external" && next.source.kind === "external") return [];
|
|
1571
1608
|
const prevHasServer = !!previous.serverPath;
|
|
1572
1609
|
const nextHasServer = !!next.serverPath;
|
|
1573
1610
|
if (!prevHasServer && !nextHasServer) return [];
|
|
@@ -1613,8 +1650,10 @@ var BoringPluginAssetManager = class {
|
|
|
1613
1650
|
version: plugin.version,
|
|
1614
1651
|
revision: plugin.revision,
|
|
1615
1652
|
rootDir: plugin.rootDir,
|
|
1653
|
+
source: plugin.source,
|
|
1616
1654
|
...plugin.frontPath ? { frontPath: plugin.frontPath } : {},
|
|
1617
|
-
...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {}
|
|
1655
|
+
...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {},
|
|
1656
|
+
...plugin.serverPath ? { serverPath: plugin.serverPath } : {}
|
|
1618
1657
|
}));
|
|
1619
1658
|
}
|
|
1620
1659
|
inspectLoadedPiSnapshot() {
|
|
@@ -1654,7 +1693,7 @@ ${prompts.join("\n\n")}` } : {}
|
|
|
1654
1693
|
async doLoadOnce() {
|
|
1655
1694
|
this.lastErrors.clear();
|
|
1656
1695
|
const scan = scanBoringPlugins(this.pluginDirs);
|
|
1657
|
-
const nextPlugins = scan.plugins;
|
|
1696
|
+
const nextPlugins = scan.plugins.filter((plugin) => plugin.hasBoring);
|
|
1658
1697
|
const nextIds = new Set(nextPlugins.map((plugin) => plugin.id));
|
|
1659
1698
|
const invalidPluginDirs = new Set(scan.preflight.errors.map((error) => resolve5(error.pluginDir)));
|
|
1660
1699
|
const events = [];
|
|
@@ -1818,31 +1857,7 @@ function collectRestartWarnings(events) {
|
|
|
1818
1857
|
return warnings;
|
|
1819
1858
|
}
|
|
1820
1859
|
async function boringPluginRoutes(app, opts) {
|
|
1821
|
-
const { manager
|
|
1822
|
-
if (enableReloadRoute) {
|
|
1823
|
-
app.post("/api/boring.reload", async (_request, reply) => {
|
|
1824
|
-
const scan = await manager.load();
|
|
1825
|
-
const rebuild = rebuildPlugins ? await rebuildPlugins() : { ok: true, diagnostics: [] };
|
|
1826
|
-
const restart_warnings = collectRestartWarnings(scan.events);
|
|
1827
|
-
const hasFailures = scan.errors.length > 0 || rebuild.diagnostics.length > 0;
|
|
1828
|
-
if (hasFailures) {
|
|
1829
|
-
return reply.status(422).send({
|
|
1830
|
-
ok: false,
|
|
1831
|
-
errors: scan.errors,
|
|
1832
|
-
diagnostics: rebuild.diagnostics,
|
|
1833
|
-
plugins: scan.loaded,
|
|
1834
|
-
// Even on failure, emit warnings for plugins that DID reload
|
|
1835
|
-
// — partial-failure tolerance means some loaded successfully.
|
|
1836
|
-
...restart_warnings.length > 0 ? { restart_warnings } : {}
|
|
1837
|
-
});
|
|
1838
|
-
}
|
|
1839
|
-
return reply.send({
|
|
1840
|
-
ok: true,
|
|
1841
|
-
plugins: scan.loaded,
|
|
1842
|
-
...restart_warnings.length > 0 ? { restart_warnings } : {}
|
|
1843
|
-
});
|
|
1844
|
-
});
|
|
1845
|
-
}
|
|
1860
|
+
const { manager } = opts;
|
|
1846
1861
|
const listPlugins = async () => manager.list();
|
|
1847
1862
|
app.get("/api/v1/agent-plugins", listPlugins);
|
|
1848
1863
|
const getPluginError = async (request, reply) => {
|
|
@@ -1919,8 +1934,499 @@ function aggregatePluginPrompts(manager) {
|
|
|
1919
1934
|
|
|
1920
1935
|
${prompts.join("\n\n")}`;
|
|
1921
1936
|
}
|
|
1937
|
+
|
|
1938
|
+
// src/server/runtimeBackend/defineRuntimeServerPlugin.ts
|
|
1939
|
+
function defineRuntimeServerPlugin(plugin) {
|
|
1940
|
+
return plugin;
|
|
1941
|
+
}
|
|
1942
|
+
function isPlainObject(value) {
|
|
1943
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1944
|
+
const proto = Object.getPrototypeOf(value);
|
|
1945
|
+
return proto === Object.prototype || proto === null;
|
|
1946
|
+
}
|
|
1947
|
+
function validateRuntimeServerPlugin(value) {
|
|
1948
|
+
if (!isPlainObject(value)) {
|
|
1949
|
+
throw new Error("runtime server plugin default export must be a plain object");
|
|
1950
|
+
}
|
|
1951
|
+
if ("id" in value) {
|
|
1952
|
+
throw new Error("runtime server plugin must not declare id; the host supplies plugin id from package metadata");
|
|
1953
|
+
}
|
|
1954
|
+
if (typeof value.routes !== "function") {
|
|
1955
|
+
throw new Error("runtime server plugin default export must define routes(router)");
|
|
1956
|
+
}
|
|
1957
|
+
if (value.dispose !== void 0 && typeof value.dispose !== "function") {
|
|
1958
|
+
throw new Error("runtime server plugin dispose must be a function when provided");
|
|
1959
|
+
}
|
|
1960
|
+
return value;
|
|
1961
|
+
}
|
|
1962
|
+
function isRuntimePluginResponse(value) {
|
|
1963
|
+
return isPlainObject(value) && value.kind === "response";
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// src/server/runtimeBackend/runtimePathSegments.ts
|
|
1967
|
+
function findUnsafeRuntimePathSegment(path) {
|
|
1968
|
+
if (path.includes("\\")) return "\\";
|
|
1969
|
+
for (const segment of path.split("/")) {
|
|
1970
|
+
if (segment === "." || segment === "..") return segment;
|
|
1971
|
+
}
|
|
1972
|
+
return null;
|
|
1973
|
+
}
|
|
1974
|
+
function describeUnsafeRuntimePathSegment(segment) {
|
|
1975
|
+
if (segment === "\\") return "backslashes";
|
|
1976
|
+
return `${segment} segments`;
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
// src/server/runtimeBackend/routerCapture.ts
|
|
1980
|
+
var METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "ALL"];
|
|
1981
|
+
function routeKey(method, path) {
|
|
1982
|
+
return `${method} ${path}`;
|
|
1983
|
+
}
|
|
1984
|
+
function runtimeRouteKey(method, path) {
|
|
1985
|
+
return routeKey(method.toUpperCase(), path);
|
|
1986
|
+
}
|
|
1987
|
+
function validateRuntimeRoutePath(path) {
|
|
1988
|
+
if (typeof path !== "string" || path.length === 0) {
|
|
1989
|
+
throw new Error("runtime route path must be a non-empty string");
|
|
1990
|
+
}
|
|
1991
|
+
if (!path.startsWith("/")) {
|
|
1992
|
+
throw new Error(`runtime route path must start with /: ${path}`);
|
|
1993
|
+
}
|
|
1994
|
+
if (path.includes("?") || path.includes("#")) {
|
|
1995
|
+
throw new Error(`runtime route path must not include query strings or fragments: ${path}`);
|
|
1996
|
+
}
|
|
1997
|
+
let decodedPath;
|
|
1998
|
+
try {
|
|
1999
|
+
decodedPath = decodeURIComponent(path);
|
|
2000
|
+
} catch {
|
|
2001
|
+
throw new Error(`runtime route path must be valid percent-encoding: ${path}`);
|
|
2002
|
+
}
|
|
2003
|
+
const unsafeSegment = findUnsafeRuntimePathSegment(decodedPath);
|
|
2004
|
+
if (unsafeSegment) {
|
|
2005
|
+
throw new Error(`runtime route path must not contain ${describeUnsafeRuntimePathSegment(unsafeSegment)}: ${path}`);
|
|
2006
|
+
}
|
|
2007
|
+
if (path.includes(":")) {
|
|
2008
|
+
throw new Error(`runtime route path must be exact and must not contain params: ${path}`);
|
|
2009
|
+
}
|
|
2010
|
+
if (path.includes("*")) {
|
|
2011
|
+
throw new Error(`runtime route path must be exact and must not contain wildcards: ${path}`);
|
|
2012
|
+
}
|
|
2013
|
+
return path;
|
|
2014
|
+
}
|
|
2015
|
+
async function captureRuntimeRoutes(register) {
|
|
2016
|
+
const routes = [];
|
|
2017
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2018
|
+
const add = (method, path, handler) => {
|
|
2019
|
+
if (typeof handler !== "function") {
|
|
2020
|
+
throw new Error(`runtime route ${method} ${path} handler must be a function`);
|
|
2021
|
+
}
|
|
2022
|
+
const normalizedPath = validateRuntimeRoutePath(path);
|
|
2023
|
+
const key = routeKey(method, normalizedPath);
|
|
2024
|
+
if (seen.has(key)) throw new Error(`duplicate runtime route: ${key}`);
|
|
2025
|
+
seen.add(key);
|
|
2026
|
+
routes.push({ method, path: normalizedPath, handler });
|
|
2027
|
+
};
|
|
2028
|
+
const router = Object.fromEntries(
|
|
2029
|
+
METHODS.map((method) => [method.toLowerCase(), (path, handler) => add(method, path, handler)])
|
|
2030
|
+
);
|
|
2031
|
+
await register(router);
|
|
2032
|
+
return routes;
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
// src/server/runtimeBackend/runtimeBackendRegistry.ts
|
|
2036
|
+
import { ErrorCode } from "@hachej/boring-agent/shared";
|
|
2037
|
+
|
|
2038
|
+
// src/server/pluginImports/importServerModule.ts
|
|
2039
|
+
import { createRequire as createRequire2 } from "module";
|
|
2040
|
+
import { pathToFileURL } from "url";
|
|
2041
|
+
var require3 = createRequire2(import.meta.url);
|
|
2042
|
+
var warnedJitiMissing = false;
|
|
2043
|
+
function warnJitiUnavailable(serverPath, reason) {
|
|
2044
|
+
if (warnedJitiMissing) return;
|
|
2045
|
+
warnedJitiMissing = true;
|
|
2046
|
+
console.warn(
|
|
2047
|
+
`[boring-workspace] hotReload requested but jiti is unavailable (${reason}). Falling back to native import() for ${serverPath}; subsequent reloads will NOT pick up source changes because Node's module cache will return the same module. Install jiti or set hotReload: false.`
|
|
2048
|
+
);
|
|
2049
|
+
}
|
|
2050
|
+
function jitiImport(serverPath) {
|
|
2051
|
+
try {
|
|
2052
|
+
const jitiModule = require3("jiti");
|
|
2053
|
+
const create = jitiModule.createJiti;
|
|
2054
|
+
if (!create) {
|
|
2055
|
+
warnJitiUnavailable(serverPath, "createJiti not exported");
|
|
2056
|
+
return null;
|
|
2057
|
+
}
|
|
2058
|
+
return create(import.meta.url, { moduleCache: false }).import(serverPath);
|
|
2059
|
+
} catch (err) {
|
|
2060
|
+
warnJitiUnavailable(serverPath, err instanceof Error ? err.message : String(err));
|
|
2061
|
+
return null;
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
async function importServerModule(serverPath, hotReload) {
|
|
2065
|
+
if (hotReload) {
|
|
2066
|
+
const jiti = jitiImport(serverPath);
|
|
2067
|
+
if (jiti) return await jiti;
|
|
2068
|
+
}
|
|
2069
|
+
const href = pathToFileURL(serverPath).href;
|
|
2070
|
+
return await import(
|
|
2071
|
+
/* @vite-ignore */
|
|
2072
|
+
href
|
|
2073
|
+
);
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
// src/server/runtimeBackend/runtimeBackendRegistry.ts
|
|
2077
|
+
var RuntimeBackendError = class extends Error {
|
|
2078
|
+
constructor(code, statusCode, message, details) {
|
|
2079
|
+
super(message);
|
|
2080
|
+
this.code = code;
|
|
2081
|
+
this.statusCode = statusCode;
|
|
2082
|
+
this.details = details;
|
|
2083
|
+
this.name = "RuntimeBackendError";
|
|
2084
|
+
}
|
|
2085
|
+
code;
|
|
2086
|
+
statusCode;
|
|
2087
|
+
details;
|
|
2088
|
+
};
|
|
2089
|
+
function errorMessage(error) {
|
|
2090
|
+
return error instanceof Error ? error.stack ?? error.message : String(error);
|
|
2091
|
+
}
|
|
2092
|
+
function moduleValue(mod) {
|
|
2093
|
+
return typeof mod === "object" && mod !== null && "default" in mod ? mod.default : mod;
|
|
2094
|
+
}
|
|
2095
|
+
function toRouteMap(routes) {
|
|
2096
|
+
const map = /* @__PURE__ */ new Map();
|
|
2097
|
+
for (const route of routes) map.set(runtimeRouteKey(route.method, route.path), route);
|
|
2098
|
+
return map;
|
|
2099
|
+
}
|
|
2100
|
+
function assertJsonSerializable(value) {
|
|
2101
|
+
if (value === void 0 || value === null) return;
|
|
2102
|
+
if (typeof value === "function" || typeof value === "symbol" || typeof value === "bigint") {
|
|
2103
|
+
throw new RuntimeBackendError(
|
|
2104
|
+
ErrorCode.enum.RUNTIME_PLUGIN_RESPONSE_UNSUPPORTED,
|
|
2105
|
+
500,
|
|
2106
|
+
"runtime plugin response is not JSON-serializable"
|
|
2107
|
+
);
|
|
2108
|
+
}
|
|
2109
|
+
try {
|
|
2110
|
+
JSON.stringify(value);
|
|
2111
|
+
} catch (error) {
|
|
2112
|
+
throw new RuntimeBackendError(
|
|
2113
|
+
ErrorCode.enum.RUNTIME_PLUGIN_RESPONSE_UNSUPPORTED,
|
|
2114
|
+
500,
|
|
2115
|
+
`runtime plugin response is not JSON-serializable: ${error instanceof Error ? error.message : String(error)}`
|
|
2116
|
+
);
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
function normalizeResponse(value) {
|
|
2120
|
+
if (value === void 0 || value === null) return { status: 204, headers: {} };
|
|
2121
|
+
if (isRuntimePluginResponse(value)) return normalizeExplicitResponse(value);
|
|
2122
|
+
assertJsonSerializable(value);
|
|
2123
|
+
return { status: 200, headers: { "content-type": "application/json; charset=utf-8" }, body: value };
|
|
2124
|
+
}
|
|
2125
|
+
function normalizeExplicitResponse(value) {
|
|
2126
|
+
const status = value.status ?? (value.body === void 0 || value.body === null ? 204 : 200);
|
|
2127
|
+
if (!Number.isInteger(status) || status < 100 || status > 599) {
|
|
2128
|
+
throw new RuntimeBackendError(
|
|
2129
|
+
ErrorCode.enum.RUNTIME_PLUGIN_RESPONSE_UNSUPPORTED,
|
|
2130
|
+
500,
|
|
2131
|
+
"runtime plugin response status must be an integer HTTP status code"
|
|
2132
|
+
);
|
|
2133
|
+
}
|
|
2134
|
+
const headers = {};
|
|
2135
|
+
if (value.headers !== void 0) {
|
|
2136
|
+
for (const [name, headerValue] of Object.entries(value.headers)) {
|
|
2137
|
+
if (typeof headerValue !== "string") {
|
|
2138
|
+
throw new RuntimeBackendError(
|
|
2139
|
+
ErrorCode.enum.RUNTIME_PLUGIN_RESPONSE_UNSUPPORTED,
|
|
2140
|
+
500,
|
|
2141
|
+
"runtime plugin response headers must be strings"
|
|
2142
|
+
);
|
|
2143
|
+
}
|
|
2144
|
+
headers[name] = headerValue;
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
if (value.body === void 0 || value.body === null) return { status, headers };
|
|
2148
|
+
assertJsonSerializable(value.body);
|
|
2149
|
+
if (!Object.keys(headers).some((name) => name.toLowerCase() === "content-type")) {
|
|
2150
|
+
headers["content-type"] = "application/json; charset=utf-8";
|
|
2151
|
+
}
|
|
2152
|
+
return { status, headers, body: value.body };
|
|
2153
|
+
}
|
|
2154
|
+
async function disposeSnapshot(snapshot) {
|
|
2155
|
+
if (!snapshot.module.dispose) return [];
|
|
2156
|
+
try {
|
|
2157
|
+
await snapshot.module.dispose();
|
|
2158
|
+
return [];
|
|
2159
|
+
} catch (error) {
|
|
2160
|
+
return [{
|
|
2161
|
+
pluginId: snapshot.pluginId,
|
|
2162
|
+
source: `runtime backend dispose (${snapshot.pluginId})`,
|
|
2163
|
+
code: ErrorCode.enum.RUNTIME_PLUGIN_LOAD_FAILED,
|
|
2164
|
+
message: errorMessage(error)
|
|
2165
|
+
}];
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
var RuntimeBackendRegistry = class {
|
|
2169
|
+
snapshots = /* @__PURE__ */ new Map();
|
|
2170
|
+
lastDiagnostics = [];
|
|
2171
|
+
reloadQueue = Promise.resolve({ ok: true, diagnostics: [] });
|
|
2172
|
+
getDiagnostics() {
|
|
2173
|
+
return [...this.lastDiagnostics];
|
|
2174
|
+
}
|
|
2175
|
+
listPluginIds() {
|
|
2176
|
+
return [...this.snapshots.keys()].sort();
|
|
2177
|
+
}
|
|
2178
|
+
async reloadFromLoadedPlugins(plugins) {
|
|
2179
|
+
const run = this.reloadQueue.then(() => this.reloadOnce(plugins), () => this.reloadOnce(plugins));
|
|
2180
|
+
this.reloadQueue = run.then(() => ({ ok: true, diagnostics: [] }), () => ({ ok: false, diagnostics: [] }));
|
|
2181
|
+
return run;
|
|
2182
|
+
}
|
|
2183
|
+
async close() {
|
|
2184
|
+
const run = this.reloadQueue.then(() => this.closeOnce(), () => this.closeOnce());
|
|
2185
|
+
this.reloadQueue = run.then(() => ({ ok: true, diagnostics: [] }), () => ({ ok: false, diagnostics: [] }));
|
|
2186
|
+
return run;
|
|
2187
|
+
}
|
|
2188
|
+
async dispatch(request) {
|
|
2189
|
+
const snapshot = this.snapshots.get(request.pluginId);
|
|
2190
|
+
if (!snapshot) {
|
|
2191
|
+
throw new RuntimeBackendError(
|
|
2192
|
+
ErrorCode.enum.RUNTIME_PLUGIN_NOT_FOUND,
|
|
2193
|
+
404,
|
|
2194
|
+
`runtime backend plugin not found: ${request.pluginId}`
|
|
2195
|
+
);
|
|
2196
|
+
}
|
|
2197
|
+
if (snapshot.source.workspaceId && snapshot.source.workspaceId !== request.workspaceId) {
|
|
2198
|
+
throw new RuntimeBackendError(
|
|
2199
|
+
ErrorCode.enum.RUNTIME_PLUGIN_NOT_FOUND,
|
|
2200
|
+
404,
|
|
2201
|
+
`runtime backend plugin not found in workspace: ${request.pluginId}`
|
|
2202
|
+
);
|
|
2203
|
+
}
|
|
2204
|
+
const route = snapshot.routes.get(runtimeRouteKey(request.method, request.path)) ?? snapshot.routes.get(runtimeRouteKey("ALL", request.path));
|
|
2205
|
+
if (!route) {
|
|
2206
|
+
throw new RuntimeBackendError(
|
|
2207
|
+
ErrorCode.enum.RUNTIME_PLUGIN_ROUTE_NOT_FOUND,
|
|
2208
|
+
404,
|
|
2209
|
+
`runtime backend route not found: ${request.method.toUpperCase()} ${request.path}`
|
|
2210
|
+
);
|
|
2211
|
+
}
|
|
2212
|
+
const ctx = {
|
|
2213
|
+
pluginId: request.pluginId,
|
|
2214
|
+
method: request.method.toUpperCase(),
|
|
2215
|
+
path: request.path,
|
|
2216
|
+
query: request.query,
|
|
2217
|
+
headers: request.headers,
|
|
2218
|
+
signal: request.signal,
|
|
2219
|
+
body: request.body,
|
|
2220
|
+
logger: request.logger
|
|
2221
|
+
};
|
|
2222
|
+
try {
|
|
2223
|
+
return normalizeResponse(await route.handler(ctx));
|
|
2224
|
+
} catch (error) {
|
|
2225
|
+
if (error instanceof RuntimeBackendError) throw error;
|
|
2226
|
+
throw new RuntimeBackendError(
|
|
2227
|
+
ErrorCode.enum.RUNTIME_PLUGIN_HANDLER_FAILED,
|
|
2228
|
+
500,
|
|
2229
|
+
error instanceof Error ? error.message : String(error)
|
|
2230
|
+
);
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
async reloadOnce(plugins) {
|
|
2234
|
+
const diagnostics = [];
|
|
2235
|
+
const externalRuntimePlugins = plugins.filter((plugin) => plugin.source.kind === "external" && plugin.serverPath);
|
|
2236
|
+
const nextIds = new Set(externalRuntimePlugins.map((plugin) => plugin.id));
|
|
2237
|
+
for (const id of [...this.snapshots.keys()]) {
|
|
2238
|
+
if (nextIds.has(id)) continue;
|
|
2239
|
+
const previous = this.snapshots.get(id);
|
|
2240
|
+
if (!previous) continue;
|
|
2241
|
+
this.snapshots.delete(id);
|
|
2242
|
+
diagnostics.push(...await disposeSnapshot(previous));
|
|
2243
|
+
}
|
|
2244
|
+
for (const plugin of externalRuntimePlugins) {
|
|
2245
|
+
const serverPath = plugin.serverPath;
|
|
2246
|
+
if (!serverPath) continue;
|
|
2247
|
+
try {
|
|
2248
|
+
const mod = await importServerModule(serverPath, true);
|
|
2249
|
+
const runtimePlugin = validateRuntimeServerPlugin(moduleValue(mod));
|
|
2250
|
+
const routes = await captureRuntimeRoutes((router) => runtimePlugin.routes(router));
|
|
2251
|
+
const nextSnapshot = {
|
|
2252
|
+
pluginId: plugin.id,
|
|
2253
|
+
source: plugin.source,
|
|
2254
|
+
module: runtimePlugin,
|
|
2255
|
+
routes: toRouteMap(routes)
|
|
2256
|
+
};
|
|
2257
|
+
const previous = this.snapshots.get(plugin.id);
|
|
2258
|
+
this.snapshots.set(plugin.id, nextSnapshot);
|
|
2259
|
+
if (previous) diagnostics.push(...await disposeSnapshot(previous));
|
|
2260
|
+
} catch (error) {
|
|
2261
|
+
diagnostics.push({
|
|
2262
|
+
pluginId: plugin.id,
|
|
2263
|
+
source: `runtime backend (${plugin.id})`,
|
|
2264
|
+
code: ErrorCode.enum.RUNTIME_PLUGIN_LOAD_FAILED,
|
|
2265
|
+
message: errorMessage(error)
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
this.lastDiagnostics = diagnostics;
|
|
2270
|
+
return { ok: diagnostics.length === 0, diagnostics };
|
|
2271
|
+
}
|
|
2272
|
+
async closeOnce() {
|
|
2273
|
+
const diagnostics = [];
|
|
2274
|
+
for (const snapshot of this.snapshots.values()) {
|
|
2275
|
+
diagnostics.push(...await disposeSnapshot(snapshot));
|
|
2276
|
+
}
|
|
2277
|
+
this.snapshots.clear();
|
|
2278
|
+
this.lastDiagnostics = diagnostics;
|
|
2279
|
+
return { ok: diagnostics.length === 0, diagnostics };
|
|
2280
|
+
}
|
|
2281
|
+
};
|
|
2282
|
+
|
|
2283
|
+
// src/server/runtimeBackend/runtimeBackendGateway.ts
|
|
2284
|
+
import { ErrorCode as ErrorCode2 } from "@hachej/boring-agent/shared";
|
|
2285
|
+
var GATEWAY_PREFIX = "/api/v1/plugins/";
|
|
2286
|
+
function rawPathFromRequest(request) {
|
|
2287
|
+
const url = request.raw.url ?? request.url;
|
|
2288
|
+
const queryIndex = url.indexOf("?");
|
|
2289
|
+
return queryIndex === -1 ? url : url.slice(0, queryIndex);
|
|
2290
|
+
}
|
|
2291
|
+
function rawGatewayTail(request, pluginId) {
|
|
2292
|
+
const rawPath = rawPathFromRequest(request);
|
|
2293
|
+
const prefix = `${GATEWAY_PREFIX}${pluginId}`;
|
|
2294
|
+
if (rawPath === prefix || rawPath === `${prefix}/`) return "/";
|
|
2295
|
+
if (!rawPath.startsWith(`${prefix}/`)) {
|
|
2296
|
+
throw new RuntimeBackendError(
|
|
2297
|
+
ErrorCode2.enum.RUNTIME_PLUGIN_ROUTE_NOT_FOUND,
|
|
2298
|
+
404,
|
|
2299
|
+
"runtime backend route not found"
|
|
2300
|
+
);
|
|
2301
|
+
}
|
|
2302
|
+
return rawPath.slice(prefix.length);
|
|
2303
|
+
}
|
|
2304
|
+
function normalizeGatewayPath(rawTail) {
|
|
2305
|
+
let path;
|
|
2306
|
+
try {
|
|
2307
|
+
path = decodeURIComponent(rawTail);
|
|
2308
|
+
} catch {
|
|
2309
|
+
throw new RuntimeBackendError(
|
|
2310
|
+
ErrorCode2.enum.RUNTIME_PLUGIN_ROUTE_NOT_FOUND,
|
|
2311
|
+
404,
|
|
2312
|
+
"runtime backend route path is not valid percent-encoding"
|
|
2313
|
+
);
|
|
2314
|
+
}
|
|
2315
|
+
if (path.length === 0) path = "/";
|
|
2316
|
+
const unsafeSegment = findUnsafeRuntimePathSegment(path);
|
|
2317
|
+
if (unsafeSegment) {
|
|
2318
|
+
throw new RuntimeBackendError(
|
|
2319
|
+
ErrorCode2.enum.RUNTIME_PLUGIN_ROUTE_NOT_FOUND,
|
|
2320
|
+
404,
|
|
2321
|
+
`runtime backend route path must not contain ${describeUnsafeRuntimePathSegment(unsafeSegment)}`
|
|
2322
|
+
);
|
|
2323
|
+
}
|
|
2324
|
+
return path;
|
|
2325
|
+
}
|
|
2326
|
+
function firstString(value) {
|
|
2327
|
+
if (Array.isArray(value)) return value[0];
|
|
2328
|
+
return value;
|
|
2329
|
+
}
|
|
2330
|
+
function headersFromRequest(request) {
|
|
2331
|
+
const headers = new Headers();
|
|
2332
|
+
for (const [name, value] of Object.entries(request.headers)) {
|
|
2333
|
+
if (value === void 0) continue;
|
|
2334
|
+
if (Array.isArray(value)) {
|
|
2335
|
+
for (const item of value) headers.append(name, item);
|
|
2336
|
+
} else {
|
|
2337
|
+
headers.set(name, String(value));
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
return headers;
|
|
2341
|
+
}
|
|
2342
|
+
function loggerFromRequest(request) {
|
|
2343
|
+
return {
|
|
2344
|
+
debug: (arg, message) => {
|
|
2345
|
+
if (message === void 0) request.log.debug(arg);
|
|
2346
|
+
else request.log.debug(arg, message);
|
|
2347
|
+
},
|
|
2348
|
+
info: (arg, message) => {
|
|
2349
|
+
if (message === void 0) request.log.info(arg);
|
|
2350
|
+
else request.log.info(arg, message);
|
|
2351
|
+
},
|
|
2352
|
+
warn: (arg, message) => {
|
|
2353
|
+
if (message === void 0) request.log.warn(arg);
|
|
2354
|
+
else request.log.warn(arg, message);
|
|
2355
|
+
},
|
|
2356
|
+
error: (arg, message) => {
|
|
2357
|
+
if (message === void 0) request.log.error(arg);
|
|
2358
|
+
else request.log.error(arg, message);
|
|
2359
|
+
}
|
|
2360
|
+
};
|
|
2361
|
+
}
|
|
2362
|
+
function sendDispatchResponse(reply, response) {
|
|
2363
|
+
reply.status(response.status);
|
|
2364
|
+
for (const [name, value] of Object.entries(response.headers)) reply.header(name, value);
|
|
2365
|
+
if (response.body === void 0 || response.body === null) return reply.send();
|
|
2366
|
+
return reply.send(response.body);
|
|
2367
|
+
}
|
|
2368
|
+
function sendError(reply, error) {
|
|
2369
|
+
if (error instanceof RuntimeBackendError) {
|
|
2370
|
+
return reply.status(error.statusCode).send({
|
|
2371
|
+
error: {
|
|
2372
|
+
code: error.code,
|
|
2373
|
+
message: error.message,
|
|
2374
|
+
...error.details ? { details: error.details } : {}
|
|
2375
|
+
}
|
|
2376
|
+
});
|
|
2377
|
+
}
|
|
2378
|
+
return reply.status(500).send({
|
|
2379
|
+
error: {
|
|
2380
|
+
code: ErrorCode2.enum.INTERNAL_ERROR,
|
|
2381
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2382
|
+
}
|
|
2383
|
+
});
|
|
2384
|
+
}
|
|
2385
|
+
async function runtimeBackendGateway(app, opts) {
|
|
2386
|
+
const handle = async (request, reply) => {
|
|
2387
|
+
const { pluginId } = request.params;
|
|
2388
|
+
if (!isValidBoringPluginId(pluginId)) {
|
|
2389
|
+
return sendError(reply, new RuntimeBackendError(
|
|
2390
|
+
ErrorCode2.enum.RUNTIME_PLUGIN_NOT_FOUND,
|
|
2391
|
+
404,
|
|
2392
|
+
"runtime backend plugin not found"
|
|
2393
|
+
));
|
|
2394
|
+
}
|
|
2395
|
+
let path;
|
|
2396
|
+
try {
|
|
2397
|
+
path = normalizeGatewayPath(rawGatewayTail(request, pluginId));
|
|
2398
|
+
} catch (error) {
|
|
2399
|
+
return sendError(reply, error);
|
|
2400
|
+
}
|
|
2401
|
+
const abort = new AbortController();
|
|
2402
|
+
const close = () => abort.abort();
|
|
2403
|
+
request.raw.on("close", close);
|
|
2404
|
+
try {
|
|
2405
|
+
const response = await opts.registry.dispatch({
|
|
2406
|
+
pluginId,
|
|
2407
|
+
method: request.method,
|
|
2408
|
+
path,
|
|
2409
|
+
query: new URLSearchParams(request.query),
|
|
2410
|
+
headers: headersFromRequest(request),
|
|
2411
|
+
signal: abort.signal,
|
|
2412
|
+
body: request.body,
|
|
2413
|
+
logger: loggerFromRequest(request),
|
|
2414
|
+
...firstString(request.headers["x-boring-workspace-id"]) ?? opts.defaultWorkspaceId ? { workspaceId: firstString(request.headers["x-boring-workspace-id"]) ?? opts.defaultWorkspaceId } : {}
|
|
2415
|
+
});
|
|
2416
|
+
return sendDispatchResponse(reply, response);
|
|
2417
|
+
} catch (error) {
|
|
2418
|
+
return sendError(reply, error);
|
|
2419
|
+
} finally {
|
|
2420
|
+
request.raw.off("close", close);
|
|
2421
|
+
}
|
|
2422
|
+
};
|
|
2423
|
+
app.all("/api/v1/plugins/:pluginId", handle);
|
|
2424
|
+
app.all("/api/v1/plugins/:pluginId/*", handle);
|
|
2425
|
+
}
|
|
1922
2426
|
export {
|
|
1923
2427
|
BoringPluginAssetManager,
|
|
2428
|
+
RuntimeBackendError,
|
|
2429
|
+
RuntimeBackendRegistry,
|
|
1924
2430
|
aggregatePluginPrompts,
|
|
1925
2431
|
bootstrapServer,
|
|
1926
2432
|
boringPluginRoutes,
|
|
@@ -1931,14 +2437,17 @@ export {
|
|
|
1931
2437
|
createInMemoryBridge,
|
|
1932
2438
|
createWorkspaceUiTools,
|
|
1933
2439
|
definePluginAsset,
|
|
2440
|
+
defineRuntimeServerPlugin,
|
|
1934
2441
|
defineServerPlugin,
|
|
1935
2442
|
pluginFileSignature,
|
|
1936
2443
|
preflightBoringPlugins,
|
|
1937
2444
|
readBoringPlugins,
|
|
1938
2445
|
readPluginSignatureCache,
|
|
1939
2446
|
resolvePluginAssetPath,
|
|
2447
|
+
runtimeBackendGateway,
|
|
1940
2448
|
scanBoringPlugins,
|
|
1941
2449
|
uiRoutes,
|
|
2450
|
+
validateRuntimeServerPlugin,
|
|
1942
2451
|
validateServerPlugin,
|
|
1943
2452
|
writePluginSignatureCache
|
|
1944
2453
|
};
|