diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256 index 17ac26b0d9b..97dc7e28c4c 100644 --- a/docs/.generated/plugin-sdk-api-baseline.sha256 +++ b/docs/.generated/plugin-sdk-api-baseline.sha256 @@ -1,2 +1,2 @@ -475eafc1885c69203ac690a0ef3c1d1ddf6879d904a6980cf5f172c29ed70868 plugin-sdk-api-baseline.json -906bb0b167b155d2f766bd13f720c8c6ed8f349769d39f57ff5070045b96ef4c plugin-sdk-api-baseline.jsonl +452cf5257df597bb0062c4478aca3afdbda6909fbaaf9ade214c27e8885935b1 plugin-sdk-api-baseline.json +30117bdbd814978ad04be54e80b72385cabb0c726de1abcbad319c9d0b3ed101 plugin-sdk-api-baseline.jsonl diff --git a/docs/plugins/sdk-overview.md b/docs/plugins/sdk-overview.md index fee244269b9..60c815a94b9 100644 --- a/docs/plugins/sdk-overview.md +++ b/docs/plugins/sdk-overview.md @@ -192,7 +192,7 @@ explicitly promotes one as public. | `plugin-sdk/process-runtime` | Process exec helpers | | `plugin-sdk/cli-runtime` | CLI formatting, wait, and version helpers | | `plugin-sdk/gateway-runtime` | Gateway client and channel-status patch helpers | - | `plugin-sdk/config-runtime` | Config load/write helpers | + | `plugin-sdk/config-runtime` | Config load/write helpers and plugin-config lookup helpers | | `plugin-sdk/telegram-command-config` | Telegram command-name/description normalization and duplicate/conflict checks, even when the bundled Telegram contract surface is unavailable | | `plugin-sdk/text-autolink-runtime` | File-reference autolink detection without the broad text-runtime barrel | | `plugin-sdk/approval-runtime` | Exec/plugin approval helpers, approval-capability builders, auth/profile helpers, native routing/runtime helpers | diff --git a/extensions/active-memory/index.ts b/extensions/active-memory/index.ts index b3d308f8ff3..e83e100f6c9 100644 --- a/extensions/active-memory/index.ts +++ b/extensions/active-memory/index.ts @@ -9,6 +9,7 @@ import { resolveAgentWorkspaceDir, } from "openclaw/plugin-sdk/agent-runtime"; import { + resolvePluginConfigObject, resolveSessionStoreEntry, updateSessionStore, type OpenClawConfig, @@ -573,14 +574,10 @@ function isActiveMemoryGloballyEnabled(cfg: OpenClawConfig): boolean { if (entry?.enabled === false) { return false; } - const pluginConfig = asRecord(entry?.config); + const pluginConfig = resolvePluginConfigObject(cfg, "active-memory"); return pluginConfig?.enabled !== false; } -function resolveActiveMemoryPluginConfigFromConfig(cfg: OpenClawConfig): unknown { - return asRecord(cfg.plugins?.entries?.["active-memory"])?.config; -} - function updateActiveMemoryGlobalEnabledInConfig( cfg: OpenClawConfig, enabled: boolean, @@ -1887,7 +1884,7 @@ export default definePluginEntry({ warnDeprecatedModelFallbackPolicy(api.pluginConfig); const refreshLiveConfigFromRuntime = () => { const livePluginConfig = - resolveActiveMemoryPluginConfigFromConfig(api.runtime.config.loadConfig()) ?? + resolvePluginConfigObject(api.runtime.config.loadConfig(), "active-memory") ?? api.pluginConfig; config = normalizePluginConfig(livePluginConfig); warnDeprecatedModelFallbackPolicy(livePluginConfig); diff --git a/extensions/memory-lancedb/index.ts b/extensions/memory-lancedb/index.ts index b69668cafcf..b9e5de343ba 100644 --- a/extensions/memory-lancedb/index.ts +++ b/extensions/memory-lancedb/index.ts @@ -10,6 +10,7 @@ import { randomUUID } from "node:crypto"; import type * as LanceDB from "@lancedb/lancedb"; import { Type } from "@sinclair/typebox"; import OpenAI from "openai"; +import { resolvePluginConfigObject } from "openclaw/plugin-sdk/config-runtime"; import { ensureGlobalUndiciEnvProxyDispatcher } from "openclaw/plugin-sdk/runtime-env"; import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import { definePluginEntry, type OpenClawPluginApi } from "./api.js"; @@ -311,9 +312,7 @@ export default definePluginEntry({ const embeddings = new Embeddings(apiKey, model, baseUrl, dimensions); const resolveCurrentHookConfig = () => { const runtimeConfig = api.runtime.config?.loadConfig?.(); - const runtimePlugins = asRecord(asRecord(runtimeConfig)?.plugins); - const runtimeEntries = asRecord(runtimePlugins?.entries); - const runtimePluginConfig = asRecord(runtimeEntries?.["memory-lancedb"])?.config; + const runtimePluginConfig = resolvePluginConfigObject(runtimeConfig, "memory-lancedb"); if (!runtimePluginConfig) { return cfg; } diff --git a/extensions/skill-workshop/index.ts b/extensions/skill-workshop/index.ts index 43d201ddd27..ef9c71896d9 100644 --- a/extensions/skill-workshop/index.ts +++ b/extensions/skill-workshop/index.ts @@ -1,3 +1,4 @@ +import { resolvePluginConfigObject } from "openclaw/plugin-sdk/config-runtime"; import { definePluginEntry, resolveDefaultAgentId } from "./api.js"; import { resolveConfig } from "./src/config.js"; import { buildWorkshopGuidance } from "./src/prompt.js"; @@ -6,12 +7,6 @@ import { createProposalFromMessages } from "./src/signals.js"; import { createSkillWorkshopTool } from "./src/tool.js"; import { applyOrStoreProposal, createStoreForContext } from "./src/workshop.js"; -function asRecord(value: unknown): Record | undefined { - return value && typeof value === "object" && !Array.isArray(value) - ? (value as Record) - : undefined; -} - export default definePluginEntry({ id: "skill-workshop", name: "Skill Workshop", @@ -24,10 +19,8 @@ export default definePluginEntry({ } const resolveCurrentConfig = () => { const runtimeConfig = api.runtime.config?.loadConfig?.(); - const runtimePlugins = asRecord(asRecord(runtimeConfig)?.plugins); - const runtimeEntries = asRecord(runtimePlugins?.entries); const runtimePluginConfig = - asRecord(runtimeEntries?.["skill-workshop"])?.config ?? api.pluginConfig; + resolvePluginConfigObject(runtimeConfig, "skill-workshop") ?? api.pluginConfig; return resolveConfig(runtimePluginConfig); }; diff --git a/extensions/thread-ownership/index.ts b/extensions/thread-ownership/index.ts index dd3964240b8..2e642580aee 100644 --- a/extensions/thread-ownership/index.ts +++ b/extensions/thread-ownership/index.ts @@ -1,3 +1,4 @@ +import { resolvePluginConfigObject } from "openclaw/plugin-sdk/config-runtime"; import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime"; import { definePluginEntry, @@ -72,20 +73,6 @@ function resolveOwnershipAgent(config: OpenClawConfig): { id: string; name: stri return { id, name }; } -function asRecord(value: unknown): Record | undefined { - return value && typeof value === "object" && !Array.isArray(value) - ? (value as Record) - : undefined; -} - -function resolveThreadOwnershipPluginConfigFromConfig( - config: OpenClawConfig, -): ThreadOwnershipConfig | undefined { - return asRecord(asRecord(config.plugins?.entries)?.["thread-ownership"])?.config as - | ThreadOwnershipConfig - | undefined; -} - export default definePluginEntry({ id: "thread-ownership", name: "Thread Ownership", @@ -94,7 +81,7 @@ export default definePluginEntry({ const resolveCurrentState = () => { const currentConfig = api.runtime.config?.loadConfig?.() ?? api.config; const pluginCfg = - resolveThreadOwnershipPluginConfigFromConfig(currentConfig) || + resolvePluginConfigObject(currentConfig, "thread-ownership") || ((api.pluginConfig ?? {}) as ThreadOwnershipConfig); return { currentConfig, diff --git a/src/plugin-sdk/config-runtime.test.ts b/src/plugin-sdk/config-runtime.test.ts new file mode 100644 index 00000000000..f65c58b4550 --- /dev/null +++ b/src/plugin-sdk/config-runtime.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from "vitest"; +import { resolvePluginConfigObject, type OpenClawConfig } from "./config-runtime.js"; + +describe("resolvePluginConfigObject", () => { + it("returns the plugin config object for a configured plugin entry", () => { + const config = { + plugins: { + entries: { + "demo-plugin": { + enabled: true, + config: { + enabled: false, + mode: "strict", + }, + }, + }, + }, + } as OpenClawConfig; + + expect(resolvePluginConfigObject(config, "demo-plugin")).toEqual({ + enabled: false, + mode: "strict", + }); + }); + + it("returns undefined for missing or non-object plugin configs", () => { + const config = { + plugins: { + entries: { + "demo-plugin": { + enabled: true, + config: "bad-shape", + }, + "array-plugin": { + enabled: true, + config: ["bad-shape"], + }, + }, + }, + } as OpenClawConfig; + + expect(resolvePluginConfigObject(config, "missing-plugin")).toBeUndefined(); + expect(resolvePluginConfigObject(config, "demo-plugin")).toBeUndefined(); + expect(resolvePluginConfigObject(config, "array-plugin")).toBeUndefined(); + expect(resolvePluginConfigObject(undefined, "demo-plugin")).toBeUndefined(); + }); +}); diff --git a/src/plugin-sdk/config-runtime.ts b/src/plugin-sdk/config-runtime.ts index c66588a943a..e0f944c20b1 100644 --- a/src/plugin-sdk/config-runtime.ts +++ b/src/plugin-sdk/config-runtime.ts @@ -12,6 +12,28 @@ export function requireRuntimeConfig(config: OpenClawConfig, context: string): O ); } +export function resolvePluginConfigObject( + config: OpenClawConfig | undefined, + pluginId: string, +): Record | undefined { + const plugins = + config?.plugins && typeof config.plugins === "object" && !Array.isArray(config.plugins) + ? (config.plugins as Record) + : undefined; + const entries = + plugins?.entries && typeof plugins.entries === "object" && !Array.isArray(plugins.entries) + ? (plugins.entries as Record) + : undefined; + const entry = entries?.[pluginId]; + if (!entry || typeof entry !== "object" || Array.isArray(entry)) { + return undefined; + } + const pluginConfig = (entry as { config?: unknown }).config; + return pluginConfig && typeof pluginConfig === "object" && !Array.isArray(pluginConfig) + ? (pluginConfig as Record) + : undefined; +} + export { resolveDefaultAgentId } from "../agents/agent-scope.js"; export { clearRuntimeConfigSnapshot,