diff --git a/extensions/amazon-bedrock/index.ts b/extensions/amazon-bedrock/index.ts index 33fa3a08d32..9158ab158d7 100644 --- a/extensions/amazon-bedrock/index.ts +++ b/extensions/amazon-bedrock/index.ts @@ -1,14 +1,13 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; const PROVIDER_ID = "amazon-bedrock"; const CLAUDE_46_MODEL_RE = /claude-(?:opus|sonnet)-4(?:\.|-)6(?:$|[-.])/i; -const amazonBedrockPlugin = { +export default definePluginEntry({ id: PROVIDER_ID, name: "Amazon Bedrock Provider", description: "Bundled Amazon Bedrock provider policy plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "Amazon Bedrock", @@ -18,6 +17,4 @@ const amazonBedrockPlugin = { CLAUDE_46_MODEL_RE.test(modelId.trim()) ? "adaptive" : undefined, }); }, -}; - -export default amazonBedrockPlugin; +}); diff --git a/extensions/anthropic/index.ts b/extensions/anthropic/index.ts index 4cad353908b..78f5bf3c17a 100644 --- a/extensions/anthropic/index.ts +++ b/extensions/anthropic/index.ts @@ -1,8 +1,7 @@ import { formatCliCommand } from "openclaw/plugin-sdk/cli-runtime"; import { parseDurationMs } from "openclaw/plugin-sdk/cli-runtime"; import { - emptyPluginConfigSchema, - type OpenClawPluginApi, + definePluginEntry, type ProviderAuthContext, type ProviderResolveDynamicModelContext, type ProviderRuntimeModel, @@ -312,12 +311,11 @@ async function runAnthropicSetupTokenNonInteractive(ctx: { }); } -const anthropicPlugin = { +export default definePluginEntry({ id: PROVIDER_ID, name: "Anthropic Provider", description: "Bundled Anthropic provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "Anthropic", @@ -399,6 +397,4 @@ const anthropicPlugin = { }); api.registerMediaUnderstandingProvider(anthropicMediaUnderstandingProvider); }, -}; - -export default anthropicPlugin; +}); diff --git a/extensions/brave/index.ts b/extensions/brave/index.ts index f23c5d4d485..1692f2db03f 100644 --- a/extensions/brave/index.ts +++ b/extensions/brave/index.ts @@ -1,16 +1,15 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createPluginBackedWebSearchProvider, getTopLevelCredentialValue, setTopLevelCredentialValue, } from "openclaw/plugin-sdk/provider-web-search"; -const bravePlugin = { +export default definePluginEntry({ id: "brave", name: "Brave Plugin", description: "Bundled Brave plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerWebSearchProvider( createPluginBackedWebSearchProvider({ id: "brave", @@ -26,6 +25,4 @@ const bravePlugin = { }), ); }, -}; - -export default bravePlugin; +}); diff --git a/extensions/byteplus/index.ts b/extensions/byteplus/index.ts index 215ac1a1705..a89cc87f531 100644 --- a/extensions/byteplus/index.ts +++ b/extensions/byteplus/index.ts @@ -1,4 +1,4 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; import { ensureModelAllowlistEntry } from "openclaw/plugin-sdk/provider-onboard"; import { buildBytePlusCodingProvider, buildBytePlusProvider } from "./provider-catalog.js"; @@ -6,12 +6,11 @@ import { buildBytePlusCodingProvider, buildBytePlusProvider } from "./provider-c const PROVIDER_ID = "byteplus"; const BYTEPLUS_DEFAULT_MODEL_REF = "byteplus-plan/ark-code-latest"; -const byteplusPlugin = { +export default definePluginEntry({ id: PROVIDER_ID, name: "BytePlus Provider", description: "Bundled BytePlus provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "BytePlus", @@ -60,6 +59,4 @@ const byteplusPlugin = { }, }); }, -}; - -export default byteplusPlugin; +}); diff --git a/extensions/cloudflare-ai-gateway/index.ts b/extensions/cloudflare-ai-gateway/index.ts index 6c3cda9d0d2..a0307d9d524 100644 --- a/extensions/cloudflare-ai-gateway/index.ts +++ b/extensions/cloudflare-ai-gateway/index.ts @@ -1,4 +1,4 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { applyAuthProfileConfig, buildApiKeyCredential, @@ -84,12 +84,11 @@ async function resolveCloudflareGatewayMetadataInteractive(ctx: { return { accountId, gatewayId }; } -const cloudflareAiGatewayPlugin = { +export default definePluginEntry({ id: PROVIDER_ID, name: "Cloudflare AI Gateway Provider", description: "Bundled Cloudflare AI Gateway provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "Cloudflare AI Gateway", @@ -252,6 +251,4 @@ const cloudflareAiGatewayPlugin = { }, }); }, -}; - -export default cloudflareAiGatewayPlugin; +}); diff --git a/extensions/copilot-proxy/index.ts b/extensions/copilot-proxy/index.ts index 2c517d9c26c..cf71710db5c 100644 --- a/extensions/copilot-proxy/index.ts +++ b/extensions/copilot-proxy/index.ts @@ -1,6 +1,5 @@ import { - emptyPluginConfigSchema, - type OpenClawPluginApi, + definePluginEntry, type ProviderAuthContext, type ProviderAuthResult, } from "openclaw/plugin-sdk/copilot-proxy"; @@ -71,12 +70,11 @@ function buildModelDefinition(modelId: string) { }; } -const copilotProxyPlugin = { +export default definePluginEntry({ id: "copilot-proxy", name: "Copilot Proxy", description: "Local Copilot Proxy (VS Code LM) provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: "copilot-proxy", label: "Copilot Proxy", @@ -157,6 +155,4 @@ const copilotProxyPlugin = { }, }); }, -}; - -export default copilotProxyPlugin; +}); diff --git a/extensions/device-pair/index.ts b/extensions/device-pair/index.ts index 7ba88842a7a..ce007756389 100644 --- a/extensions/device-pair/index.ts +++ b/extensions/device-pair/index.ts @@ -1,12 +1,13 @@ import os from "node:os"; -import type { OpenClawPluginApi } from "openclaw/plugin-sdk/device-pair"; import { approveDevicePairing, + definePluginEntry, issueDeviceBootstrapToken, listDevicePairing, resolveGatewayBindUrl, runPluginCommandWithTimeout, resolveTailnetHostWithRunner, + type OpenClawPluginApi, } from "openclaw/plugin-sdk/device-pair"; import qrcode from "qrcode-terminal"; import { @@ -325,226 +326,233 @@ function formatSetupInstructions(): string { ].join("\n"); } -export default function register(api: OpenClawPluginApi) { - registerPairingNotifierService(api); +export default definePluginEntry({ + id: "device-pair", + name: "Device Pair", + description: "QR/bootstrap pairing helpers for OpenClaw devices", + register(api: OpenClawPluginApi) { + registerPairingNotifierService(api); - api.registerCommand({ - name: "pair", - description: "Generate setup codes and approve device pairing requests.", - acceptsArgs: true, - handler: async (ctx) => { - const args = ctx.args?.trim() ?? ""; - const tokens = args.split(/\s+/).filter(Boolean); - const action = tokens[0]?.toLowerCase() ?? ""; - api.logger.info?.( - `device-pair: /pair invoked channel=${ctx.channel} sender=${ctx.senderId ?? "unknown"} action=${ - action || "new" - }`, - ); + api.registerCommand({ + name: "pair", + description: "Generate setup codes and approve device pairing requests.", + acceptsArgs: true, + handler: async (ctx) => { + const args = ctx.args?.trim() ?? ""; + const tokens = args.split(/\s+/).filter(Boolean); + const action = tokens[0]?.toLowerCase() ?? ""; + api.logger.info?.( + `device-pair: /pair invoked channel=${ctx.channel} sender=${ctx.senderId ?? "unknown"} action=${ + action || "new" + }`, + ); - if (action === "status" || action === "pending") { - const list = await listDevicePairing(); - return { text: formatPendingRequests(list.pending) }; - } - - if (action === "notify") { - const notifyAction = tokens[1]?.trim().toLowerCase() ?? "status"; - return await handleNotifyCommand({ - api, - ctx, - action: notifyAction, - }); - } - - if (action === "approve") { - const requested = tokens[1]?.trim(); - const list = await listDevicePairing(); - if (list.pending.length === 0) { - return { text: "No pending device pairing requests." }; + if (action === "status" || action === "pending") { + const list = await listDevicePairing(); + return { text: formatPendingRequests(list.pending) }; } - let pending: (typeof list.pending)[number] | undefined; - if (requested) { - if (requested.toLowerCase() === "latest") { - pending = [...list.pending].toSorted((a, b) => (b.ts ?? 0) - (a.ts ?? 0))[0]; - } else { - pending = list.pending.find((entry) => entry.requestId === requested); + if (action === "notify") { + const notifyAction = tokens[1]?.trim().toLowerCase() ?? "status"; + return await handleNotifyCommand({ + api, + ctx, + action: notifyAction, + }); + } + + if (action === "approve") { + const requested = tokens[1]?.trim(); + const list = await listDevicePairing(); + if (list.pending.length === 0) { + return { text: "No pending device pairing requests." }; } - } else if (list.pending.length === 1) { - pending = list.pending[0]; - } else { + + let pending: (typeof list.pending)[number] | undefined; + if (requested) { + if (requested.toLowerCase() === "latest") { + pending = [...list.pending].toSorted((a, b) => (b.ts ?? 0) - (a.ts ?? 0))[0]; + } else { + pending = list.pending.find((entry) => entry.requestId === requested); + } + } else if (list.pending.length === 1) { + pending = list.pending[0]; + } else { + return { + text: + `${formatPendingRequests(list.pending)}\n\n` + + "Multiple pending requests found. Approve one explicitly:\n" + + "/pair approve \n" + + "Or approve the most recent:\n" + + "/pair approve latest", + }; + } + if (!pending) { + return { text: "Pairing request not found." }; + } + const approved = await approveDevicePairing(pending.requestId); + if (!approved) { + return { text: "Pairing request not found." }; + } + const label = approved.device.displayName?.trim() || approved.device.deviceId; + const platform = approved.device.platform?.trim(); + const platformLabel = platform ? ` (${platform})` : ""; + return { text: `✅ Paired ${label}${platformLabel}.` }; + } + + const authLabelResult = resolveAuthLabel(api.config); + if (authLabelResult.error) { + return { text: `Error: ${authLabelResult.error}` }; + } + + const urlResult = await resolveGatewayUrl(api); + if (!urlResult.url) { + return { text: `Error: ${urlResult.error ?? "Gateway URL unavailable."}` }; + } + + const payload: SetupPayload = { + url: urlResult.url, + bootstrapToken: (await issueDeviceBootstrapToken()).token, + }; + + if (action === "qr") { + const setupCode = encodeSetupCode(payload); + const qrAscii = await renderQrAscii(setupCode); + const authLabel = authLabelResult.label ?? "auth"; + + const channel = ctx.channel; + const target = ctx.senderId?.trim() || ctx.from?.trim() || ctx.to?.trim() || ""; + let autoNotifyArmed = false; + + if (channel === "telegram" && target) { + try { + autoNotifyArmed = await armPairNotifyOnce({ api, ctx }); + } catch (err) { + api.logger.warn?.( + `device-pair: failed to arm one-shot pairing notify (${String( + (err as Error)?.message ?? err, + )})`, + ); + } + } + + if (channel === "telegram" && target) { + try { + const send = api.runtime?.channel?.telegram?.sendMessageTelegram; + if (send) { + await send( + target, + ["Scan this QR code with the OpenClaw iOS app:", "", "```", qrAscii, "```"].join( + "\n", + ), + { + ...(ctx.messageThreadId != null + ? { messageThreadId: ctx.messageThreadId } + : {}), + ...(ctx.accountId ? { accountId: ctx.accountId } : {}), + }, + ); + return { + text: [ + `Gateway: ${payload.url}`, + `Auth: ${authLabel}`, + "", + autoNotifyArmed + ? "After scanning, wait here for the pairing request ping." + : "After scanning, come back here and run `/pair approve` to complete pairing.", + ...(autoNotifyArmed + ? [ + "I’ll auto-ping here when the pairing request arrives, then auto-disable.", + "If the ping does not arrive, run `/pair approve latest` manually.", + ] + : []), + ].join("\n"), + }; + } + } catch (err) { + api.logger.warn?.( + `device-pair: telegram QR send failed, falling back (${String( + (err as Error)?.message ?? err, + )})`, + ); + } + } + + // Render based on channel capability + api.logger.info?.(`device-pair: QR fallback channel=${channel} target=${target}`); + const infoLines = [ + `Gateway: ${payload.url}`, + `Auth: ${authLabel}`, + "", + autoNotifyArmed + ? "After scanning, wait here for the pairing request ping." + : "After scanning, run `/pair approve` to complete pairing.", + ...(autoNotifyArmed + ? [ + "I’ll auto-ping here when the pairing request arrives, then auto-disable.", + "If the ping does not arrive, run `/pair approve latest` manually.", + ] + : []), + ]; + + // WebUI + CLI/TUI: ASCII QR return { - text: - `${formatPendingRequests(list.pending)}\n\n` + - "Multiple pending requests found. Approve one explicitly:\n" + - "/pair approve \n" + - "Or approve the most recent:\n" + - "/pair approve latest", + text: [ + "Scan this QR code with the OpenClaw iOS app:", + "", + "```", + qrAscii, + "```", + "", + ...infoLines, + ].join("\n"), }; } - if (!pending) { - return { text: "Pairing request not found." }; - } - const approved = await approveDevicePairing(pending.requestId); - if (!approved) { - return { text: "Pairing request not found." }; - } - const label = approved.device.displayName?.trim() || approved.device.deviceId; - const platform = approved.device.platform?.trim(); - const platformLabel = platform ? ` (${platform})` : ""; - return { text: `✅ Paired ${label}${platformLabel}.` }; - } - - const authLabelResult = resolveAuthLabel(api.config); - if (authLabelResult.error) { - return { text: `Error: ${authLabelResult.error}` }; - } - - const urlResult = await resolveGatewayUrl(api); - if (!urlResult.url) { - return { text: `Error: ${urlResult.error ?? "Gateway URL unavailable."}` }; - } - - const payload: SetupPayload = { - url: urlResult.url, - bootstrapToken: (await issueDeviceBootstrapToken()).token, - }; - - if (action === "qr") { - const setupCode = encodeSetupCode(payload); - const qrAscii = await renderQrAscii(setupCode); - const authLabel = authLabelResult.label ?? "auth"; const channel = ctx.channel; const target = ctx.senderId?.trim() || ctx.from?.trim() || ctx.to?.trim() || ""; - let autoNotifyArmed = false; + const authLabel = authLabelResult.label ?? "auth"; if (channel === "telegram" && target) { try { - autoNotifyArmed = await armPairNotifyOnce({ api, ctx }); - } catch (err) { - api.logger.warn?.( - `device-pair: failed to arm one-shot pairing notify (${String( - (err as Error)?.message ?? err, - )})`, + const runtimeKeys = Object.keys(api.runtime ?? {}); + const channelKeys = Object.keys(api.runtime?.channel ?? {}); + api.logger.debug?.( + `device-pair: runtime keys=${runtimeKeys.join(",") || "none"} channel keys=${ + channelKeys.join(",") || "none" + }`, ); - } - } - - if (channel === "telegram" && target) { - try { const send = api.runtime?.channel?.telegram?.sendMessageTelegram; - if (send) { - await send( - target, - ["Scan this QR code with the OpenClaw iOS app:", "", "```", qrAscii, "```"].join( - "\n", - ), - { - ...(ctx.messageThreadId != null ? { messageThreadId: ctx.messageThreadId } : {}), - ...(ctx.accountId ? { accountId: ctx.accountId } : {}), - }, + if (!send) { + throw new Error( + `telegram runtime unavailable (runtime keys: ${runtimeKeys.join(",")}; channel keys: ${channelKeys.join( + ",", + )})`, ); - return { - text: [ - `Gateway: ${payload.url}`, - `Auth: ${authLabel}`, - "", - autoNotifyArmed - ? "After scanning, wait here for the pairing request ping." - : "After scanning, come back here and run `/pair approve` to complete pairing.", - ...(autoNotifyArmed - ? [ - "I’ll auto-ping here when the pairing request arrives, then auto-disable.", - "If the ping does not arrive, run `/pair approve latest` manually.", - ] - : []), - ].join("\n"), - }; } + await send(target, formatSetupInstructions(), { + ...(ctx.messageThreadId != null ? { messageThreadId: ctx.messageThreadId } : {}), + ...(ctx.accountId ? { accountId: ctx.accountId } : {}), + }); + api.logger.info?.( + `device-pair: telegram split send ok target=${target} account=${ctx.accountId ?? "none"} thread=${ + ctx.messageThreadId ?? "none" + }`, + ); + return { text: encodeSetupCode(payload) }; } catch (err) { api.logger.warn?.( - `device-pair: telegram QR send failed, falling back (${String( + `device-pair: telegram split send failed, falling back to single message (${String( (err as Error)?.message ?? err, )})`, ); } } - // Render based on channel capability - api.logger.info?.(`device-pair: QR fallback channel=${channel} target=${target}`); - const infoLines = [ - `Gateway: ${payload.url}`, - `Auth: ${authLabel}`, - "", - autoNotifyArmed - ? "After scanning, wait here for the pairing request ping." - : "After scanning, run `/pair approve` to complete pairing.", - ...(autoNotifyArmed - ? [ - "I’ll auto-ping here when the pairing request arrives, then auto-disable.", - "If the ping does not arrive, run `/pair approve latest` manually.", - ] - : []), - ]; - - // WebUI + CLI/TUI: ASCII QR return { - text: [ - "Scan this QR code with the OpenClaw iOS app:", - "", - "```", - qrAscii, - "```", - "", - ...infoLines, - ].join("\n"), + text: formatSetupReply(payload, authLabel), }; - } - - const channel = ctx.channel; - const target = ctx.senderId?.trim() || ctx.from?.trim() || ctx.to?.trim() || ""; - const authLabel = authLabelResult.label ?? "auth"; - - if (channel === "telegram" && target) { - try { - const runtimeKeys = Object.keys(api.runtime ?? {}); - const channelKeys = Object.keys(api.runtime?.channel ?? {}); - api.logger.debug?.( - `device-pair: runtime keys=${runtimeKeys.join(",") || "none"} channel keys=${ - channelKeys.join(",") || "none" - }`, - ); - const send = api.runtime?.channel?.telegram?.sendMessageTelegram; - if (!send) { - throw new Error( - `telegram runtime unavailable (runtime keys: ${runtimeKeys.join(",")}; channel keys: ${channelKeys.join( - ",", - )})`, - ); - } - await send(target, formatSetupInstructions(), { - ...(ctx.messageThreadId != null ? { messageThreadId: ctx.messageThreadId } : {}), - ...(ctx.accountId ? { accountId: ctx.accountId } : {}), - }); - api.logger.info?.( - `device-pair: telegram split send ok target=${target} account=${ctx.accountId ?? "none"} thread=${ - ctx.messageThreadId ?? "none" - }`, - ); - return { text: encodeSetupCode(payload) }; - } catch (err) { - api.logger.warn?.( - `device-pair: telegram split send failed, falling back to single message (${String( - (err as Error)?.message ?? err, - )})`, - ); - } - } - - return { - text: formatSetupReply(payload, authLabel), - }; - }, - }); -} + }, + }); + }, +}); diff --git a/extensions/diagnostics-otel/index.ts b/extensions/diagnostics-otel/index.ts index a6ab6c133b6..15b6aee404e 100644 --- a/extensions/diagnostics-otel/index.ts +++ b/extensions/diagnostics-otel/index.ts @@ -1,15 +1,11 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk/diagnostics-otel"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/diagnostics-otel"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createDiagnosticsOtelService } from "./src/service.js"; -const plugin = { +export default definePluginEntry({ id: "diagnostics-otel", name: "Diagnostics OpenTelemetry", description: "Export diagnostics events to OpenTelemetry", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerService(createDiagnosticsOtelService()); }, -}; - -export default plugin; +}); diff --git a/extensions/elevenlabs/index.ts b/extensions/elevenlabs/index.ts index 034c56815c3..4d32eb4c532 100644 --- a/extensions/elevenlabs/index.ts +++ b/extensions/elevenlabs/index.ts @@ -1,14 +1,11 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { buildElevenLabsSpeechProvider } from "openclaw/plugin-sdk/speech"; -const elevenLabsPlugin = { +export default definePluginEntry({ id: "elevenlabs", name: "ElevenLabs Speech", description: "Bundled ElevenLabs speech provider", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerSpeechProvider(buildElevenLabsSpeechProvider()); }, -}; - -export default elevenLabsPlugin; +}); diff --git a/extensions/firecrawl/index.ts b/extensions/firecrawl/index.ts index 6b38ac6dc75..aa6e41070be 100644 --- a/extensions/firecrawl/index.ts +++ b/extensions/firecrawl/index.ts @@ -1,22 +1,15 @@ -import { - emptyPluginConfigSchema, - type AnyAgentTool, - type OpenClawPluginApi, -} from "openclaw/plugin-sdk/core"; +import { definePluginEntry, type AnyAgentTool } from "openclaw/plugin-sdk/core"; import { createFirecrawlScrapeTool } from "./src/firecrawl-scrape-tool.js"; import { createFirecrawlWebSearchProvider } from "./src/firecrawl-search-provider.js"; import { createFirecrawlSearchTool } from "./src/firecrawl-search-tool.js"; -const firecrawlPlugin = { +export default definePluginEntry({ id: "firecrawl", name: "Firecrawl Plugin", description: "Bundled Firecrawl search and scrape plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerWebSearchProvider(createFirecrawlWebSearchProvider()); api.registerTool(createFirecrawlSearchTool(api) as AnyAgentTool); api.registerTool(createFirecrawlScrapeTool(api) as AnyAgentTool); }, -}; - -export default firecrawlPlugin; +}); diff --git a/extensions/github-copilot/index.ts b/extensions/github-copilot/index.ts index 45f964c60f0..ee85f76fd61 100644 --- a/extensions/github-copilot/index.ts +++ b/extensions/github-copilot/index.ts @@ -1,6 +1,5 @@ import { - emptyPluginConfigSchema, - type OpenClawPluginApi, + definePluginEntry, type ProviderAuthContext, type ProviderResolveDynamicModelContext, type ProviderRuntimeModel, @@ -116,12 +115,11 @@ async function runGitHubCopilotAuth(ctx: ProviderAuthContext) { }; } -const githubCopilotPlugin = { +export default definePluginEntry({ id: "github-copilot", name: "GitHub Copilot Provider", description: "Bundled GitHub Copilot provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "GitHub Copilot", @@ -196,6 +194,4 @@ const githubCopilotPlugin = { await fetchCopilotUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn), }); }, -}; - -export default githubCopilotPlugin; +}); diff --git a/extensions/google/index.ts b/extensions/google/index.ts index 87872051cbd..f9268cc0aae 100644 --- a/extensions/google/index.ts +++ b/extensions/google/index.ts @@ -1,4 +1,4 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { buildGoogleImageGenerationProvider } from "openclaw/plugin-sdk/image-generation"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; import { @@ -14,12 +14,11 @@ import { registerGoogleGeminiCliProvider } from "./gemini-cli-provider.js"; import { googleMediaUnderstandingProvider } from "./media-understanding-provider.js"; import { isModernGoogleModel, resolveGoogle31ForwardCompatModel } from "./provider-models.js"; -const googlePlugin = { +export default definePluginEntry({ id: "google", name: "Google Plugin", description: "Bundled Google plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: "google", label: "Google AI Studio", @@ -70,6 +69,4 @@ const googlePlugin = { }), ); }, -}; - -export default googlePlugin; +}); diff --git a/extensions/huggingface/index.ts b/extensions/huggingface/index.ts index c0c65f0051b..6f50743f43c 100644 --- a/extensions/huggingface/index.ts +++ b/extensions/huggingface/index.ts @@ -1,16 +1,15 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; import { applyHuggingfaceConfig, HUGGINGFACE_DEFAULT_MODEL_REF } from "./onboard.js"; import { buildHuggingfaceProvider } from "./provider-catalog.js"; const PROVIDER_ID = "huggingface"; -const huggingfacePlugin = { +export default definePluginEntry({ id: PROVIDER_ID, name: "Hugging Face Provider", description: "Bundled Hugging Face provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "Hugging Face", @@ -56,6 +55,4 @@ const huggingfacePlugin = { }, }); }, -}; - -export default huggingfacePlugin; +}); diff --git a/extensions/kilocode/index.ts b/extensions/kilocode/index.ts index d875bfdb3c2..edbe5db7cfb 100644 --- a/extensions/kilocode/index.ts +++ b/extensions/kilocode/index.ts @@ -1,4 +1,4 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog"; import { @@ -10,12 +10,11 @@ import { buildKilocodeProviderWithDiscovery } from "./provider-catalog.js"; const PROVIDER_ID = "kilocode"; -const kilocodePlugin = { +export default definePluginEntry({ id: PROVIDER_ID, name: "Kilo Gateway Provider", description: "Bundled Kilo Gateway provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "Kilo Gateway", @@ -66,6 +65,4 @@ const kilocodePlugin = { isCacheTtlEligible: (ctx) => ctx.modelId.startsWith("anthropic/"), }); }, -}; - -export default kilocodePlugin; +}); diff --git a/extensions/kimi-coding/index.ts b/extensions/kimi-coding/index.ts index 03f680a5c38..579f469d595 100644 --- a/extensions/kimi-coding/index.ts +++ b/extensions/kimi-coding/index.ts @@ -1,4 +1,4 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; import { isRecord } from "openclaw/plugin-sdk/text-runtime"; import { applyKimiCodeConfig, KIMI_CODING_MODEL_REF } from "./onboard.js"; @@ -7,12 +7,11 @@ import { buildKimiCodingProvider } from "./provider-catalog.js"; const PLUGIN_ID = "kimi"; const PROVIDER_ID = "kimi"; -const kimiCodingPlugin = { +export default definePluginEntry({ id: PLUGIN_ID, name: "Kimi Provider", description: "Bundled Kimi provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "Kimi", @@ -82,6 +81,4 @@ const kimiCodingPlugin = { }, }); }, -}; - -export default kimiCodingPlugin; +}); diff --git a/extensions/llm-task/index.ts b/extensions/llm-task/index.ts index 7d258ab6a39..a3920e5806e 100644 --- a/extensions/llm-task/index.ts +++ b/extensions/llm-task/index.ts @@ -1,6 +1,15 @@ -import type { AnyAgentTool, OpenClawPluginApi } from "openclaw/plugin-sdk/llm-task"; +import { + definePluginEntry, + type AnyAgentTool, + type OpenClawPluginApi, +} from "openclaw/plugin-sdk/llm-task"; import { createLlmTaskTool } from "./src/llm-task-tool.js"; -export default function register(api: OpenClawPluginApi) { - api.registerTool(createLlmTaskTool(api) as unknown as AnyAgentTool, { optional: true }); -} +export default definePluginEntry({ + id: "llm-task", + name: "LLM Task", + description: "Optional tool for structured subtask execution", + register(api: OpenClawPluginApi) { + api.registerTool(createLlmTaskTool(api) as unknown as AnyAgentTool, { optional: true }); + }, +}); diff --git a/extensions/lobster/index.ts b/extensions/lobster/index.ts index 1d5775c4d74..c70ccc49da0 100644 --- a/extensions/lobster/index.ts +++ b/extensions/lobster/index.ts @@ -1,18 +1,24 @@ -import type { - AnyAgentTool, - OpenClawPluginApi, - OpenClawPluginToolFactory, +import { + definePluginEntry, + type AnyAgentTool, + type OpenClawPluginApi, + type OpenClawPluginToolFactory, } from "openclaw/plugin-sdk/lobster"; import { createLobsterTool } from "./src/lobster-tool.js"; -export default function register(api: OpenClawPluginApi) { - api.registerTool( - ((ctx) => { - if (ctx.sandboxed) { - return null; - } - return createLobsterTool(api) as AnyAgentTool; - }) as OpenClawPluginToolFactory, - { optional: true }, - ); -} +export default definePluginEntry({ + id: "lobster", + name: "Lobster", + description: "Optional local shell helper tools", + register(api: OpenClawPluginApi) { + api.registerTool( + ((ctx) => { + if (ctx.sandboxed) { + return null; + } + return createLobsterTool(api) as AnyAgentTool; + }) as OpenClawPluginToolFactory, + { optional: true }, + ); + }, +}); diff --git a/extensions/memory-core/index.ts b/extensions/memory-core/index.ts index 6559485e46a..54c8a5361a7 100644 --- a/extensions/memory-core/index.ts +++ b/extensions/memory-core/index.ts @@ -1,13 +1,11 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk/memory-core"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/memory-core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; -const memoryCorePlugin = { +export default definePluginEntry({ id: "memory-core", name: "Memory (Core)", description: "File-backed memory search tools and CLI", kind: "memory", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerTool( (ctx) => { const memorySearchTool = api.runtime.tools.createMemorySearchTool({ @@ -33,6 +31,4 @@ const memoryCorePlugin = { { commands: ["memory"] }, ); }, -}; - -export default memoryCorePlugin; +}); diff --git a/extensions/memory-lancedb/index.test.ts b/extensions/memory-lancedb/index.test.ts index a733c3dffb8..5dabcc9dabf 100644 --- a/extensions/memory-lancedb/index.test.ts +++ b/extensions/memory-lancedb/index.test.ts @@ -18,6 +18,18 @@ const HAS_OPENAI_KEY = Boolean(process.env.OPENAI_API_KEY); const liveEnabled = HAS_OPENAI_KEY && process.env.OPENCLAW_LIVE_TEST === "1"; const describeLive = liveEnabled ? describe : describe.skip; +type MemoryPluginTestConfig = { + embedding?: { + apiKey?: string; + model?: string; + dimensions?: number; + }; + dbPath?: string; + captureMaxChars?: number; + autoCapture?: boolean; + autoRecall?: boolean; +}; + function installTmpDirHarness(params: { prefix: string }) { let tmpDir = ""; let dbPath = ""; @@ -51,7 +63,7 @@ describe("memory plugin e2e", () => { }, dbPath: getDbPath(), ...overrides, - }); + }) as MemoryPluginTestConfig | undefined; } test("memory plugin registers and initializes correctly", async () => { @@ -89,7 +101,7 @@ describe("memory plugin e2e", () => { apiKey: "${TEST_MEMORY_API_KEY}", }, dbPath: getDbPath(), - }); + }) as MemoryPluginTestConfig | undefined; expect(config?.embedding?.apiKey).toBe("test-key-123"); diff --git a/extensions/memory-lancedb/index.ts b/extensions/memory-lancedb/index.ts index 6ae7574aaa8..b3033b118c9 100644 --- a/extensions/memory-lancedb/index.ts +++ b/extensions/memory-lancedb/index.ts @@ -10,7 +10,7 @@ import { randomUUID } from "node:crypto"; import type * as LanceDB from "@lancedb/lancedb"; import { Type } from "@sinclair/typebox"; import OpenAI from "openai"; -import type { OpenClawPluginApi } from "openclaw/plugin-sdk/memory-lancedb"; +import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/memory-lancedb"; import { DEFAULT_CAPTURE_MAX_CHARS, MEMORY_CATEGORIES, @@ -289,7 +289,7 @@ export function detectCategory(text: string): MemoryCategory { // Plugin Definition // ============================================================================ -const memoryPlugin = { +export default definePluginEntry({ id: "memory-lancedb", name: "Memory (LanceDB)", description: "LanceDB-backed long-term memory with auto-recall/capture", @@ -673,6 +673,4 @@ const memoryPlugin = { }, }); }, -}; - -export default memoryPlugin; +}); diff --git a/extensions/microsoft/index.ts b/extensions/microsoft/index.ts index db0bebbcc0b..e0e39e3a18f 100644 --- a/extensions/microsoft/index.ts +++ b/extensions/microsoft/index.ts @@ -1,14 +1,11 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { buildMicrosoftSpeechProvider } from "openclaw/plugin-sdk/speech"; -const microsoftPlugin = { +export default definePluginEntry({ id: "microsoft", name: "Microsoft Speech", description: "Bundled Microsoft speech provider", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerSpeechProvider(buildMicrosoftSpeechProvider()); }, -}; - -export default microsoftPlugin; +}); diff --git a/extensions/minimax/index.ts b/extensions/minimax/index.ts index 1ebf7382d52..d1a97cb43dc 100644 --- a/extensions/minimax/index.ts +++ b/extensions/minimax/index.ts @@ -1,7 +1,6 @@ import { buildOauthProviderAuthResult, - emptyPluginConfigSchema, - type OpenClawPluginApi, + definePluginEntry, type ProviderAuthContext, type ProviderAuthResult, type ProviderCatalogContext, @@ -159,12 +158,11 @@ function createOAuthHandler(region: MiniMaxRegion) { }; } -const minimaxPlugin = { +export default definePluginEntry({ id: API_PROVIDER_ID, name: "MiniMax", description: "Bundled MiniMax API-key and OAuth provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: API_PROVIDER_ID, label: PROVIDER_LABEL, @@ -280,6 +278,4 @@ const minimaxPlugin = { api.registerMediaUnderstandingProvider(minimaxMediaUnderstandingProvider); api.registerMediaUnderstandingProvider(minimaxPortalMediaUnderstandingProvider); }, -}; - -export default minimaxPlugin; +}); diff --git a/extensions/mistral/index.ts b/extensions/mistral/index.ts index 5a15c50a857..cfb77d3a012 100644 --- a/extensions/mistral/index.ts +++ b/extensions/mistral/index.ts @@ -1,16 +1,15 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; import { mistralMediaUnderstandingProvider } from "./media-understanding-provider.js"; import { applyMistralConfig, MISTRAL_DEFAULT_MODEL_REF } from "./onboard.js"; const PROVIDER_ID = "mistral"; -const mistralPlugin = { +export default definePluginEntry({ id: PROVIDER_ID, name: "Mistral Provider", description: "Bundled Mistral provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "Mistral", @@ -53,6 +52,4 @@ const mistralPlugin = { }); api.registerMediaUnderstandingProvider(mistralMediaUnderstandingProvider); }, -}; - -export default mistralPlugin; +}); diff --git a/extensions/modelstudio/index.ts b/extensions/modelstudio/index.ts index 20318b2a022..fc5dab4c4f8 100644 --- a/extensions/modelstudio/index.ts +++ b/extensions/modelstudio/index.ts @@ -1,4 +1,4 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog"; import { @@ -10,12 +10,11 @@ import { buildModelStudioProvider } from "./provider-catalog.js"; const PROVIDER_ID = "modelstudio"; -const modelStudioPlugin = { +export default definePluginEntry({ id: PROVIDER_ID, name: "Model Studio Provider", description: "Bundled Model Studio provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "Model Studio", @@ -89,6 +88,4 @@ const modelStudioPlugin = { }, }); }, -}; - -export default modelStudioPlugin; +}); diff --git a/extensions/moonshot/index.ts b/extensions/moonshot/index.ts index c47c4a92d41..704b841818c 100644 --- a/extensions/moonshot/index.ts +++ b/extensions/moonshot/index.ts @@ -1,4 +1,4 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog"; import { @@ -20,12 +20,11 @@ import { buildMoonshotProvider } from "./provider-catalog.js"; const PROVIDER_ID = "moonshot"; -const moonshotPlugin = { +export default definePluginEntry({ id: PROVIDER_ID, name: "Moonshot Provider", description: "Bundled Moonshot provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "Moonshot", @@ -108,6 +107,4 @@ const moonshotPlugin = { }), ); }, -}; - -export default moonshotPlugin; +}); diff --git a/extensions/nvidia/index.ts b/extensions/nvidia/index.ts index 583932bc600..a5018e63579 100644 --- a/extensions/nvidia/index.ts +++ b/extensions/nvidia/index.ts @@ -1,15 +1,14 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog"; import { buildNvidiaProvider } from "./provider-catalog.js"; const PROVIDER_ID = "nvidia"; -const nvidiaPlugin = { +export default definePluginEntry({ id: PROVIDER_ID, name: "NVIDIA Provider", description: "Bundled NVIDIA provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "NVIDIA", @@ -27,6 +26,4 @@ const nvidiaPlugin = { }, }); }, -}; - -export default nvidiaPlugin; +}); diff --git a/extensions/ollama/index.ts b/extensions/ollama/index.ts index 6f75f9b08a5..6f7ec7f2088 100644 --- a/extensions/ollama/index.ts +++ b/extensions/ollama/index.ts @@ -1,5 +1,5 @@ import { - emptyPluginConfigSchema, + definePluginEntry, type OpenClawPluginApi, type ProviderAuthContext, type ProviderAuthMethodNonInteractiveContext, @@ -15,11 +15,10 @@ async function loadProviderSetup() { return await import("openclaw/plugin-sdk/ollama-setup"); } -const ollamaPlugin = { +export default definePluginEntry({ id: "ollama", name: "Ollama Provider", description: "Bundled Ollama provider plugin", - configSchema: emptyPluginConfigSchema(), register(api: OpenClawPluginApi) { api.registerProvider({ id: PROVIDER_ID, @@ -123,6 +122,4 @@ const ollamaPlugin = { }, }); }, -}; - -export default ollamaPlugin; +}); diff --git a/extensions/open-prose/index.ts b/extensions/open-prose/index.ts index 76fa2b18f9e..540148f498c 100644 --- a/extensions/open-prose/index.ts +++ b/extensions/open-prose/index.ts @@ -1,5 +1,10 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk/open-prose"; +import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/open-prose"; -export default function register(_api: OpenClawPluginApi) { - // OpenProse is delivered via plugin-shipped skills. -} +export default definePluginEntry({ + id: "open-prose", + name: "OpenProse", + description: "Plugin-shipped prose skills bundle", + register(_api: OpenClawPluginApi) { + // OpenProse is delivered via plugin-shipped skills. + }, +}); diff --git a/extensions/openai/index.ts b/extensions/openai/index.ts index dd8bbdd615d..5664d19b82c 100644 --- a/extensions/openai/index.ts +++ b/extensions/openai/index.ts @@ -1,22 +1,19 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { buildOpenAIImageGenerationProvider } from "openclaw/plugin-sdk/image-generation"; import { buildOpenAISpeechProvider } from "openclaw/plugin-sdk/speech"; import { openaiMediaUnderstandingProvider } from "./media-understanding-provider.js"; import { buildOpenAICodexProviderPlugin } from "./openai-codex-provider.js"; import { buildOpenAIProvider } from "./openai-provider.js"; -const openAIPlugin = { +export default definePluginEntry({ id: "openai", name: "OpenAI Provider", description: "Bundled OpenAI provider plugins", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider(buildOpenAIProvider()); api.registerProvider(buildOpenAICodexProviderPlugin()); api.registerSpeechProvider(buildOpenAISpeechProvider()); api.registerMediaUnderstandingProvider(openaiMediaUnderstandingProvider); api.registerImageGenerationProvider(buildOpenAIImageGenerationProvider()); }, -}; - -export default openAIPlugin; +}); diff --git a/extensions/opencode-go/index.ts b/extensions/opencode-go/index.ts index 09319628684..8ef9b6ea0b4 100644 --- a/extensions/opencode-go/index.ts +++ b/extensions/opencode-go/index.ts @@ -1,16 +1,15 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; import { OPENCODE_GO_DEFAULT_MODEL_REF } from "openclaw/plugin-sdk/provider-models"; import { applyOpencodeGoConfig } from "./onboard.js"; const PROVIDER_ID = "opencode-go"; -const opencodeGoPlugin = { +export default definePluginEntry({ id: PROVIDER_ID, name: "OpenCode Go Provider", description: "Bundled OpenCode Go provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "OpenCode Go", @@ -53,6 +52,4 @@ const opencodeGoPlugin = { isModernModelRef: () => true, }); }, -}; - -export default opencodeGoPlugin; +}); diff --git a/extensions/opencode/index.ts b/extensions/opencode/index.ts index 4f9bbb1384a..9649ff6e83b 100644 --- a/extensions/opencode/index.ts +++ b/extensions/opencode/index.ts @@ -1,4 +1,4 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; import { OPENCODE_ZEN_DEFAULT_MODEL } from "openclaw/plugin-sdk/provider-models"; import { applyOpencodeZenConfig } from "./onboard.js"; @@ -14,12 +14,11 @@ function isModernOpencodeModel(modelId: string): boolean { return !lower.startsWith(MINIMAX_PREFIX); } -const opencodePlugin = { +export default definePluginEntry({ id: PROVIDER_ID, name: "OpenCode Zen Provider", description: "Bundled OpenCode Zen provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "OpenCode Zen", @@ -63,6 +62,4 @@ const opencodePlugin = { isModernModelRef: ({ modelId }) => isModernOpencodeModel(modelId), }); }, -}; - -export default opencodePlugin; +}); diff --git a/extensions/openrouter/index.ts b/extensions/openrouter/index.ts index b4c1d908c4f..3d20250e760 100644 --- a/extensions/openrouter/index.ts +++ b/extensions/openrouter/index.ts @@ -1,7 +1,6 @@ import type { StreamFn } from "@mariozechner/pi-agent-core"; import { - emptyPluginConfigSchema, - type OpenClawPluginApi, + definePluginEntry, type ProviderResolveDynamicModelContext, type ProviderRuntimeModel, } from "openclaw/plugin-sdk/core"; @@ -74,12 +73,11 @@ function isOpenRouterCacheTtlModel(modelId: string): boolean { return OPENROUTER_CACHE_TTL_MODEL_PREFIXES.some((prefix) => modelId.startsWith(prefix)); } -const openRouterPlugin = { +export default definePluginEntry({ id: "openrouter", name: "OpenRouter Provider", description: "Bundled OpenRouter provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "OpenRouter", @@ -151,6 +149,4 @@ const openRouterPlugin = { isCacheTtlEligible: (ctx) => isOpenRouterCacheTtlModel(ctx.modelId), }); }, -}; - -export default openRouterPlugin; +}); diff --git a/extensions/perplexity/index.ts b/extensions/perplexity/index.ts index 0fe3034a000..95ae612ed35 100644 --- a/extensions/perplexity/index.ts +++ b/extensions/perplexity/index.ts @@ -1,16 +1,15 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createPluginBackedWebSearchProvider, getScopedCredentialValue, setScopedCredentialValue, } from "openclaw/plugin-sdk/provider-web-search"; -const perplexityPlugin = { +export default definePluginEntry({ id: "perplexity", name: "Perplexity Plugin", description: "Bundled Perplexity plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerWebSearchProvider( createPluginBackedWebSearchProvider({ id: "perplexity", @@ -27,6 +26,4 @@ const perplexityPlugin = { }), ); }, -}; - -export default perplexityPlugin; +}); diff --git a/extensions/phone-control/index.test.ts b/extensions/phone-control/index.test.ts index 1eee0ff9d64..5964919e9d7 100644 --- a/extensions/phone-control/index.test.ts +++ b/extensions/phone-control/index.test.ts @@ -68,7 +68,7 @@ describe("phone-control plugin", () => { }); let command: OpenClawPluginCommandDefinition | undefined; - registerPhoneControl( + registerPhoneControl.register( createApi({ stateDir, getConfig: () => config, diff --git a/extensions/phone-control/index.ts b/extensions/phone-control/index.ts index 7b63b67b10c..88446e4fde7 100644 --- a/extensions/phone-control/index.ts +++ b/extensions/phone-control/index.ts @@ -1,6 +1,10 @@ import fs from "node:fs/promises"; import path from "node:path"; -import type { OpenClawPluginApi, OpenClawPluginService } from "openclaw/plugin-sdk/phone-control"; +import { + definePluginEntry, + type OpenClawPluginApi, + type OpenClawPluginService, +} from "openclaw/plugin-sdk/phone-control"; type ArmGroup = "camera" | "screen" | "writes" | "all"; @@ -283,139 +287,144 @@ function formatStatus(state: ArmStateFile | null): string { return `Phone control: armed (${until}).\nTemporarily allowed: ${cmdLabel}`; } -export default function register(api: OpenClawPluginApi) { - let expiryInterval: ReturnType | null = null; +export default definePluginEntry({ + id: "phone-control", + name: "Phone Control", + description: "Temporary allowlist control for phone automation commands", + register(api: OpenClawPluginApi) { + let expiryInterval: ReturnType | null = null; - const timerService: OpenClawPluginService = { - id: "phone-control-expiry", - start: async (ctx) => { - const statePath = resolveStatePath(ctx.stateDir); - const tick = async () => { - const state = await readArmState(statePath); - if (!state || state.expiresAtMs == null) { - return; - } - if (Date.now() < state.expiresAtMs) { - return; - } - await disarmNow({ - api, - stateDir: ctx.stateDir, - statePath, - reason: "expired", - }); - }; - - // Best effort; don't crash the gateway if state is corrupt. - await tick().catch(() => {}); - - expiryInterval = setInterval(() => { - tick().catch(() => {}); - }, 15_000); - expiryInterval.unref?.(); - - return; - }, - stop: async () => { - if (expiryInterval) { - clearInterval(expiryInterval); - expiryInterval = null; - } - return; - }, - }; - - api.registerService(timerService); - - api.registerCommand({ - name: "phone", - description: "Arm/disarm high-risk phone node commands (camera/screen/writes).", - acceptsArgs: true, - handler: async (ctx) => { - const args = ctx.args?.trim() ?? ""; - const tokens = args.split(/\s+/).filter(Boolean); - const action = tokens[0]?.toLowerCase() ?? ""; - - const stateDir = api.runtime.state.resolveStateDir(); - const statePath = resolveStatePath(stateDir); - - if (!action || action === "help") { - const state = await readArmState(statePath); - return { text: `${formatStatus(state)}\n\n${formatHelp()}` }; - } - - if (action === "status") { - const state = await readArmState(statePath); - return { text: formatStatus(state) }; - } - - if (action === "disarm") { - const res = await disarmNow({ - api, - stateDir, - statePath, - reason: "manual", - }); - if (!res.changed) { - return { text: "Phone control: disarmed." }; - } - const restoredLabel = res.restored.length > 0 ? res.restored.join(", ") : "none"; - const removedLabel = res.removed.length > 0 ? res.removed.join(", ") : "none"; - return { - text: `Phone control: disarmed.\nRemoved allowlist: ${removedLabel}\nRestored denylist: ${restoredLabel}`, - }; - } - - if (action === "arm") { - const group = parseGroup(tokens[1]); - if (!group) { - return { text: `Usage: /phone arm [duration]\nGroups: ${formatGroupList()}` }; - } - const durationMs = parseDurationMs(tokens[2]) ?? 10 * 60_000; - const expiresAtMs = Date.now() + durationMs; - - const commands = resolveCommandsForGroup(group); - const cfg = api.runtime.config.loadConfig(); - const allowSet = new Set(normalizeAllowList(cfg)); - const denySet = new Set(normalizeDenyList(cfg)); - - const addedToAllow: string[] = []; - const removedFromDeny: string[] = []; - for (const cmd of commands) { - if (!allowSet.has(cmd)) { - allowSet.add(cmd); - addedToAllow.push(cmd); + const timerService: OpenClawPluginService = { + id: "phone-control-expiry", + start: async (ctx) => { + const statePath = resolveStatePath(ctx.stateDir); + const tick = async () => { + const state = await readArmState(statePath); + if (!state || state.expiresAtMs == null) { + return; } - if (denySet.delete(cmd)) { - removedFromDeny.push(cmd); + if (Date.now() < state.expiresAtMs) { + return; } - } - const next = patchConfigNodeLists(cfg, { - allowCommands: uniqSorted([...allowSet]), - denyCommands: uniqSorted([...denySet]), - }); - await api.runtime.config.writeConfigFile(next); - - await writeArmState(statePath, { - version: STATE_VERSION, - armedAtMs: Date.now(), - expiresAtMs, - group, - armedCommands: uniqSorted(commands), - addedToAllow: uniqSorted(addedToAllow), - removedFromDeny: uniqSorted(removedFromDeny), - }); - - const allowedLabel = uniqSorted(commands).join(", "); - return { - text: - `Phone control: armed for ${formatDuration(durationMs)}.\n` + - `Temporarily allowed: ${allowedLabel}\n` + - `To disarm early: /phone disarm`, + await disarmNow({ + api, + stateDir: ctx.stateDir, + statePath, + reason: "expired", + }); }; - } - return { text: formatHelp() }; - }, - }); -} + // Best effort; don't crash the gateway if state is corrupt. + await tick().catch(() => {}); + + expiryInterval = setInterval(() => { + tick().catch(() => {}); + }, 15_000); + expiryInterval.unref?.(); + + return; + }, + stop: async () => { + if (expiryInterval) { + clearInterval(expiryInterval); + expiryInterval = null; + } + return; + }, + }; + + api.registerService(timerService); + + api.registerCommand({ + name: "phone", + description: "Arm/disarm high-risk phone node commands (camera/screen/writes).", + acceptsArgs: true, + handler: async (ctx) => { + const args = ctx.args?.trim() ?? ""; + const tokens = args.split(/\s+/).filter(Boolean); + const action = tokens[0]?.toLowerCase() ?? ""; + + const stateDir = api.runtime.state.resolveStateDir(); + const statePath = resolveStatePath(stateDir); + + if (!action || action === "help") { + const state = await readArmState(statePath); + return { text: `${formatStatus(state)}\n\n${formatHelp()}` }; + } + + if (action === "status") { + const state = await readArmState(statePath); + return { text: formatStatus(state) }; + } + + if (action === "disarm") { + const res = await disarmNow({ + api, + stateDir, + statePath, + reason: "manual", + }); + if (!res.changed) { + return { text: "Phone control: disarmed." }; + } + const restoredLabel = res.restored.length > 0 ? res.restored.join(", ") : "none"; + const removedLabel = res.removed.length > 0 ? res.removed.join(", ") : "none"; + return { + text: `Phone control: disarmed.\nRemoved allowlist: ${removedLabel}\nRestored denylist: ${restoredLabel}`, + }; + } + + if (action === "arm") { + const group = parseGroup(tokens[1]); + if (!group) { + return { text: `Usage: /phone arm [duration]\nGroups: ${formatGroupList()}` }; + } + const durationMs = parseDurationMs(tokens[2]) ?? 10 * 60_000; + const expiresAtMs = Date.now() + durationMs; + + const commands = resolveCommandsForGroup(group); + const cfg = api.runtime.config.loadConfig(); + const allowSet = new Set(normalizeAllowList(cfg)); + const denySet = new Set(normalizeDenyList(cfg)); + + const addedToAllow: string[] = []; + const removedFromDeny: string[] = []; + for (const cmd of commands) { + if (!allowSet.has(cmd)) { + allowSet.add(cmd); + addedToAllow.push(cmd); + } + if (denySet.delete(cmd)) { + removedFromDeny.push(cmd); + } + } + const next = patchConfigNodeLists(cfg, { + allowCommands: uniqSorted([...allowSet]), + denyCommands: uniqSorted([...denySet]), + }); + await api.runtime.config.writeConfigFile(next); + + await writeArmState(statePath, { + version: STATE_VERSION, + armedAtMs: Date.now(), + expiresAtMs, + group, + armedCommands: uniqSorted(commands), + addedToAllow: uniqSorted(addedToAllow), + removedFromDeny: uniqSorted(removedFromDeny), + }); + + const allowedLabel = uniqSorted(commands).join(", "); + return { + text: + `Phone control: armed for ${formatDuration(durationMs)}.\n` + + `Temporarily allowed: ${allowedLabel}\n` + + `To disarm early: /phone disarm`, + }; + } + + return { text: formatHelp() }; + }, + }); + }, +}); diff --git a/extensions/qianfan/index.ts b/extensions/qianfan/index.ts index 04094e1c2ca..0bb9c7760f6 100644 --- a/extensions/qianfan/index.ts +++ b/extensions/qianfan/index.ts @@ -1,4 +1,4 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog"; import { applyQianfanConfig, QIANFAN_DEFAULT_MODEL_REF } from "./onboard.js"; @@ -6,12 +6,11 @@ import { buildQianfanProvider } from "./provider-catalog.js"; const PROVIDER_ID = "qianfan"; -const qianfanPlugin = { +export default definePluginEntry({ id: PROVIDER_ID, name: "Qianfan Provider", description: "Bundled Qianfan provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "Qianfan", @@ -50,6 +49,4 @@ const qianfanPlugin = { }, }); }, -}; - -export default qianfanPlugin; +}); diff --git a/extensions/qwen-portal-auth/index.ts b/extensions/qwen-portal-auth/index.ts index 2a9538a33ab..377a4a598af 100644 --- a/extensions/qwen-portal-auth/index.ts +++ b/extensions/qwen-portal-auth/index.ts @@ -2,8 +2,7 @@ import { ensureAuthProfileStore, listProfilesForProvider } from "openclaw/plugin import { QWEN_OAUTH_MARKER } from "openclaw/plugin-sdk/agent-runtime"; import { buildOauthProviderAuthResult, - emptyPluginConfigSchema, - type OpenClawPluginApi, + definePluginEntry, type ProviderAuthContext, type ProviderCatalogContext, } from "openclaw/plugin-sdk/qwen-portal-auth"; @@ -55,12 +54,11 @@ function resolveCatalog(ctx: ProviderCatalogContext) { }; } -const qwenPortalPlugin = { +export default definePluginEntry({ id: "qwen-portal-auth", name: "Qwen OAuth", description: "OAuth flow for Qwen (free-tier) models", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: PROVIDER_LABEL, @@ -146,6 +144,4 @@ const qwenPortalPlugin = { }), }); }, -}; - -export default qwenPortalPlugin; +}); diff --git a/extensions/sglang/index.ts b/extensions/sglang/index.ts index 9918c7ee98b..eb6b302ee01 100644 --- a/extensions/sglang/index.ts +++ b/extensions/sglang/index.ts @@ -5,7 +5,7 @@ import { SGLANG_PROVIDER_LABEL, } from "openclaw/plugin-sdk/agent-runtime"; import { - emptyPluginConfigSchema, + definePluginEntry, type OpenClawPluginApi, type ProviderAuthMethodNonInteractiveContext, } from "openclaw/plugin-sdk/core"; @@ -16,11 +16,10 @@ async function loadProviderSetup() { return await import("openclaw/plugin-sdk/self-hosted-provider-setup"); } -const sglangPlugin = { +export default definePluginEntry({ id: "sglang", name: "SGLang Provider", description: "Bundled SGLang provider plugin", - configSchema: emptyPluginConfigSchema(), register(api: OpenClawPluginApi) { api.registerProvider({ id: PROVIDER_ID, @@ -87,6 +86,4 @@ const sglangPlugin = { }, }); }, -}; - -export default sglangPlugin; +}); diff --git a/extensions/synthetic/index.ts b/extensions/synthetic/index.ts index 9bdeea0b8a5..360e4124cdd 100644 --- a/extensions/synthetic/index.ts +++ b/extensions/synthetic/index.ts @@ -1,4 +1,4 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog"; import { applySyntheticConfig, SYNTHETIC_DEFAULT_MODEL_REF } from "./onboard.js"; @@ -6,12 +6,11 @@ import { buildSyntheticProvider } from "./provider-catalog.js"; const PROVIDER_ID = "synthetic"; -const syntheticPlugin = { +export default definePluginEntry({ id: PROVIDER_ID, name: "Synthetic Provider", description: "Bundled Synthetic provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "Synthetic", @@ -50,6 +49,4 @@ const syntheticPlugin = { }, }); }, -}; - -export default syntheticPlugin; +}); diff --git a/extensions/talk-voice/index.test.ts b/extensions/talk-voice/index.test.ts index 15876987554..5b246c94bf1 100644 --- a/extensions/talk-voice/index.test.ts +++ b/extensions/talk-voice/index.test.ts @@ -20,7 +20,7 @@ function createHarness(config: Record) { command = definition; }), }; - register(api as never); + register.register(api as never); if (!command) { throw new Error("talk-voice command not registered"); } diff --git a/extensions/talk-voice/index.ts b/extensions/talk-voice/index.ts index fb9e7bdb39d..5448c3425b0 100644 --- a/extensions/talk-voice/index.ts +++ b/extensions/talk-voice/index.ts @@ -1,6 +1,6 @@ import { resolveActiveTalkProviderConfig } from "openclaw/plugin-sdk/config-runtime"; import type { SpeechVoiceOption } from "openclaw/plugin-sdk/speech"; -import type { OpenClawPluginApi } from "openclaw/plugin-sdk/talk-voice"; +import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/talk-voice"; function mask(s: string, keep: number = 6): string { const trimmed = s.trim(); @@ -99,119 +99,124 @@ function asProviderBaseUrl(value: unknown): string | undefined { return trimmed || undefined; } -export default function register(api: OpenClawPluginApi) { - api.registerCommand({ - name: "voice", - nativeNames: { - discord: "talkvoice", - }, - description: "List/set Talk provider voices (affects iOS Talk playback).", - acceptsArgs: true, - handler: async (ctx) => { - const commandLabel = resolveCommandLabel(ctx.channel); - const args = ctx.args?.trim() ?? ""; - const tokens = args.split(/\s+/).filter(Boolean); - const action = (tokens[0] ?? "status").toLowerCase(); +export default definePluginEntry({ + id: "talk-voice", + name: "Talk Voice", + description: "Command helpers for managing Talk voice configuration", + register(api: OpenClawPluginApi) { + api.registerCommand({ + name: "voice", + nativeNames: { + discord: "talkvoice", + }, + description: "List/set Talk provider voices (affects iOS Talk playback).", + acceptsArgs: true, + handler: async (ctx) => { + const commandLabel = resolveCommandLabel(ctx.channel); + const args = ctx.args?.trim() ?? ""; + const tokens = args.split(/\s+/).filter(Boolean); + const action = (tokens[0] ?? "status").toLowerCase(); - const cfg = api.runtime.config.loadConfig(); - const active = resolveActiveTalkProviderConfig(cfg.talk); - if (!active) { - return { - text: - "Talk voice is not configured.\n\n" + - "Missing: talk.provider and talk.providers..\n" + - "Set it on the gateway, then retry.", - }; - } - const providerId = active.provider; - const providerLabel = resolveProviderLabel(providerId); - const apiKey = asTrimmedString(active.config.apiKey); - const baseUrl = asProviderBaseUrl(active.config.baseUrl); - - const currentVoiceId = - asTrimmedString(active.config.voiceId) || asTrimmedString(cfg.talk?.voiceId); - - if (action === "status") { - return { - text: - "Talk voice status:\n" + - `- provider: ${providerId}\n` + - `- talk.voiceId: ${currentVoiceId ? currentVoiceId : "(unset)"}\n` + - `- ${providerId}.apiKey: ${apiKey ? mask(apiKey) : "(unset)"}`, - }; - } - - if (action === "list") { - const limit = Number.parseInt(tokens[1] ?? "12", 10); - try { - const voices = await api.runtime.tts.listVoices({ - provider: providerId, - cfg, - apiKey: apiKey || undefined, - baseUrl, - }); + const cfg = api.runtime.config.loadConfig(); + const active = resolveActiveTalkProviderConfig(cfg.talk); + if (!active) { return { - text: formatVoiceList(voices, Number.isFinite(limit) ? limit : 12, providerId), + text: + "Talk voice is not configured.\n\n" + + "Missing: talk.provider and talk.providers..\n" + + "Set it on the gateway, then retry.", }; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return { text: `${providerLabel} voice list failed: ${message}` }; } - } + const providerId = active.provider; + const providerLabel = resolveProviderLabel(providerId); + const apiKey = asTrimmedString(active.config.apiKey); + const baseUrl = asProviderBaseUrl(active.config.baseUrl); - if (action === "set") { - const query = tokens.slice(1).join(" ").trim(); - if (!query) { - return { text: `Usage: ${commandLabel} set ` }; - } - let voices: SpeechVoiceOption[]; - try { - voices = await api.runtime.tts.listVoices({ - provider: providerId, - cfg, - apiKey: apiKey || undefined, - baseUrl, - }); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return { text: `${providerLabel} voice lookup failed: ${message}` }; - } - const chosen = findVoice(voices, query); - if (!chosen) { - const hint = isLikelyVoiceId(query) ? query : `"${query}"`; - return { text: `No voice found for ${hint}. Try: ${commandLabel} list` }; + const currentVoiceId = + asTrimmedString(active.config.voiceId) || asTrimmedString(cfg.talk?.voiceId); + + if (action === "status") { + return { + text: + "Talk voice status:\n" + + `- provider: ${providerId}\n` + + `- talk.voiceId: ${currentVoiceId ? currentVoiceId : "(unset)"}\n` + + `- ${providerId}.apiKey: ${apiKey ? mask(apiKey) : "(unset)"}`, + }; } - const nextConfig = { - ...cfg, - talk: { - ...cfg.talk, - provider: providerId, - providers: { - ...(cfg.talk?.providers ?? {}), - [providerId]: { - ...(cfg.talk?.providers?.[providerId] ?? {}), - voiceId: chosen.id, + if (action === "list") { + const limit = Number.parseInt(tokens[1] ?? "12", 10); + try { + const voices = await api.runtime.tts.listVoices({ + provider: providerId, + cfg, + apiKey: apiKey || undefined, + baseUrl, + }); + return { + text: formatVoiceList(voices, Number.isFinite(limit) ? limit : 12, providerId), + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { text: `${providerLabel} voice list failed: ${message}` }; + } + } + + if (action === "set") { + const query = tokens.slice(1).join(" ").trim(); + if (!query) { + return { text: `Usage: ${commandLabel} set ` }; + } + let voices: SpeechVoiceOption[]; + try { + voices = await api.runtime.tts.listVoices({ + provider: providerId, + cfg, + apiKey: apiKey || undefined, + baseUrl, + }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { text: `${providerLabel} voice lookup failed: ${message}` }; + } + const chosen = findVoice(voices, query); + if (!chosen) { + const hint = isLikelyVoiceId(query) ? query : `"${query}"`; + return { text: `No voice found for ${hint}. Try: ${commandLabel} list` }; + } + + const nextConfig = { + ...cfg, + talk: { + ...cfg.talk, + provider: providerId, + providers: { + ...(cfg.talk?.providers ?? {}), + [providerId]: { + ...(cfg.talk?.providers?.[providerId] ?? {}), + voiceId: chosen.id, + }, }, + ...(providerId === "elevenlabs" ? { voiceId: chosen.id } : {}), }, - ...(providerId === "elevenlabs" ? { voiceId: chosen.id } : {}), - }, + }; + await api.runtime.config.writeConfigFile(nextConfig); + + const name = (chosen.name ?? "").trim() || "(unnamed)"; + return { text: `✅ ${providerLabel} Talk voice set to ${name}\n${chosen.id}` }; + } + + return { + text: [ + "Voice commands:", + "", + `${commandLabel} status`, + `${commandLabel} list [limit]`, + `${commandLabel} set `, + ].join("\n"), }; - await api.runtime.config.writeConfigFile(nextConfig); - - const name = (chosen.name ?? "").trim() || "(unnamed)"; - return { text: `✅ ${providerLabel} Talk voice set to ${name}\n${chosen.id}` }; - } - - return { - text: [ - "Voice commands:", - "", - `${commandLabel} status`, - `${commandLabel} list [limit]`, - `${commandLabel} set `, - ].join("\n"), - }; - }, - }); -} + }, + }); + }, +}); diff --git a/extensions/thread-ownership/index.test.ts b/extensions/thread-ownership/index.test.ts index 3d98d8f9735..44bdf51b312 100644 --- a/extensions/thread-ownership/index.test.ts +++ b/extensions/thread-ownership/index.test.ts @@ -39,7 +39,7 @@ describe("thread-ownership plugin", () => { }); it("registers message_received and message_sending hooks", () => { - register(api as any); + register.register(api as any); expect(api.on).toHaveBeenCalledTimes(2); expect(api.on).toHaveBeenCalledWith("message_received", expect.any(Function)); @@ -48,7 +48,7 @@ describe("thread-ownership plugin", () => { describe("message_sending", () => { beforeEach(() => { - register(api as any); + register.register(api as any); }); async function sendSlackThreadMessage() { @@ -120,7 +120,7 @@ describe("thread-ownership plugin", () => { describe("message_received @-mention tracking", () => { beforeEach(() => { - register(api as any); + register.register(api as any); }); it("tracks @-mentions and skips ownership check for mentioned threads", async () => { diff --git a/extensions/thread-ownership/index.ts b/extensions/thread-ownership/index.ts index f0d2cb6291b..8e6e5c1d020 100644 --- a/extensions/thread-ownership/index.ts +++ b/extensions/thread-ownership/index.ts @@ -1,4 +1,8 @@ -import type { OpenClawConfig, OpenClawPluginApi } from "openclaw/plugin-sdk/thread-ownership"; +import { + definePluginEntry, + type OpenClawConfig, + type OpenClawPluginApi, +} from "openclaw/plugin-sdk/thread-ownership"; type ThreadOwnershipConfig = { forwarderUrl?: string; @@ -39,95 +43,79 @@ function resolveOwnershipAgent(config: OpenClawConfig): { id: string; name: stri return { id, name }; } -export default function register(api: OpenClawPluginApi) { - const pluginCfg = (api.pluginConfig ?? {}) as ThreadOwnershipConfig; - const forwarderUrl = ( - pluginCfg.forwarderUrl ?? - process.env.SLACK_FORWARDER_URL ?? - "http://slack-forwarder:8750" - ).replace(/\/$/, ""); +export default definePluginEntry({ + id: "thread-ownership", + name: "Thread Ownership", + description: "Slack thread claim coordination for multi-agent setups", + register(api: OpenClawPluginApi) { + const pluginCfg = (api.pluginConfig ?? {}) as ThreadOwnershipConfig; + const forwarderUrl = ( + pluginCfg.forwarderUrl ?? + process.env.SLACK_FORWARDER_URL ?? + "http://slack-forwarder:8750" + ).replace(/\/$/, ""); - const abTestChannels = new Set( - pluginCfg.abTestChannels ?? - process.env.THREAD_OWNERSHIP_CHANNELS?.split(",").filter(Boolean) ?? - [], - ); + const abTestChannels = new Set( + pluginCfg.abTestChannels ?? + process.env.THREAD_OWNERSHIP_CHANNELS?.split(",").filter(Boolean) ?? + [], + ); - const { id: agentId, name: agentName } = resolveOwnershipAgent(api.config); - const botUserId = process.env.SLACK_BOT_USER_ID ?? ""; + const { id: agentId, name: agentName } = resolveOwnershipAgent(api.config); + const botUserId = process.env.SLACK_BOT_USER_ID ?? ""; - // --------------------------------------------------------------------------- - // message_received: track @-mentions so the agent can reply even if it - // doesn't own the thread. - // --------------------------------------------------------------------------- - api.on("message_received", async (event, ctx) => { - if (ctx.channelId !== "slack") return; + api.on("message_received", async (event, ctx) => { + if (ctx.channelId !== "slack") return; - const text = event.content ?? ""; - const threadTs = (event.metadata?.threadTs as string) ?? ""; - const channelId = (event.metadata?.channelId as string) ?? ctx.conversationId ?? ""; + const text = event.content ?? ""; + const threadTs = (event.metadata?.threadTs as string) ?? ""; + const channelId = (event.metadata?.channelId as string) ?? ctx.conversationId ?? ""; + if (!threadTs || !channelId) return; - if (!threadTs || !channelId) return; + const mentioned = + (agentName && text.includes(`@${agentName}`)) || + (botUserId && text.includes(`<@${botUserId}>`)); + if (mentioned) { + cleanExpiredMentions(); + mentionedThreads.set(`${channelId}:${threadTs}`, Date.now()); + } + }); - // Check if this agent was @-mentioned. - const mentioned = - (agentName && text.includes(`@${agentName}`)) || - (botUserId && text.includes(`<@${botUserId}>`)); + api.on("message_sending", async (event, ctx) => { + if (ctx.channelId !== "slack") return; + + const threadTs = (event.metadata?.threadTs as string) ?? ""; + const channelId = (event.metadata?.channelId as string) ?? event.to; + if (!threadTs) return; + if (abTestChannels.size > 0 && !abTestChannels.has(channelId)) return; - if (mentioned) { cleanExpiredMentions(); - mentionedThreads.set(`${channelId}:${threadTs}`, Date.now()); - } - }); + if (mentionedThreads.has(`${channelId}:${threadTs}`)) return; - // --------------------------------------------------------------------------- - // message_sending: check thread ownership before sending to Slack. - // Returns { cancel: true } if another agent owns the thread. - // --------------------------------------------------------------------------- - api.on("message_sending", async (event, ctx) => { - if (ctx.channelId !== "slack") return; + try { + const resp = await fetch(`${forwarderUrl}/api/v1/ownership/${channelId}/${threadTs}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ agent_id: agentId }), + signal: AbortSignal.timeout(3000), + }); - const threadTs = (event.metadata?.threadTs as string) ?? ""; - const channelId = (event.metadata?.channelId as string) ?? event.to; - - // Top-level messages (no thread) are always allowed. - if (!threadTs) return; - - // Only enforce in A/B test channels (if set is empty, skip entirely). - if (abTestChannels.size > 0 && !abTestChannels.has(channelId)) return; - - // If this agent was @-mentioned in this thread recently, skip ownership check. - cleanExpiredMentions(); - if (mentionedThreads.has(`${channelId}:${threadTs}`)) return; - - // Try to claim ownership via the forwarder HTTP API. - try { - const resp = await fetch(`${forwarderUrl}/api/v1/ownership/${channelId}/${threadTs}`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ agent_id: agentId }), - signal: AbortSignal.timeout(3000), - }); - - if (resp.ok) { - // We own it (or just claimed it), proceed. - return; - } - - if (resp.status === 409) { - // Another agent owns this thread — cancel the send. - const body = (await resp.json()) as { owner?: string }; - api.logger.info?.( - `thread-ownership: cancelled send to ${channelId}:${threadTs} — owned by ${body.owner}`, + if (resp.ok) { + return; + } + if (resp.status === 409) { + const body = (await resp.json()) as { owner?: string }; + api.logger.info?.( + `thread-ownership: cancelled send to ${channelId}:${threadTs} — owned by ${body.owner}`, + ); + return { cancel: true }; + } + api.logger.warn?.(`thread-ownership: unexpected status ${resp.status}, allowing send`); + } catch (err) { + api.logger.warn?.( + `thread-ownership: ownership check failed (${String(err)}), allowing send`, ); - return { cancel: true }; } - - // Unexpected status — fail open. - api.logger.warn?.(`thread-ownership: unexpected status ${resp.status}, allowing send`); - } catch (err) { - // Network error — fail open. - api.logger.warn?.(`thread-ownership: ownership check failed (${String(err)}), allowing send`); - } - }); -} + }); + }, +}); diff --git a/extensions/together/index.ts b/extensions/together/index.ts index 01bf59338f1..d4ae42bba82 100644 --- a/extensions/together/index.ts +++ b/extensions/together/index.ts @@ -1,4 +1,4 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog"; import { applyTogetherConfig, TOGETHER_DEFAULT_MODEL_REF } from "./onboard.js"; @@ -6,12 +6,11 @@ import { buildTogetherProvider } from "./provider-catalog.js"; const PROVIDER_ID = "together"; -const togetherPlugin = { +export default definePluginEntry({ id: PROVIDER_ID, name: "Together Provider", description: "Bundled Together provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "Together", @@ -50,6 +49,4 @@ const togetherPlugin = { }, }); }, -}; - -export default togetherPlugin; +}); diff --git a/extensions/venice/index.ts b/extensions/venice/index.ts index 37d4e767db3..2565049647e 100644 --- a/extensions/venice/index.ts +++ b/extensions/venice/index.ts @@ -1,4 +1,4 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog"; import { applyVeniceConfig, VENICE_DEFAULT_MODEL_REF } from "./onboard.js"; @@ -6,12 +6,11 @@ import { buildVeniceProvider } from "./provider-catalog.js"; const PROVIDER_ID = "venice"; -const venicePlugin = { +export default definePluginEntry({ id: PROVIDER_ID, name: "Venice Provider", description: "Bundled Venice provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "Venice", @@ -56,6 +55,4 @@ const venicePlugin = { }, }); }, -}; - -export default venicePlugin; +}); diff --git a/extensions/vercel-ai-gateway/index.ts b/extensions/vercel-ai-gateway/index.ts index fc4dbae156a..ecaa6d96d33 100644 --- a/extensions/vercel-ai-gateway/index.ts +++ b/extensions/vercel-ai-gateway/index.ts @@ -1,4 +1,4 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog"; import { applyVercelAiGatewayConfig, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF } from "./onboard.js"; @@ -6,12 +6,11 @@ import { buildVercelAiGatewayProvider } from "./provider-catalog.js"; const PROVIDER_ID = "vercel-ai-gateway"; -const vercelAiGatewayPlugin = { +export default definePluginEntry({ id: PROVIDER_ID, name: "Vercel AI Gateway Provider", description: "Bundled Vercel AI Gateway provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "Vercel AI Gateway", @@ -50,6 +49,4 @@ const vercelAiGatewayPlugin = { }, }); }, -}; - -export default vercelAiGatewayPlugin; +}); diff --git a/extensions/vllm/index.ts b/extensions/vllm/index.ts index 24805e700a6..7017977861c 100644 --- a/extensions/vllm/index.ts +++ b/extensions/vllm/index.ts @@ -5,7 +5,7 @@ import { VLLM_PROVIDER_LABEL, } from "openclaw/plugin-sdk/agent-runtime"; import { - emptyPluginConfigSchema, + definePluginEntry, type OpenClawPluginApi, type ProviderAuthMethodNonInteractiveContext, } from "openclaw/plugin-sdk/core"; @@ -16,11 +16,10 @@ async function loadProviderSetup() { return await import("openclaw/plugin-sdk/self-hosted-provider-setup"); } -const vllmPlugin = { +export default definePluginEntry({ id: "vllm", name: "vLLM Provider", description: "Bundled vLLM provider plugin", - configSchema: emptyPluginConfigSchema(), register(api: OpenClawPluginApi) { api.registerProvider({ id: PROVIDER_ID, @@ -87,6 +86,4 @@ const vllmPlugin = { }, }); }, -}; - -export default vllmPlugin; +}); diff --git a/extensions/voice-call/index.ts b/extensions/voice-call/index.ts index f20e2da6674..9f976881a11 100644 --- a/extensions/voice-call/index.ts +++ b/extensions/voice-call/index.ts @@ -1,7 +1,8 @@ import { Type } from "@sinclair/typebox"; -import type { - GatewayRequestHandlerOptions, - OpenClawPluginApi, +import { + definePluginEntry, + type GatewayRequestHandlerOptions, + type OpenClawPluginApi, } from "openclaw/plugin-sdk/voice-call"; import { registerVoiceCallCli } from "./src/cli.js"; import { @@ -143,7 +144,7 @@ const VoiceCallToolSchema = Type.Union([ }), ]); -const voiceCallPlugin = { +export default definePluginEntry({ id: "voice-call", name: "Voice Call", description: "Voice-call plugin with Telnyx/Twilio/Plivo providers", @@ -560,6 +561,4 @@ const voiceCallPlugin = { }, }); }, -}; - -export default voiceCallPlugin; +}); diff --git a/extensions/volcengine/index.ts b/extensions/volcengine/index.ts index 975bcce610d..f6b4b020746 100644 --- a/extensions/volcengine/index.ts +++ b/extensions/volcengine/index.ts @@ -1,4 +1,4 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; import { ensureModelAllowlistEntry } from "openclaw/plugin-sdk/provider-onboard"; import { buildDoubaoCodingProvider, buildDoubaoProvider } from "./provider-catalog.js"; @@ -6,12 +6,11 @@ import { buildDoubaoCodingProvider, buildDoubaoProvider } from "./provider-catal const PROVIDER_ID = "volcengine"; const VOLCENGINE_DEFAULT_MODEL_REF = "volcengine-plan/ark-code-latest"; -const volcenginePlugin = { +export default definePluginEntry({ id: PROVIDER_ID, name: "Volcengine Provider", description: "Bundled Volcengine provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "Volcengine", @@ -60,6 +59,4 @@ const volcenginePlugin = { }, }); }, -}; - -export default volcenginePlugin; +}); diff --git a/extensions/xai/index.ts b/extensions/xai/index.ts index 7771575795a..485b7ec6461 100644 --- a/extensions/xai/index.ts +++ b/extensions/xai/index.ts @@ -1,4 +1,4 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; import { normalizeProviderId } from "openclaw/plugin-sdk/provider-models"; import { @@ -16,12 +16,11 @@ function matchesModernXaiModel(modelId: string): boolean { return XAI_MODERN_MODEL_PREFIXES.some((prefix) => normalized.startsWith(prefix)); } -const xaiPlugin = { +export default definePluginEntry({ id: "xai", name: "xAI Plugin", description: "Bundled xAI plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "xAI", @@ -68,6 +67,4 @@ const xaiPlugin = { }), ); }, -}; - -export default xaiPlugin; +}); diff --git a/extensions/xiaomi/index.ts b/extensions/xiaomi/index.ts index dd18127edfa..def263b1cda 100644 --- a/extensions/xiaomi/index.ts +++ b/extensions/xiaomi/index.ts @@ -1,4 +1,4 @@ -import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog"; import { PROVIDER_LABELS } from "openclaw/plugin-sdk/provider-usage"; @@ -7,12 +7,11 @@ import { buildXiaomiProvider } from "./provider-catalog.js"; const PROVIDER_ID = "xiaomi"; -const xiaomiPlugin = { +export default definePluginEntry({ id: PROVIDER_ID, name: "Xiaomi Provider", description: "Bundled Xiaomi provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "Xiaomi", @@ -62,6 +61,4 @@ const xiaomiPlugin = { }), }); }, -}; - -export default xiaomiPlugin; +}); diff --git a/extensions/zai/index.ts b/extensions/zai/index.ts index 33929645968..79ae3a9d8aa 100644 --- a/extensions/zai/index.ts +++ b/extensions/zai/index.ts @@ -1,6 +1,5 @@ import { - emptyPluginConfigSchema, - type OpenClawPluginApi, + definePluginEntry, type ProviderAuthContext, type ProviderAuthMethod, type ProviderAuthMethodNonInteractiveContext, @@ -226,12 +225,11 @@ function buildZaiApiKeyMethod(params: { }; } -const zaiPlugin = { +export default definePluginEntry({ id: PROVIDER_ID, name: "Z.AI Provider", description: "Bundled Z.AI provider plugin", - configSchema: emptyPluginConfigSchema(), - register(api: OpenClawPluginApi) { + register(api) { api.registerProvider({ id: PROVIDER_ID, label: "Z.AI", @@ -311,6 +309,4 @@ const zaiPlugin = { }); api.registerMediaUnderstandingProvider(zaiMediaUnderstandingProvider); }, -}; - -export default zaiPlugin; +}); diff --git a/src/commands/status.scan.shared.ts b/src/commands/status.scan.shared.ts index b855c85320a..6f28bcd7773 100644 --- a/src/commands/status.scan.shared.ts +++ b/src/commands/status.scan.shared.ts @@ -113,7 +113,7 @@ export async function resolveSharedMemoryStatusSnapshot(params: { purpose: "status"; }) => Promise<{ manager: { - probeVectorAvailability(): Promise; + probeVectorAvailability(): Promise; status(): MemoryProviderStatus; close?(): Promise; } | null; diff --git a/src/config/schema.shared.ts b/src/config/schema.shared.ts index 148d5b3fb86..9eb6f71e052 100644 --- a/src/config/schema.shared.ts +++ b/src/config/schema.shared.ts @@ -1,4 +1,5 @@ type JsonSchemaObject = { + type?: string | string[]; properties?: Record; additionalProperties?: JsonSchemaObject | boolean; items?: JsonSchemaObject | JsonSchemaObject[]; diff --git a/src/plugin-sdk/copilot-proxy.ts b/src/plugin-sdk/copilot-proxy.ts index 80a83010c1d..d4a4dec92bf 100644 --- a/src/plugin-sdk/copilot-proxy.ts +++ b/src/plugin-sdk/copilot-proxy.ts @@ -1,7 +1,7 @@ // Narrow plugin-sdk surface for the bundled copilot-proxy plugin. // Keep this list additive and scoped to symbols used under extensions/copilot-proxy. -export { emptyPluginConfigSchema } from "../plugins/config-schema.js"; +export { definePluginEntry } from "./core.js"; export type { OpenClawPluginApi, ProviderAuthContext, diff --git a/src/plugin-sdk/core.ts b/src/plugin-sdk/core.ts index 1cfea088601..b683ecbb945 100644 --- a/src/plugin-sdk/core.ts +++ b/src/plugin-sdk/core.ts @@ -5,6 +5,7 @@ import type { OpenClawPluginApi, OpenClawPluginCommandDefinition, OpenClawPluginConfigSchema, + OpenClawPluginDefinition, PluginInteractiveTelegramHandlerContext, } from "../plugins/types.js"; @@ -42,6 +43,7 @@ export type { ProviderAuthMethod, ProviderAuthResult, OpenClawPluginCommandDefinition, + OpenClawPluginDefinition, PluginInteractiveTelegramHandlerContext, } from "../plugins/types.js"; export type { OpenClawConfig } from "../config/config.js"; @@ -88,11 +90,53 @@ type DefineChannelPluginEntryOptions OpenClawPluginConfigSchema; + configSchema?: DefinePluginEntryOptions["configSchema"]; setRuntime?: (runtime: PluginRuntime) => void; registerFull?: (api: OpenClawPluginApi) => void; }; +type DefinePluginEntryOptions = { + id: string; + name: string; + description: string; + kind?: OpenClawPluginDefinition["kind"]; + configSchema?: OpenClawPluginConfigSchema | (() => OpenClawPluginConfigSchema); + register: (api: OpenClawPluginApi) => void; +}; + +type DefinedPluginEntry = { + id: string; + name: string; + description: string; + configSchema: OpenClawPluginConfigSchema; + register: NonNullable; +} & Pick; + +function resolvePluginConfigSchema( + configSchema: DefinePluginEntryOptions["configSchema"] = emptyPluginConfigSchema, +): OpenClawPluginConfigSchema { + return typeof configSchema === "function" ? configSchema() : configSchema; +} + +// Shared generic plugin-entry boilerplate for bundled and third-party plugins. +export function definePluginEntry({ + id, + name, + description, + kind, + configSchema = emptyPluginConfigSchema, + register, +}: DefinePluginEntryOptions): DefinedPluginEntry { + return { + id, + name, + description, + ...(kind ? { kind } : {}), + configSchema: resolvePluginConfigSchema(configSchema), + register, + }; +} + // Shared channel-plugin entry boilerplate for bundled and third-party channels. export function defineChannelPluginEntry({ id, @@ -103,11 +147,11 @@ export function defineChannelPluginEntry({ setRuntime, registerFull, }: DefineChannelPluginEntryOptions) { - return { + return definePluginEntry({ id, name, description, - configSchema: configSchema(), + configSchema, register(api: OpenClawPluginApi) { setRuntime?.(api.runtime); api.registerChannel({ plugin }); @@ -116,7 +160,7 @@ export function defineChannelPluginEntry({ } registerFull?.(api); }, - }; + }); } // Shared setup-entry shape so bundled channels do not duplicate `{ plugin }`. diff --git a/src/plugin-sdk/device-pair.ts b/src/plugin-sdk/device-pair.ts index 5828ad0535f..a87e1eea8f1 100644 --- a/src/plugin-sdk/device-pair.ts +++ b/src/plugin-sdk/device-pair.ts @@ -1,6 +1,7 @@ // Narrow plugin-sdk surface for the bundled device-pair plugin. // Keep this list additive and scoped to symbols used under extensions/device-pair. +export { definePluginEntry } from "./core.js"; export { approveDevicePairing, listDevicePairing } from "../infra/device-pairing.js"; export { issueDeviceBootstrapToken } from "../infra/device-bootstrap.js"; export type { OpenClawPluginApi } from "../plugins/types.js"; diff --git a/src/plugin-sdk/llm-task.ts b/src/plugin-sdk/llm-task.ts index c69e82f36f7..b93a3197d26 100644 --- a/src/plugin-sdk/llm-task.ts +++ b/src/plugin-sdk/llm-task.ts @@ -1,6 +1,7 @@ // Narrow plugin-sdk surface for the bundled llm-task plugin. // Keep this list additive and scoped to symbols used under extensions/llm-task. +export { definePluginEntry } from "./core.js"; export { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js"; export { formatThinkingLevels, diff --git a/src/plugin-sdk/lobster.ts b/src/plugin-sdk/lobster.ts index 436acdf4d45..968fcf2cae1 100644 --- a/src/plugin-sdk/lobster.ts +++ b/src/plugin-sdk/lobster.ts @@ -1,6 +1,7 @@ // Narrow plugin-sdk surface for the bundled lobster plugin. // Keep this list additive and scoped to symbols used under extensions/lobster. +export { definePluginEntry } from "./core.js"; export { applyWindowsSpawnProgramPolicy, materializeWindowsSpawnProgram, diff --git a/src/plugin-sdk/memory-lancedb.ts b/src/plugin-sdk/memory-lancedb.ts index 840ed95982c..23d3e2619c8 100644 --- a/src/plugin-sdk/memory-lancedb.ts +++ b/src/plugin-sdk/memory-lancedb.ts @@ -1,4 +1,5 @@ // Narrow plugin-sdk surface for the bundled memory-lancedb plugin. // Keep this list additive and scoped to symbols used under extensions/memory-lancedb. +export { definePluginEntry } from "./core.js"; export type { OpenClawPluginApi } from "../plugins/types.js"; diff --git a/src/plugin-sdk/minimax-portal-auth.ts b/src/plugin-sdk/minimax-portal-auth.ts index 07aefa0aafa..a8dad415488 100644 --- a/src/plugin-sdk/minimax-portal-auth.ts +++ b/src/plugin-sdk/minimax-portal-auth.ts @@ -1,7 +1,7 @@ // Narrow plugin-sdk surface for MiniMax OAuth helpers used by the bundled minimax plugin. // Keep this list additive and scoped to MiniMax OAuth support code. -export { emptyPluginConfigSchema } from "../plugins/config-schema.js"; +export { definePluginEntry } from "./core.js"; export { buildOauthProviderAuthResult } from "./provider-auth-result.js"; export type { OpenClawPluginApi, diff --git a/src/plugin-sdk/open-prose.ts b/src/plugin-sdk/open-prose.ts index 1973404f2a8..049370ed986 100644 --- a/src/plugin-sdk/open-prose.ts +++ b/src/plugin-sdk/open-prose.ts @@ -1,4 +1,5 @@ // Narrow plugin-sdk surface for the bundled open-prose plugin. // Keep this list additive and scoped to symbols used under extensions/open-prose. +export { definePluginEntry } from "./core.js"; export type { OpenClawPluginApi } from "../plugins/types.js"; diff --git a/src/plugin-sdk/phone-control.ts b/src/plugin-sdk/phone-control.ts index 394ff9c88ee..c116eba1076 100644 --- a/src/plugin-sdk/phone-control.ts +++ b/src/plugin-sdk/phone-control.ts @@ -1,6 +1,7 @@ // Narrow plugin-sdk surface for the bundled phone-control plugin. // Keep this list additive and scoped to symbols used under extensions/phone-control. +export { definePluginEntry } from "./core.js"; export type { OpenClawPluginApi, OpenClawPluginCommandDefinition, diff --git a/src/plugin-sdk/qwen-portal-auth.ts b/src/plugin-sdk/qwen-portal-auth.ts index f6cde98b90f..adc61259a09 100644 --- a/src/plugin-sdk/qwen-portal-auth.ts +++ b/src/plugin-sdk/qwen-portal-auth.ts @@ -1,7 +1,7 @@ // Narrow plugin-sdk surface for the bundled qwen-portal-auth plugin. // Keep this list additive and scoped to symbols used under extensions/qwen-portal-auth. -export { emptyPluginConfigSchema } from "../plugins/config-schema.js"; +export { definePluginEntry } from "./core.js"; export { buildOauthProviderAuthResult } from "./provider-auth-result.js"; export type { OpenClawPluginApi, diff --git a/src/plugin-sdk/subpaths.test.ts b/src/plugin-sdk/subpaths.test.ts index 156f7d9b81f..a3cc2b3ba1f 100644 --- a/src/plugin-sdk/subpaths.test.ts +++ b/src/plugin-sdk/subpaths.test.ts @@ -49,6 +49,7 @@ describe("plugin-sdk subpath exports", () => { it("keeps core focused on generic shared exports", () => { expect(typeof coreSdk.emptyPluginConfigSchema).toBe("function"); + expect(typeof coreSdk.definePluginEntry).toBe("function"); expect(typeof coreSdk.defineChannelPluginEntry).toBe("function"); expect(typeof coreSdk.defineSetupPluginEntry).toBe("function"); expect("runPassiveAccountLifecycle" in asExports(coreSdk)).toBe(false); diff --git a/src/plugin-sdk/talk-voice.ts b/src/plugin-sdk/talk-voice.ts index 3ee313ec42f..e89f210af62 100644 --- a/src/plugin-sdk/talk-voice.ts +++ b/src/plugin-sdk/talk-voice.ts @@ -1,4 +1,5 @@ // Narrow plugin-sdk surface for the bundled talk-voice plugin. // Keep this list additive and scoped to symbols used under extensions/talk-voice. +export { definePluginEntry } from "./core.js"; export type { OpenClawPluginApi } from "../plugins/types.js"; diff --git a/src/plugin-sdk/thread-ownership.ts b/src/plugin-sdk/thread-ownership.ts index 48d72fa5d35..ea8ad079a8c 100644 --- a/src/plugin-sdk/thread-ownership.ts +++ b/src/plugin-sdk/thread-ownership.ts @@ -1,5 +1,6 @@ // Narrow plugin-sdk surface for the bundled thread-ownership plugin. // Keep this list additive and scoped to symbols used under extensions/thread-ownership. +export { definePluginEntry } from "./core.js"; export type { OpenClawConfig } from "../config/config.js"; export type { OpenClawPluginApi } from "../plugins/types.js"; diff --git a/src/plugin-sdk/voice-call.ts b/src/plugin-sdk/voice-call.ts index 7dea0885862..b3f1a889f78 100644 --- a/src/plugin-sdk/voice-call.ts +++ b/src/plugin-sdk/voice-call.ts @@ -1,6 +1,7 @@ // Narrow plugin-sdk surface for the bundled voice-call plugin. // Keep this list additive and scoped to symbols used under extensions/voice-call. +export { definePluginEntry } from "./core.js"; export { TtsAutoSchema, TtsConfigSchema, diff --git a/src/plugins/voice-call.plugin.test.ts b/src/plugins/voice-call.plugin.test.ts index 0ca6106d1a9..6a018c27b42 100644 --- a/src/plugins/voice-call.plugin.test.ts +++ b/src/plugins/voice-call.plugin.test.ts @@ -45,7 +45,7 @@ type RegisterCliContext = { function setup(config: Record): Registered { const methods = new Map(); const tools: unknown[] = []; - plugin.register({ + void plugin.register({ id: "voice-call", name: "Voice Call", description: "test",