openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
220 lines (219 loc) • 10.8 kB
JavaScript
import { n as validateJsonSchemaValue } from "./schema-validator-CLfGeb79.js";
import "./board-D2ZgR9aD.js";
import { n as GITHUB_ACTIONS_BINDING_ID, r as GITHUB_ACTIONS_GRANT_PREFIX, t as GITHUB_ACTIONS_AUTHOR_GUIDANCE } from "./github-actions-capability-rN2d0K3V.js";
import { t as SANDBOX_HOST_PATH } from "./sandbox-host-BttkEpCR.js";
//#region src/boards/board-host-capability-ids.ts
const CORE_BOARD_DATA_BINDING_IDS = [
"sessions.list",
"usage.status",
"usage.cost",
"cron.list",
"cron.status",
"agents.list",
"health"
];
const CORE_BOARD_ACTION_VERB_IDS = ["cron.trigger"];
/** Widget grants share one string namespace across reads and actions. */
const CORE_BOARD_HOST_CAPABILITY_IDS = [
...CORE_BOARD_DATA_BINDING_IDS,
GITHUB_ACTIONS_BINDING_ID,
...CORE_BOARD_ACTION_VERB_IDS
];
//#endregion
//#region src/plugins/dashboard-capabilities.ts
var PluginDashboardDeclarationError = class extends Error {
constructor(message) {
super(message);
this.name = "PluginDashboardDeclarationError";
}
};
function fail$1(pluginId, message) {
throw new PluginDashboardDeclarationError(`invalid dashboard declaration for plugin ${JSON.stringify(pluginId)}: ${message}`);
}
function buildCapabilityId(params) {
const capabilityId = `${params.pluginId.replaceAll("%", "%25").replaceAll(".", "%2E")}.${params.localId}`;
if (capabilityId.length > params.maxLength) return fail$1(params.pluginId, `capability id ${JSON.stringify(capabilityId)} exceeds ${params.maxLength} characters`);
return capabilityId;
}
function requireOwnedMethod(params) {
const descriptor = params.registry.gatewayMethodDescriptors.find((candidate) => candidate.name === params.method);
if (descriptor?.owner.kind !== "plugin" || descriptor.owner.pluginId !== params.pluginId) return fail$1(params.pluginId, `method ${JSON.stringify(params.method)} must be registered by the declaring plugin`);
if (descriptor.scope !== params.expectedScope) return fail$1(params.pluginId, `method ${JSON.stringify(params.method)} must use ${params.expectedScope}, got ${descriptor.scope}`);
const handler = params.registry.gatewayHandlers[params.method];
if (!handler) return fail$1(params.pluginId, `method ${JSON.stringify(params.method)} is missing its registered handler`);
return handler;
}
/** Validates and publishes one plugin's manifest-declared dashboard capabilities atomically. */
function registerPluginDashboardCapabilities(params) {
const dashboard = params.record.dashboard;
if (!dashboard) return;
const dataBindings = [];
const actionVerbs = [];
const capabilityIds = /* @__PURE__ */ new Set();
const claimCapabilityId = (capabilityId) => {
if (capabilityIds.has(capabilityId) || params.registry.dashboardDataBindings.has(capabilityId) || params.registry.dashboardActionVerbs.has(capabilityId)) fail$1(params.record.id, `duplicate capability id ${JSON.stringify(capabilityId)}`);
if (CORE_BOARD_HOST_CAPABILITY_IDS.includes(capabilityId) || capabilityId.startsWith(GITHUB_ACTIONS_GRANT_PREFIX) || capabilityId.startsWith("cron.trigger:")) fail$1(params.record.id, `capability id ${JSON.stringify(capabilityId)} is reserved by core`);
capabilityIds.add(capabilityId);
};
for (const declaration of dashboard.dataBindings ?? []) {
const capabilityId = buildCapabilityId({
pluginId: params.record.id,
localId: declaration.id,
maxLength: 64
});
claimCapabilityId(capabilityId);
dataBindings.push({
...declaration,
pluginId: params.record.id,
capabilityId,
handler: requireOwnedMethod({
pluginId: params.record.id,
method: declaration.method,
expectedScope: "operator.read",
registry: params.registry
})
});
}
for (const declaration of dashboard.actionVerbs ?? []) {
const capabilityId = buildCapabilityId({
pluginId: params.record.id,
localId: declaration.id,
maxLength: 269
});
claimCapabilityId(capabilityId);
const handler = requireOwnedMethod({
pluginId: params.record.id,
method: declaration.method,
expectedScope: "operator.write",
registry: params.registry
});
if (declaration.paramShape) try {
validateJsonSchemaValue({
schema: declaration.paramShape,
cacheKey: `dashboard-action:${params.record.id}:${declaration.id}`,
value: void 0
});
} catch (error) {
fail$1(params.record.id, `action ${JSON.stringify(capabilityId)} has an invalid paramShape: ${String(error)}`);
}
actionVerbs.push({
...declaration,
pluginId: params.record.id,
capabilityId,
handler
});
}
for (const registration of dataBindings) params.registry.dashboardDataBindings.set(registration.capabilityId, registration);
for (const registration of actionVerbs) params.registry.dashboardActionVerbs.set(registration.capabilityId, registration);
}
/** Describe only validated, active registrations, with whole entries under one prompt budget. */
function describeDashboardCapabilities(registry) {
let text = `${GITHUB_ACTIONS_AUTHOR_GUIDANCE} Other core reads (grant the binding ID): ${CORE_BOARD_DATA_BINDING_IDS.join(", ")}.`;
const entries = [...[...registry?.dashboardDataBindings.values() ?? []].map((entry) => ({
id: entry.capabilityId,
text: `read ${entry.capabilityId}: ${entry.description}`
})), ...[...registry?.dashboardActionVerbs.values() ?? []].map((entry) => ({
id: entry.capabilityId,
text: `action ${entry.capabilityId}: ${entry.description}${entry.paramShape ? `; params ${JSON.stringify(entry.paramShape)}` : ""}`
}))].toSorted((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0);
let omitted = 0;
for (const entry of entries) if (text.length + entry.text.length + 2 > 950) omitted += 1;
else text += `\n${entry.text}`;
if (omitted) text += `\n${omitted} plugin capabilities omitted; see the active plugins' dashboard documentation.`;
return text;
}
//#endregion
//#region src/plugins/board-widget-content-kinds.ts
const CONTENT_KIND_PATTERN = /^[a-z][a-z0-9-]{0,31}$/u;
const SURFACE_PATTERN = /^[a-z][a-z0-9._-]{0,63}$/u;
const RESERVED_CONTENT_KINDS = /* @__PURE__ */ new Set([
"html",
"mcp-app",
"plugin"
]);
function fail(pluginId, message) {
throw new PluginDashboardDeclarationError(`invalid board widget content kind for plugin ${JSON.stringify(pluginId)}: ${message}`);
}
function isGatewayLocalPath(value) {
return value.startsWith("/") && !value.startsWith("//") && !value.startsWith("/\\");
}
function isCanonicalPublicResourcePath(value) {
try {
return value !== "/mcp-app-sandbox" && new URL(value, "http://localhost").pathname === value;
} catch {
return false;
}
}
/** Validates and publishes one runtime board-widget content kind. */
function registerPluginBoardWidgetContentKind(params) {
const { definition, record, registry } = params;
const kind = typeof definition.kind === "string" ? definition.kind.trim() : "";
const label = typeof definition.label === "string" ? definition.label.trim() : "";
const surface = typeof definition.resources?.surface === "string" ? definition.resources.surface.trim() : "";
const paths = definition.resources?.paths;
if (!CONTENT_KIND_PATTERN.test(kind) || RESERVED_CONTENT_KINDS.has(kind)) fail(record.id, `kind ${JSON.stringify(kind)} is invalid or reserved`);
if (!label || label.length > 80) fail(record.id, "label must contain 1-80 characters");
if (!SURFACE_PATTERN.test(surface)) fail(record.id, `resource surface ${JSON.stringify(surface)} is invalid`);
if (!Array.isArray(paths) || paths.length === 0 || paths.length > 8 || paths.some((resourcePath) => typeof resourcePath !== "string" || resourcePath.length > 256 || !isGatewayLocalPath(resourcePath)) || new Set(paths).size !== paths.length) fail(record.id, "resource paths must be 1-8 unique gateway-local absolute paths");
if (typeof definition.validateSource !== "function" || typeof definition.composeDocument !== "function") fail(record.id, "validateSource and composeDocument callbacks are required");
const publicReader = definition.resources.readPublicResource;
if (publicReader !== void 0 && typeof publicReader !== "function") fail(record.id, "readPublicResource must be a function");
if (publicReader && paths.some((resourcePath) => !isCanonicalPublicResourcePath(resourcePath))) fail(record.id, `public resource paths must be canonical URL pathnames and cannot use ${SANDBOX_HOST_PATH}`);
for (const registered of registry.boardWidgetContentKinds.values()) {
const existing = registered.definition.resources;
if ((publicReader || existing.readPublicResource) && existing.paths.some((entry) => paths.includes(entry))) fail(record.id, "public resource paths must be unique across registered content kinds");
}
if (registry.boardWidgetContentKinds.has(kind)) fail(record.id, `duplicate kind ${JSON.stringify(kind)}`);
const pluginKind = `${record.id}:${kind}`;
if (!/^[a-z0-9][a-z0-9-]{0,63}:[a-z0-9][a-z0-9._-]{0,63}$/u.test(pluginKind)) fail(record.id, `persisted kind ${JSON.stringify(pluginKind)} is invalid`);
registry.boardWidgetContentKinds.set(kind, {
pluginId: record.id,
pluginKind,
definition: {
...definition,
kind,
label,
resources: {
...definition.resources,
surface,
paths: [...paths]
}
}
});
}
function createPluginBoardWidgetContentKindRegistrar(registry) {
return (record, definition) => registerPluginBoardWidgetContentKind({
record,
registry,
definition
});
}
function resolveBoardWidgetContentKind(registry, kind) {
return registry?.boardWidgetContentKinds.get(kind);
}
function resolveBoardWidgetContentKindByPluginKind(registry, pluginKind) {
if (!registry) return;
for (const registration of registry.boardWidgetContentKinds.values()) if (registration.pluginKind === pluginKind) return registration;
}
function listBoardWidgetContentKinds(registry) {
return [...registry?.boardWidgetContentKinds.keys() ?? []].toSorted();
}
/** Resolves registration resource paths below one connection-scoped plugin capability URL. */
function resolveBoardWidgetContentKindResourceUrls(registration, scopedHostUrl) {
try {
const scoped = new URL(scopedHostUrl);
const prefix = scoped.pathname.replace(/\/+$/u, "");
if (!/^\/__openclaw__\/cap\/[^/]+$/u.test(prefix)) return;
return Object.fromEntries(registration.definition.resources.paths.map((resourcePath) => {
const url = new URL(scoped.toString());
url.pathname = `${prefix}${resourcePath}`;
url.search = "";
url.hash = "";
return [resourcePath, url.toString()];
}));
} catch {
return;
}
}
//#endregion
export { resolveBoardWidgetContentKindResourceUrls as a, registerPluginDashboardCapabilities as c, resolveBoardWidgetContentKindByPluginKind as i, CORE_BOARD_DATA_BINDING_IDS as l, listBoardWidgetContentKinds as n, PluginDashboardDeclarationError as o, resolveBoardWidgetContentKind as r, describeDashboardCapabilities as s, createPluginBoardWidgetContentKindRegistrar as t };