mirror of https://github.com/openclaw/openclaw.git
fix(plugins): lazily initialize runtime and split plugin-sdk startup imports (#28620)
Merged via squash.
Prepared head SHA: 8bd7d6c13b
Co-authored-by: hmemcpy <601206+hmemcpy@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
This commit is contained in:
parent
4b17d6d882
commit
a4850b1b8f
|
|
@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai
|
|||
### Fixes
|
||||
|
||||
- Gateway/security default response headers: add `Permissions-Policy: camera=(), microphone=(), geolocation=()` to baseline gateway HTTP security headers for all responses. (#30186) thanks @habakan.
|
||||
- Plugins/startup loading: lazily initialize plugin runtime, split startup-critical plugin SDK imports into `openclaw/plugin-sdk/core` and `openclaw/plugin-sdk/telegram`, and preserve `api.runtime` reflection semantics for plugin compatibility. (#28620) thanks @hmemcpy.
|
||||
- Security/auth labels: remove token and API-key snippets from user-facing auth status labels so `/status` and `/models` do not expose credential fragments. (#33262) thanks @cu1ch3n.
|
||||
- Security/audit denyCommands guidance: suggest likely exact node command IDs for unknown `gateway.nodes.denyCommands` entries so ineffective denylist entries are easier to correct. (#29713) thanks @liquidhorizon88-bot.
|
||||
- Docs/security hardening guidance: document Docker `DOCKER-USER` + UFW policy and add cross-linking from Docker install docs for VPS/public-host setups. (#27613) thanks @dorukardahan.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import os from "node:os";
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import {
|
||||
approveDevicePairing,
|
||||
listDevicePairing,
|
||||
resolveGatewayBindUrl,
|
||||
runPluginCommandWithTimeout,
|
||||
resolveTailnetHostWithRunner,
|
||||
} from "openclaw/plugin-sdk";
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
import qrcode from "qrcode-terminal";
|
||||
import {
|
||||
armPairNotifyOnce,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
|
||||
|
||||
const memoryCorePlugin = {
|
||||
id: "memory-core",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { OpenClawPluginApi, OpenClawPluginService } from "openclaw/plugin-sdk";
|
||||
import type { OpenClawPluginApi, OpenClawPluginService } from "openclaw/plugin-sdk/core";
|
||||
|
||||
type ArmGroup = "camera" | "screen" | "writes" | "all";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
|
||||
type ElevenLabsVoice = {
|
||||
voice_id: string;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { ChannelPlugin, OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
||||
import type { ChannelPlugin, OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
|
||||
import { telegramPlugin } from "./src/channel.js";
|
||||
import { setTelegramRuntime } from "./src/runtime.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import {
|
|||
type OpenClawConfig,
|
||||
type ResolvedTelegramAccount,
|
||||
type TelegramProbe,
|
||||
} from "openclaw/plugin-sdk";
|
||||
} from "openclaw/plugin-sdk/telegram";
|
||||
import { getTelegramRuntime } from "./runtime.js";
|
||||
|
||||
const meta = getChatChannelMeta("telegram");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk/core";
|
||||
|
||||
let runtime: PluginRuntime | null = null;
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,14 @@
|
|||
"types": "./dist/plugin-sdk/index.d.ts",
|
||||
"default": "./dist/plugin-sdk/index.js"
|
||||
},
|
||||
"./plugin-sdk/core": {
|
||||
"types": "./dist/plugin-sdk/core.d.ts",
|
||||
"default": "./dist/plugin-sdk/core.js"
|
||||
},
|
||||
"./plugin-sdk/telegram": {
|
||||
"types": "./dist/plugin-sdk/telegram.d.ts",
|
||||
"default": "./dist/plugin-sdk/telegram.js"
|
||||
},
|
||||
"./plugin-sdk/account-id": {
|
||||
"types": "./dist/plugin-sdk/account-id.d.ts",
|
||||
"default": "./dist/plugin-sdk/account-id.js"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import path from "node:path";
|
|||
//
|
||||
// Our package export map points subpath `types` at `dist/plugin-sdk/<entry>.d.ts`, so we
|
||||
// generate stable entry d.ts files that re-export the real declarations.
|
||||
const entrypoints = ["index", "account-id"] as const;
|
||||
const entrypoints = ["index", "core", "telegram", "account-id"] as const;
|
||||
for (const entry of entrypoints) {
|
||||
const out = path.join(process.cwd(), `dist/plugin-sdk/${entry}.d.ts`);
|
||||
fs.mkdirSync(path.dirname(out), { recursive: true });
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
export type { OpenClawPluginApi, OpenClawPluginService } from "../plugins/types.js";
|
||||
export type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
|
||||
export type { PluginRuntime } from "../plugins/runtime/types.js";
|
||||
|
||||
export { emptyPluginConfigSchema } from "../plugins/config-schema.js";
|
||||
|
||||
export {
|
||||
approveDevicePairing,
|
||||
listDevicePairing,
|
||||
rejectDevicePairing,
|
||||
} from "../infra/device-pairing.js";
|
||||
|
||||
export {
|
||||
runPluginCommandWithTimeout,
|
||||
type PluginCommandRunOptions,
|
||||
type PluginCommandRunResult,
|
||||
} from "./run-command.js";
|
||||
|
||||
export { resolveGatewayBindUrl } from "../shared/gateway-bind-url.js";
|
||||
export type { GatewayBindUrlResult } from "../shared/gateway-bind-url.js";
|
||||
|
||||
export { resolveTailnetHostWithRunner } from "../shared/tailscale-status.js";
|
||||
export type {
|
||||
TailscaleStatusCommandResult,
|
||||
TailscaleStatusCommandRunner,
|
||||
} from "../shared/tailscale-status.js";
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
export type { ChannelMessageActionAdapter } from "../channels/plugins/types.js";
|
||||
export type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
|
||||
export type { OpenClawConfig } from "../config/config.js";
|
||||
export type { ResolvedTelegramAccount } from "../telegram/accounts.js";
|
||||
export type { TelegramProbe } from "../telegram/probe.js";
|
||||
|
||||
export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
|
||||
|
||||
export {
|
||||
applyAccountNameToChannelSection,
|
||||
migrateBaseNameToDefaultAccount,
|
||||
} from "../channels/plugins/setup-helpers.js";
|
||||
export { buildChannelConfigSchema } from "../channels/plugins/config-schema.js";
|
||||
export {
|
||||
deleteAccountFromConfigSection,
|
||||
setAccountEnabledInConfigSection,
|
||||
} from "../channels/plugins/config-helpers.js";
|
||||
export { formatPairingApproveHint } from "../channels/plugins/helpers.js";
|
||||
export { PAIRING_APPROVED_MESSAGE } from "../channels/plugins/pairing-message.js";
|
||||
|
||||
export { getChatChannelMeta } from "../channels/registry.js";
|
||||
|
||||
export {
|
||||
listTelegramAccountIds,
|
||||
resolveDefaultTelegramAccountId,
|
||||
resolveTelegramAccount,
|
||||
} from "../telegram/accounts.js";
|
||||
export {
|
||||
listTelegramDirectoryGroupsFromConfig,
|
||||
listTelegramDirectoryPeersFromConfig,
|
||||
} from "../channels/plugins/directory-config.js";
|
||||
export {
|
||||
looksLikeTelegramTargetId,
|
||||
normalizeTelegramMessagingTarget,
|
||||
} from "../channels/plugins/normalize/telegram.js";
|
||||
export {
|
||||
parseTelegramReplyToMessageId,
|
||||
parseTelegramThreadId,
|
||||
} from "../telegram/outbound-params.js";
|
||||
export { collectTelegramStatusIssues } from "../channels/plugins/status-issues/telegram.js";
|
||||
|
||||
export {
|
||||
resolveAllowlistProviderRuntimeGroupPolicy,
|
||||
resolveDefaultGroupPolicy,
|
||||
} from "../config/runtime-group-policy.js";
|
||||
export {
|
||||
resolveTelegramGroupRequireMention,
|
||||
resolveTelegramGroupToolPolicy,
|
||||
} from "../channels/plugins/group-mentions.js";
|
||||
export { telegramOnboardingAdapter } from "../channels/plugins/onboarding/telegram.js";
|
||||
export { TelegramConfigSchema } from "../config/zod-schema.providers-core.js";
|
||||
|
||||
export { buildTokenChannelStatusSummary } from "./status-helpers.js";
|
||||
|
|
@ -974,6 +974,37 @@ describe("loadOpenClawPlugins", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("preserves runtime reflection semantics when runtime is lazily initialized", () => {
|
||||
useNoBundledPlugins();
|
||||
const plugin = writePlugin({
|
||||
id: "runtime-introspection",
|
||||
filename: "runtime-introspection.cjs",
|
||||
body: `module.exports = { id: "runtime-introspection", register(api) {
|
||||
const runtime = api.runtime ?? {};
|
||||
const keys = Object.keys(runtime);
|
||||
if (!keys.includes("channel")) {
|
||||
throw new Error("runtime channel key missing");
|
||||
}
|
||||
if (!("channel" in runtime)) {
|
||||
throw new Error("runtime channel missing from has check");
|
||||
}
|
||||
if (!Object.getOwnPropertyDescriptor(runtime, "channel")) {
|
||||
throw new Error("runtime channel descriptor missing");
|
||||
}
|
||||
} };`,
|
||||
});
|
||||
|
||||
const registry = loadRegistryFromSinglePlugin({
|
||||
plugin,
|
||||
pluginConfig: {
|
||||
allow: ["runtime-introspection"],
|
||||
},
|
||||
});
|
||||
|
||||
const record = registry.plugins.find((entry) => entry.id === "runtime-introspection");
|
||||
expect(record?.status).toBe("loaded");
|
||||
});
|
||||
|
||||
it("prefers dist plugin-sdk alias when loader runs from dist", () => {
|
||||
const { root, distFile } = createPluginSdkAliasFixture();
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import { isPathInside, safeStatSync } from "./path-safety.js";
|
|||
import { createPluginRegistry, type PluginRecord, type PluginRegistry } from "./registry.js";
|
||||
import { setActivePluginRegistry } from "./runtime.js";
|
||||
import { createPluginRuntime } from "./runtime/index.js";
|
||||
import type { PluginRuntime } from "./runtime/types.js";
|
||||
import { validateJsonSchemaValue } from "./schema-validator.js";
|
||||
import type {
|
||||
OpenClawPluginDefinition,
|
||||
|
|
@ -91,6 +92,14 @@ const resolvePluginSdkAccountIdAlias = (): string | null => {
|
|||
return resolvePluginSdkAliasFile({ srcFile: "account-id.ts", distFile: "account-id.js" });
|
||||
};
|
||||
|
||||
const resolvePluginSdkCoreAlias = (): string | null => {
|
||||
return resolvePluginSdkAliasFile({ srcFile: "core.ts", distFile: "core.js" });
|
||||
};
|
||||
|
||||
const resolvePluginSdkTelegramAlias = (): string | null => {
|
||||
return resolvePluginSdkAliasFile({ srcFile: "telegram.ts", distFile: "telegram.js" });
|
||||
};
|
||||
|
||||
export const __testing = {
|
||||
resolvePluginSdkAliasFile,
|
||||
};
|
||||
|
|
@ -393,7 +402,39 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
|||
// Clear previously registered plugin commands before reloading
|
||||
clearPluginCommands();
|
||||
|
||||
const runtime = createPluginRuntime();
|
||||
// Lazily initialize the runtime so startup paths that discover/skip plugins do
|
||||
// not eagerly load every channel runtime dependency.
|
||||
let resolvedRuntime: PluginRuntime | null = null;
|
||||
const resolveRuntime = (): PluginRuntime => {
|
||||
resolvedRuntime ??= createPluginRuntime();
|
||||
return resolvedRuntime;
|
||||
};
|
||||
const runtime = new Proxy({} as PluginRuntime, {
|
||||
get(_target, prop, receiver) {
|
||||
return Reflect.get(resolveRuntime(), prop, receiver);
|
||||
},
|
||||
set(_target, prop, value, receiver) {
|
||||
return Reflect.set(resolveRuntime(), prop, value, receiver);
|
||||
},
|
||||
has(_target, prop) {
|
||||
return Reflect.has(resolveRuntime(), prop);
|
||||
},
|
||||
ownKeys() {
|
||||
return Reflect.ownKeys(resolveRuntime() as object);
|
||||
},
|
||||
getOwnPropertyDescriptor(_target, prop) {
|
||||
return Reflect.getOwnPropertyDescriptor(resolveRuntime() as object, prop);
|
||||
},
|
||||
defineProperty(_target, prop, attributes) {
|
||||
return Reflect.defineProperty(resolveRuntime() as object, prop, attributes);
|
||||
},
|
||||
deleteProperty(_target, prop) {
|
||||
return Reflect.deleteProperty(resolveRuntime() as object, prop);
|
||||
},
|
||||
getPrototypeOf() {
|
||||
return Reflect.getPrototypeOf(resolveRuntime() as object);
|
||||
},
|
||||
});
|
||||
const { registry, createApi } = createPluginRegistry({
|
||||
logger,
|
||||
runtime,
|
||||
|
|
@ -435,17 +476,22 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
|||
}
|
||||
const pluginSdkAlias = resolvePluginSdkAlias();
|
||||
const pluginSdkAccountIdAlias = resolvePluginSdkAccountIdAlias();
|
||||
const pluginSdkCoreAlias = resolvePluginSdkCoreAlias();
|
||||
const pluginSdkTelegramAlias = resolvePluginSdkTelegramAlias();
|
||||
const aliasMap = {
|
||||
...(pluginSdkAlias ? { "openclaw/plugin-sdk": pluginSdkAlias } : {}),
|
||||
...(pluginSdkCoreAlias ? { "openclaw/plugin-sdk/core": pluginSdkCoreAlias } : {}),
|
||||
...(pluginSdkTelegramAlias ? { "openclaw/plugin-sdk/telegram": pluginSdkTelegramAlias } : {}),
|
||||
...(pluginSdkAccountIdAlias
|
||||
? { "openclaw/plugin-sdk/account-id": pluginSdkAccountIdAlias }
|
||||
: {}),
|
||||
};
|
||||
jitiLoader = createJiti(import.meta.url, {
|
||||
interopDefault: true,
|
||||
extensions: [".ts", ".tsx", ".mts", ".cts", ".mtsx", ".ctsx", ".js", ".mjs", ".cjs", ".json"],
|
||||
...(pluginSdkAlias || pluginSdkAccountIdAlias
|
||||
...(Object.keys(aliasMap).length > 0
|
||||
? {
|
||||
alias: {
|
||||
...(pluginSdkAlias ? { "openclaw/plugin-sdk": pluginSdkAlias } : {}),
|
||||
...(pluginSdkAccountIdAlias
|
||||
? { "openclaw/plugin-sdk/account-id": pluginSdkAccountIdAlias }
|
||||
: {}),
|
||||
},
|
||||
alias: aliasMap,
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
},
|
||||
"include": [
|
||||
"src/plugin-sdk/index.ts",
|
||||
"src/plugin-sdk/core.ts",
|
||||
"src/plugin-sdk/telegram.ts",
|
||||
"src/plugin-sdk/account-id.ts",
|
||||
"src/plugin-sdk/keyed-async-queue.ts",
|
||||
"src/types/**/*.d.ts"
|
||||
|
|
|
|||
|
|
@ -30,6 +30,24 @@ export default defineConfig([
|
|||
fixedExtension: false,
|
||||
platform: "node",
|
||||
},
|
||||
{
|
||||
// Keep sync lazy-runtime channel modules as concrete dist files.
|
||||
entry: {
|
||||
"channels/plugins/agent-tools/whatsapp-login":
|
||||
"src/channels/plugins/agent-tools/whatsapp-login.ts",
|
||||
"channels/plugins/actions/discord": "src/channels/plugins/actions/discord.ts",
|
||||
"channels/plugins/actions/signal": "src/channels/plugins/actions/signal.ts",
|
||||
"channels/plugins/actions/telegram": "src/channels/plugins/actions/telegram.ts",
|
||||
"telegram/audit": "src/telegram/audit.ts",
|
||||
"telegram/token": "src/telegram/token.ts",
|
||||
"line/accounts": "src/line/accounts.ts",
|
||||
"line/send": "src/line/send.ts",
|
||||
"line/template-messages": "src/line/template-messages.ts",
|
||||
},
|
||||
env,
|
||||
fixedExtension: false,
|
||||
platform: "node",
|
||||
},
|
||||
{
|
||||
entry: "src/plugin-sdk/index.ts",
|
||||
outDir: "dist/plugin-sdk",
|
||||
|
|
@ -37,6 +55,20 @@ export default defineConfig([
|
|||
fixedExtension: false,
|
||||
platform: "node",
|
||||
},
|
||||
{
|
||||
entry: "src/plugin-sdk/core.ts",
|
||||
outDir: "dist/plugin-sdk",
|
||||
env,
|
||||
fixedExtension: false,
|
||||
platform: "node",
|
||||
},
|
||||
{
|
||||
entry: "src/plugin-sdk/telegram.ts",
|
||||
outDir: "dist/plugin-sdk",
|
||||
env,
|
||||
fixedExtension: false,
|
||||
platform: "node",
|
||||
},
|
||||
{
|
||||
entry: "src/plugin-sdk/account-id.ts",
|
||||
outDir: "dist/plugin-sdk",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@ export default defineConfig({
|
|||
find: "openclaw/plugin-sdk/account-id",
|
||||
replacement: path.join(repoRoot, "src", "plugin-sdk", "account-id.ts"),
|
||||
},
|
||||
{
|
||||
find: "openclaw/plugin-sdk/core",
|
||||
replacement: path.join(repoRoot, "src", "plugin-sdk", "core.ts"),
|
||||
},
|
||||
{
|
||||
find: "openclaw/plugin-sdk/telegram",
|
||||
replacement: path.join(repoRoot, "src", "plugin-sdk", "telegram.ts"),
|
||||
},
|
||||
{
|
||||
find: "openclaw/plugin-sdk/keyed-async-queue",
|
||||
replacement: path.join(repoRoot, "src", "plugin-sdk", "keyed-async-queue.ts"),
|
||||
|
|
|
|||
Loading…
Reference in New Issue