mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-05 12:31:40 +00:00
* refactor(plugin-sdk): narrow wildcard barrels to explicit used exports * refactor(tools): delete dead tool-planning module exposed by barrel narrowing * fix(plugin-sdk): restore deprecation tag on OpenClawSchemaType alias * test(agents): drop test for deleted runtime proxy module * refactor(tools): trim descriptor types to cache consumers * refactor(deadcode): harvest exports orphaned by barrel narrowing * refactor(deadcode): harvest exports orphaned by barrel narrowing (rest) * fix(agents): restore sdk imports and test markers via public predicate * fix(plugin-sdk): named type re-exports in plugin-entry; trim types barrel precisely * chore(plugin-sdk): account unmasked deprecated provider types in budgets * fix(plugins): name star-only type rows for dts bundling * fix(plugins): restore host-hook surface; unexport internal api compositions * fix(plugins): named type imports for api composition; restore needed source exports * fix(plugins): knip-visible type imports for registry surfaces * test: adapt tests to privatized media and command internals * fix(qa-lab): re-export snapshot conversation type * style: format sessions sdk imports * fix(plugins): restore smoke entry export; pin budgets to exact actuals * fix(plugins): canonical smoke-entry import; drop orphaned root shims * fix(plugins): allowlist manifest probe, repoint qa web import, drop dead browser barrels * fix(plugin-sdk): pin codex auth marker and scaffold provider type * fix(qa-lab): keep web-facing model-selection shim within boundary rules * fix(plugin-sdk): preserve merged contracts through narrowed barrels * chore(plugin-sdk): pin post-rebase surface budgets
108 lines
3.8 KiB
TypeScript
108 lines
3.8 KiB
TypeScript
// Tracks plugin HTTP registry context for current async execution.
|
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
import { normalizePluginHttpPath } from "./http-path.js";
|
|
import { findOverlappingPluginHttpRoute } from "./http-route-overlap.js";
|
|
import type { PluginHttpRouteRegistration, PluginRegistry } from "./registry.js";
|
|
import { requireActivePluginHttpRouteRegistry } from "./runtime.js";
|
|
|
|
type PluginHttpRouteHandler = (
|
|
req: IncomingMessage,
|
|
res: ServerResponse,
|
|
) => Promise<boolean | void> | boolean | void;
|
|
|
|
const pluginHttpRouteRegistryScope = new AsyncLocalStorage<PluginRegistry>();
|
|
|
|
export function withPluginHttpRouteRegistry<T>(registry: PluginRegistry, run: () => T): T {
|
|
return pluginHttpRouteRegistryScope.run(registry, run);
|
|
}
|
|
|
|
export function registerPluginHttpRoute(params: {
|
|
path?: string | null;
|
|
fallbackPath?: string | null;
|
|
handler: PluginHttpRouteHandler;
|
|
auth: PluginHttpRouteRegistration["auth"];
|
|
match?: PluginHttpRouteRegistration["match"];
|
|
gatewayRuntimeScopeSurface?: PluginHttpRouteRegistration["gatewayRuntimeScopeSurface"];
|
|
replaceExisting?: boolean;
|
|
pluginId?: string;
|
|
source?: string;
|
|
accountId?: string;
|
|
log?: (message: string) => void;
|
|
registry?: PluginRegistry;
|
|
}): () => void {
|
|
const registry =
|
|
params.registry ??
|
|
pluginHttpRouteRegistryScope.getStore() ??
|
|
requireActivePluginHttpRouteRegistry();
|
|
const routes = registry.httpRoutes ?? [];
|
|
registry.httpRoutes = routes;
|
|
|
|
const normalizedPath = normalizePluginHttpPath(params.path, params.fallbackPath);
|
|
const suffix = params.accountId ? ` for account "${params.accountId}"` : "";
|
|
if (!normalizedPath) {
|
|
params.log?.(`plugin: webhook path missing${suffix}`);
|
|
return () => {};
|
|
}
|
|
|
|
const routeMatch = params.match ?? "exact";
|
|
const overlappingRoute = findOverlappingPluginHttpRoute(routes, {
|
|
path: normalizedPath,
|
|
match: routeMatch,
|
|
});
|
|
if (overlappingRoute && overlappingRoute.auth !== params.auth) {
|
|
params.log?.(
|
|
`plugin: route overlap denied at ${normalizedPath} (${routeMatch}, ${params.auth})${suffix}; ` +
|
|
`overlaps ${overlappingRoute.path} (${overlappingRoute.match}, ${overlappingRoute.auth}) ` +
|
|
`owned by ${overlappingRoute.pluginId ?? "unknown-plugin"} (${overlappingRoute.source ?? "unknown-source"})`,
|
|
);
|
|
return () => {};
|
|
}
|
|
const existingIndex = routes.findIndex(
|
|
(entry) => entry.path === normalizedPath && entry.match === routeMatch,
|
|
);
|
|
if (existingIndex >= 0) {
|
|
const existing = routes[existingIndex];
|
|
if (!existing) {
|
|
return () => {};
|
|
}
|
|
if (!params.replaceExisting) {
|
|
params.log?.(
|
|
`plugin: route conflict at ${normalizedPath} (${routeMatch})${suffix}; owned by ${existing.pluginId ?? "unknown-plugin"} (${existing.source ?? "unknown-source"})`,
|
|
);
|
|
return () => {};
|
|
}
|
|
if (existing.pluginId && params.pluginId && existing.pluginId !== params.pluginId) {
|
|
params.log?.(
|
|
`plugin: route replacement denied for ${normalizedPath} (${routeMatch})${suffix}; owned by ${existing.pluginId}`,
|
|
);
|
|
return () => {};
|
|
}
|
|
const pluginHint = params.pluginId ? ` (${params.pluginId})` : "";
|
|
params.log?.(
|
|
`plugin: replacing stale webhook path ${normalizedPath} (${routeMatch})${suffix}${pluginHint}`,
|
|
);
|
|
routes.splice(existingIndex, 1);
|
|
}
|
|
|
|
const entry: PluginHttpRouteRegistration = {
|
|
path: normalizedPath,
|
|
handler: params.handler,
|
|
auth: params.auth,
|
|
match: routeMatch,
|
|
...(params.gatewayRuntimeScopeSurface
|
|
? { gatewayRuntimeScopeSurface: params.gatewayRuntimeScopeSurface }
|
|
: {}),
|
|
pluginId: params.pluginId,
|
|
source: params.source,
|
|
};
|
|
routes.push(entry);
|
|
|
|
return () => {
|
|
const index = routes.indexOf(entry);
|
|
if (index >= 0) {
|
|
routes.splice(index, 1);
|
|
}
|
|
};
|
|
}
|