From 0141471dd5a7b57389911d50924fd86129557cb0 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 27 Apr 2026 13:07:02 +0100 Subject: [PATCH] refactor: move shared helpers off reserved sdk seams --- .../.generated/plugin-sdk-api-baseline.sha256 | 4 +- docs/plugins/sdk-subpaths.md | 8 +-- extensions/bluebubbles/src/monitor.ts | 2 +- .../src/monitor-transport-runtime-api.ts | 2 +- extensions/google-meet/index.ts | 6 +-- .../google-meet/src/transports/chrome.ts | 2 +- extensions/matrix/src/cli.ts | 2 +- extensions/mattermost/runtime-api.ts | 2 +- .../mattermost/src/mattermost/interactions.ts | 2 +- .../src/mattermost/monitor-helpers.ts | 2 +- .../mattermost/src/mattermost/slash-http.ts | 2 +- .../qa-lab/src/gateway-rpc-client.test.ts | 2 +- extensions/qa-lab/src/gateway-rpc-client.ts | 2 +- extensions/qa-lab/src/runtime-api.ts | 2 +- extensions/qa-lab/src/suite-runtime-flow.ts | 6 +-- extensions/synology-chat/src/security.ts | 2 +- extensions/telegram/src/webhook.ts | 2 +- extensions/tlon/runtime-api.ts | 2 +- extensions/tlon/src/urbit/auth.ssrf.test.ts | 2 +- extensions/voice-call/src/providers/twilio.ts | 2 +- extensions/voice-call/src/webhook-security.ts | 2 +- extensions/webhooks/src/http.ts | 2 +- extensions/zalo/src/monitor.webhook.ts | 2 +- extensions/zalouser/runtime-api.ts | 2 +- extensions/zalouser/src/qr-temp-file.ts | 2 +- src/plugin-sdk/gateway-runtime.ts | 3 ++ src/plugin-sdk/security-runtime.ts | 1 + src/plugin-sdk/ssrf-runtime.ts | 1 + src/plugin-sdk/webhook-ingress.ts | 1 + ...in-sdk-package-contract-guardrails.test.ts | 51 +++++++++++++++++++ 30 files changed, 89 insertions(+), 34 deletions(-) diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256 index 98e90d0c9a0..00cac5fc495 100644 --- a/docs/.generated/plugin-sdk-api-baseline.sha256 +++ b/docs/.generated/plugin-sdk-api-baseline.sha256 @@ -1,2 +1,2 @@ -74344f185b3149695443bf8815c9dd784daf9c0b8118ecc54129dc57899e9564 plugin-sdk-api-baseline.json -7b84c2f1e5743dac9c764fdee6d3b23e64553516c409f4a24f009a36c40d64e8 plugin-sdk-api-baseline.jsonl +491267e919c6bf426f673a9066e703811c7779a32de87edd0ce493147fd4438e plugin-sdk-api-baseline.json +590d21aeb520f34b5bf23abb7b17602b204f170547c772d60b604bb34a3940bb plugin-sdk-api-baseline.jsonl diff --git a/docs/plugins/sdk-subpaths.md b/docs/plugins/sdk-subpaths.md index 5f1e871b505..9e42d9a4f5a 100644 --- a/docs/plugins/sdk-subpaths.md +++ b/docs/plugins/sdk-subpaths.md @@ -138,12 +138,12 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview) | `plugin-sdk/allow-from` | `formatAllowFromLowercase` | | `plugin-sdk/channel-secret-runtime` | Narrow secret-contract collection helpers for channel/plugin secret surfaces | | `plugin-sdk/secret-ref-runtime` | Narrow `coerceSecretRef` and SecretRef typing helpers for secret-contract/config parsing | - | `plugin-sdk/security-runtime` | Shared trust, DM gating, external-content, and secret-collection helpers | + | `plugin-sdk/security-runtime` | Shared trust, DM gating, external-content, constant-time secret comparison, and secret-collection helpers | | `plugin-sdk/ssrf-policy` | Host allowlist and private-network SSRF policy helpers | | `plugin-sdk/ssrf-dispatcher` | Narrow pinned-dispatcher helpers without the broad infra runtime surface | - | `plugin-sdk/ssrf-runtime` | Pinned-dispatcher, SSRF-guarded fetch, and SSRF policy helpers | + | `plugin-sdk/ssrf-runtime` | Pinned-dispatcher, SSRF-guarded fetch, SSRF error, and SSRF policy helpers | | `plugin-sdk/secret-input` | Secret input parsing helpers | - | `plugin-sdk/webhook-ingress` | Webhook request/target helpers | + | `plugin-sdk/webhook-ingress` | Webhook request/target helpers and raw websocket/body coercion | | `plugin-sdk/webhook-request-guards` | Request body size/timeout helpers | @@ -160,7 +160,7 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview) | `plugin-sdk/lazy-runtime` | Lazy runtime import/binding helpers such as `createLazyRuntimeModule`, `createLazyRuntimeMethod`, and `createLazyRuntimeSurface` | | `plugin-sdk/process-runtime` | Process exec helpers | | `plugin-sdk/cli-runtime` | CLI formatting, wait, version, argument-invocation, and lazy command-group helpers | - | `plugin-sdk/gateway-runtime` | Gateway client and channel-status patch helpers | + | `plugin-sdk/gateway-runtime` | Gateway client, gateway CLI RPC, gateway protocol errors, and channel-status patch helpers | | `plugin-sdk/config-runtime` | Config load/write helpers and plugin-config lookup helpers | | `plugin-sdk/telegram-command-config` | Telegram command-name/description normalization and duplicate/conflict checks, even when the bundled Telegram contract surface is unavailable | | `plugin-sdk/text-autolink-runtime` | File-reference autolink detection without the broad text-runtime barrel | diff --git a/extensions/bluebubbles/src/monitor.ts b/extensions/bluebubbles/src/monitor.ts index 6fb5a166179..0ba23f7f2f6 100644 --- a/extensions/bluebubbles/src/monitor.ts +++ b/extensions/bluebubbles/src/monitor.ts @@ -1,6 +1,6 @@ import type { IncomingMessage, ServerResponse } from "node:http"; -import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime"; import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; +import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime"; import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime"; import { resolveBlueBubblesEffectiveAllowPrivateNetwork } from "./accounts.js"; import { createBlueBubblesDebounceRegistry } from "./monitor-debounce.js"; diff --git a/extensions/feishu/src/monitor-transport-runtime-api.ts b/extensions/feishu/src/monitor-transport-runtime-api.ts index f1262af4f45..b3ba5569778 100644 --- a/extensions/feishu/src/monitor-transport-runtime-api.ts +++ b/extensions/feishu/src/monitor-transport-runtime-api.ts @@ -1,5 +1,5 @@ export type { RuntimeEnv } from "../runtime-api.js"; -export { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime"; +export { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime"; export { applyBasicWebhookRequestGuards } from "openclaw/plugin-sdk/webhook-ingress"; export { installRequestBodyLimitGuard, diff --git a/extensions/google-meet/index.ts b/extensions/google-meet/index.ts index 4198df386e1..d47d20dc615 100644 --- a/extensions/google-meet/index.ts +++ b/extensions/google-meet/index.ts @@ -1,10 +1,10 @@ +import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; import { callGatewayFromCli, ErrorCodes, errorShape, -} from "openclaw/plugin-sdk/browser-node-runtime"; -import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; -import type { GatewayRequestHandlerOptions } from "openclaw/plugin-sdk/gateway-runtime"; + type GatewayRequestHandlerOptions, +} from "openclaw/plugin-sdk/gateway-runtime"; import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"; import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime"; import { Type } from "typebox"; diff --git a/extensions/google-meet/src/transports/chrome.ts b/extensions/google-meet/src/transports/chrome.ts index 47d36d3bfe1..bcc3b0d1a00 100644 --- a/extensions/google-meet/src/transports/chrome.ts +++ b/extensions/google-meet/src/transports/chrome.ts @@ -1,5 +1,5 @@ -import { callGatewayFromCli } from "openclaw/plugin-sdk/browser-node-runtime"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; +import { callGatewayFromCli } from "openclaw/plugin-sdk/gateway-runtime"; import type { PluginRuntime } from "openclaw/plugin-sdk/plugin-runtime"; import type { RuntimeLogger } from "openclaw/plugin-sdk/plugin-runtime"; import type { GoogleMeetConfig } from "../config.js"; diff --git a/extensions/matrix/src/cli.ts b/extensions/matrix/src/cli.ts index 84343db923e..d4492973bbb 100644 --- a/extensions/matrix/src/cli.ts +++ b/extensions/matrix/src/cli.ts @@ -1,6 +1,5 @@ import type { Command } from "commander"; import { normalizeAccountId } from "openclaw/plugin-sdk/account-id"; -import { formatZonedTimestamp } from "openclaw/plugin-sdk/matrix-runtime-shared"; import type { ChannelSetupInput } from "openclaw/plugin-sdk/setup"; import { resolveMatrixAccount, resolveMatrixAccountConfig } from "./matrix/accounts.js"; import { listMatrixOwnDevices, pruneMatrixStaleGatewayDevices } from "./matrix/actions/devices.js"; @@ -30,6 +29,7 @@ import { isOpenClawManagedMatrixDevice } from "./matrix/device-health.js"; import type { MatrixDirectRoomCandidate } from "./matrix/direct-management.js"; import { formatMatrixErrorMessage } from "./matrix/errors.js"; import { applyMatrixProfileUpdate, type MatrixProfileUpdateResult } from "./profile-update.js"; +import { formatZonedTimestamp } from "./runtime-api.js"; import { getMatrixRuntime } from "./runtime.js"; import { matrixSetupAdapter } from "./setup-core.js"; import type { CoreConfig } from "./types.js"; diff --git a/extensions/mattermost/runtime-api.ts b/extensions/mattermost/runtime-api.ts index 0bac0c5f34a..2a2a531bfea 100644 --- a/extensions/mattermost/runtime-api.ts +++ b/extensions/mattermost/runtime-api.ts @@ -61,7 +61,7 @@ export { evaluateSenderGroupAccessForPolicy } from "openclaw/plugin-sdk/group-ac export { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline"; export { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback"; export { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media"; -export { rawDataToString } from "openclaw/plugin-sdk/browser-node-runtime"; +export { rawDataToString } from "openclaw/plugin-sdk/webhook-ingress"; export { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking"; export { DEFAULT_GROUP_HISTORY_LIMIT, diff --git a/extensions/mattermost/src/mattermost/interactions.ts b/extensions/mattermost/src/mattermost/interactions.ts index f64a504ef95..4c5f77af65d 100644 --- a/extensions/mattermost/src/mattermost/interactions.ts +++ b/extensions/mattermost/src/mattermost/interactions.ts @@ -1,6 +1,6 @@ import { createHmac } from "node:crypto"; import type { IncomingMessage, ServerResponse } from "node:http"; -import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime"; +import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime"; import { normalizeOptionalString, normalizeStringifiedOptionalString, diff --git a/extensions/mattermost/src/mattermost/monitor-helpers.ts b/extensions/mattermost/src/mattermost/monitor-helpers.ts index f633148413c..54603e8e8ca 100644 --- a/extensions/mattermost/src/mattermost/monitor-helpers.ts +++ b/extensions/mattermost/src/mattermost/monitor-helpers.ts @@ -1,4 +1,3 @@ -import { rawDataToString } from "openclaw/plugin-sdk/browser-node-runtime"; import { formatInboundFromLabel as formatInboundFromLabelShared } from "openclaw/plugin-sdk/channel-inbound"; import { createDedupeCache, type OpenClawConfig } from "openclaw/plugin-sdk/core"; import { resolveThreadSessionKeys as resolveThreadSessionKeysShared } from "openclaw/plugin-sdk/routing"; @@ -6,6 +5,7 @@ import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, } from "openclaw/plugin-sdk/text-runtime"; +import { rawDataToString } from "openclaw/plugin-sdk/webhook-ingress"; export { createDedupeCache, rawDataToString }; diff --git a/extensions/mattermost/src/mattermost/slash-http.ts b/extensions/mattermost/src/mattermost/slash-http.ts index 1b7e144b9fd..186390b1199 100644 --- a/extensions/mattermost/src/mattermost/slash-http.ts +++ b/extensions/mattermost/src/mattermost/slash-http.ts @@ -6,7 +6,7 @@ */ import type { IncomingMessage, ServerResponse } from "node:http"; -import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime"; +import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime"; import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime"; import type { ResolvedMattermostAccount } from "../mattermost/accounts.js"; import { getMattermostRuntime } from "../runtime.js"; diff --git a/extensions/qa-lab/src/gateway-rpc-client.test.ts b/extensions/qa-lab/src/gateway-rpc-client.test.ts index eab529a961f..a7320bd2ac9 100644 --- a/extensions/qa-lab/src/gateway-rpc-client.test.ts +++ b/extensions/qa-lab/src/gateway-rpc-client.test.ts @@ -10,7 +10,7 @@ const gatewayRpcMock = vi.hoisted(() => { }; }); -vi.mock("openclaw/plugin-sdk/browser-node-runtime", () => ({ +vi.mock("openclaw/plugin-sdk/gateway-runtime", () => ({ callGatewayFromCli: gatewayRpcMock.callGatewayFromCli, })); diff --git a/extensions/qa-lab/src/gateway-rpc-client.ts b/extensions/qa-lab/src/gateway-rpc-client.ts index e4b16d95c5e..0a2583b4395 100644 --- a/extensions/qa-lab/src/gateway-rpc-client.ts +++ b/extensions/qa-lab/src/gateway-rpc-client.ts @@ -1,5 +1,5 @@ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; -import { callGatewayFromCli } from "openclaw/plugin-sdk/browser-node-runtime"; +import { callGatewayFromCli } from "openclaw/plugin-sdk/gateway-runtime"; import { formatQaGatewayLogsForError } from "./gateway-log-redaction.js"; type QaGatewayRpcRequestOptions = { diff --git a/extensions/qa-lab/src/runtime-api.ts b/extensions/qa-lab/src/runtime-api.ts index 602832e034c..a02cf412a79 100644 --- a/extensions/qa-lab/src/runtime-api.ts +++ b/extensions/qa-lab/src/runtime-api.ts @@ -1,7 +1,7 @@ export type { Command } from "commander"; export type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; export { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; -export { callGatewayFromCli } from "openclaw/plugin-sdk/browser-node-runtime"; +export { callGatewayFromCli } from "openclaw/plugin-sdk/gateway-runtime"; export type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store"; export { defaultQaRuntimeModelForMode } from "./model-selection.runtime.js"; export { diff --git a/extensions/qa-lab/src/suite-runtime-flow.ts b/extensions/qa-lab/src/suite-runtime-flow.ts index 50969f1b131..fee41e537e0 100644 --- a/extensions/qa-lab/src/suite-runtime-flow.ts +++ b/extensions/qa-lab/src/suite-runtime-flow.ts @@ -2,10 +2,8 @@ import { randomUUID } from "node:crypto"; import fs from "node:fs/promises"; import path from "node:path"; import { setTimeout as sleep } from "node:timers/promises"; -import { - formatMemoryDreamingDay, - resolveSessionTranscriptsDirForAgent, -} from "openclaw/plugin-sdk/memory-core"; +import { resolveSessionTranscriptsDirForAgent } from "openclaw/plugin-sdk/memory-host-core"; +import { formatMemoryDreamingDay } from "openclaw/plugin-sdk/memory-host-status"; import { buildAgentSessionKey } from "openclaw/plugin-sdk/routing"; import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import { diff --git a/extensions/synology-chat/src/security.ts b/extensions/synology-chat/src/security.ts index 050375de2ef..61a4429486b 100644 --- a/extensions/synology-chat/src/security.ts +++ b/extensions/synology-chat/src/security.ts @@ -2,7 +2,7 @@ * Security module: token validation, rate limiting, input sanitization, user allowlist. */ -import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime"; +import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime"; import { createFixedWindowRateLimiter, type FixedWindowRateLimiter, diff --git a/extensions/telegram/src/webhook.ts b/extensions/telegram/src/webhook.ts index ec6e2720071..47a0aeb60b8 100644 --- a/extensions/telegram/src/webhook.ts +++ b/extensions/telegram/src/webhook.ts @@ -2,11 +2,11 @@ import { createServer } from "node:http"; import type { IncomingMessage } from "node:http"; import net from "node:net"; import * as grammy from "grammy"; -import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; import { isDiagnosticsEnabled } from "openclaw/plugin-sdk/diagnostic-runtime"; import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env"; import { defaultRuntime } from "openclaw/plugin-sdk/runtime-env"; +import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime"; import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime"; import { logWebhookError, diff --git a/extensions/tlon/runtime-api.ts b/extensions/tlon/runtime-api.ts index 755b897398c..35f278592db 100644 --- a/extensions/tlon/runtime-api.ts +++ b/extensions/tlon/runtime-api.ts @@ -14,4 +14,4 @@ export { type LookupFn, type SsrFPolicy, } from "openclaw/plugin-sdk/ssrf-runtime"; -export { SsrFBlockedError } from "openclaw/plugin-sdk/browser-security-runtime"; +export { SsrFBlockedError } from "openclaw/plugin-sdk/ssrf-runtime"; diff --git a/extensions/tlon/src/urbit/auth.ssrf.test.ts b/extensions/tlon/src/urbit/auth.ssrf.test.ts index bccbf55efad..e74f4a48c94 100644 --- a/extensions/tlon/src/urbit/auth.ssrf.test.ts +++ b/extensions/tlon/src/urbit/auth.ssrf.test.ts @@ -1,4 +1,4 @@ -import { SsrFBlockedError } from "openclaw/plugin-sdk/browser-security-runtime"; +import { SsrFBlockedError } from "openclaw/plugin-sdk/ssrf-runtime"; import type { LookupFn } from "openclaw/plugin-sdk/ssrf-runtime"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { authenticate } from "./auth.js"; diff --git a/extensions/voice-call/src/providers/twilio.ts b/extensions/voice-call/src/providers/twilio.ts index 0a7bb0377c8..6fe360eabb4 100644 --- a/extensions/voice-call/src/providers/twilio.ts +++ b/extensions/voice-call/src/providers/twilio.ts @@ -1,5 +1,5 @@ import crypto from "node:crypto"; -import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime"; +import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime"; import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime"; import type { TwilioConfig } from "../config.js"; import { getHeader } from "../http-headers.js"; diff --git a/extensions/voice-call/src/webhook-security.ts b/extensions/voice-call/src/webhook-security.ts index d9a9321c3a8..a7b4ff3ff1f 100644 --- a/extensions/voice-call/src/webhook-security.ts +++ b/extensions/voice-call/src/webhook-security.ts @@ -1,6 +1,6 @@ import crypto from "node:crypto"; -import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime"; import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; +import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime"; import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import { getHeader } from "./http-headers.js"; import type { WebhookContext } from "./types.js"; diff --git a/extensions/webhooks/src/http.ts b/extensions/webhooks/src/http.ts index 98ab0e3c2c0..349c2514aca 100644 --- a/extensions/webhooks/src/http.ts +++ b/extensions/webhooks/src/http.ts @@ -1,5 +1,5 @@ import type { IncomingMessage, ServerResponse } from "node:http"; -import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime"; +import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime"; import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import { z } from "zod"; import type { PluginRuntime } from "../api.js"; diff --git a/extensions/zalo/src/monitor.webhook.ts b/extensions/zalo/src/monitor.webhook.ts index 309ad83dc59..877a7b2b36f 100644 --- a/extensions/zalo/src/monitor.webhook.ts +++ b/extensions/zalo/src/monitor.webhook.ts @@ -1,6 +1,6 @@ import type { IncomingMessage, ServerResponse } from "node:http"; -import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime"; import { createClaimableDedupe } from "openclaw/plugin-sdk/persistent-dedupe"; +import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime"; import type { ResolvedZaloAccount } from "./accounts.js"; import type { ZaloFetch, ZaloUpdate } from "./api.js"; import type { ZaloRuntimeEnv } from "./monitor.types.js"; diff --git a/extensions/zalouser/runtime-api.ts b/extensions/zalouser/runtime-api.ts index 9a5d6de648a..6a0a145352d 100644 --- a/extensions/zalouser/runtime-api.ts +++ b/extensions/zalouser/runtime-api.ts @@ -58,4 +58,4 @@ export { sendPayloadWithChunkedTextAndMedia, type OutboundReplyPayload, } from "openclaw/plugin-sdk/reply-payload"; -export { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/browser-security-runtime"; +export { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path"; diff --git a/extensions/zalouser/src/qr-temp-file.ts b/extensions/zalouser/src/qr-temp-file.ts index 8e93a6e0a5e..1ba9601ec40 100644 --- a/extensions/zalouser/src/qr-temp-file.ts +++ b/extensions/zalouser/src/qr-temp-file.ts @@ -1,6 +1,6 @@ import fsp from "node:fs/promises"; import path from "node:path"; -import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/browser-security-runtime"; +import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path"; export async function writeQrDataUrlToTempFile( qrDataUrl: string, diff --git a/src/plugin-sdk/gateway-runtime.ts b/src/plugin-sdk/gateway-runtime.ts index f4963d1aa60..d7cff644fb7 100644 --- a/src/plugin-sdk/gateway-runtime.ts +++ b/src/plugin-sdk/gateway-runtime.ts @@ -1,10 +1,13 @@ // Public gateway/client helpers for plugins that talk to the host gateway surface. export * from "../gateway/channel-status-patches.js"; +export { addGatewayClientOptions, callGatewayFromCli } from "../cli/gateway-rpc.js"; +export type { GatewayRpcOpts } from "../cli/gateway-rpc.js"; export { GatewayClient } from "../gateway/client.js"; export { createOperatorApprovalsGatewayClient, withOperatorApprovalsGatewayClient, } from "../gateway/operator-approvals-client.js"; +export { ErrorCodes, errorShape } from "../gateway/protocol/index.js"; export type { EventFrame } from "../gateway/protocol/index.js"; export type { GatewayRequestHandlerOptions } from "../gateway/server-methods/types.js"; diff --git a/src/plugin-sdk/security-runtime.ts b/src/plugin-sdk/security-runtime.ts index c245c5e1a1d..46f68a97095 100644 --- a/src/plugin-sdk/security-runtime.ts +++ b/src/plugin-sdk/security-runtime.ts @@ -9,3 +9,4 @@ export * from "../security/context-visibility.js"; export * from "../security/dm-policy-shared.js"; export * from "../security/external-content.js"; export * from "../security/safe-regex.js"; +export { safeEqualSecret } from "../security/secret-equal.js"; diff --git a/src/plugin-sdk/ssrf-runtime.ts b/src/plugin-sdk/ssrf-runtime.ts index cfe7171c1d4..31dc6ddde4a 100644 --- a/src/plugin-sdk/ssrf-runtime.ts +++ b/src/plugin-sdk/ssrf-runtime.ts @@ -4,6 +4,7 @@ export { closeDispatcher, createPinnedDispatcher, + SsrFBlockedError, isBlockedHostnameOrIp, resolvePinnedHostname, resolvePinnedHostnameWithPolicy, diff --git a/src/plugin-sdk/webhook-ingress.ts b/src/plugin-sdk/webhook-ingress.ts index 1e5c26ab361..8e336db9996 100644 --- a/src/plugin-sdk/webhook-ingress.ts +++ b/src/plugin-sdk/webhook-ingress.ts @@ -43,5 +43,6 @@ export { normalizeWebhookPath, resolveWebhookPath } from "./webhook-path.js"; export { resolveRequestClientIp } from "../gateway/net.js"; export { createAuthRateLimiter } from "../gateway/auth-rate-limit.js"; export type { AuthRateLimiter, RateLimitConfig } from "../gateway/auth-rate-limit.js"; +export { rawDataToString } from "../infra/ws.js"; export { normalizePluginHttpPath } from "../plugins/http-path.js"; export { DEFAULT_WEBHOOK_MAX_BODY_BYTES } from "../infra/http-body.js"; diff --git a/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts b/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts index 627dec2bfdb..9e4a00e726e 100644 --- a/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts +++ b/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts @@ -86,6 +86,12 @@ function collectPluginOwnedSdkEntrypoints(): string[] { .toSorted(); } +function resolvePluginOwnerFromEntrypoint(entrypoint: string): string | undefined { + return collectBundledPluginIds().find( + (pluginId) => entrypoint === pluginId || entrypoint.startsWith(`${pluginId}-`), + ); +} + function collectClassificationOverlaps(classifications: Record) { const seen = new Map(); for (const [classification, entrypoints] of Object.entries(classifications)) { @@ -224,6 +230,47 @@ function collectExtensionCoreImportLeaks(): Array<{ file: string; specifier: str return leaks; } +function collectCrossOwnerReservedSdkImports(): Array<{ + file: string; + specifier: string; + owner?: string; +}> { + const leaks: Array<{ file: string; specifier: string; owner?: string }> = []; + const reserved = new Set(reservedBundledPluginSdkEntrypoints); + const importPattern = + /\b(?:import|export)\b[\s\S]*?\bfrom\s*["']openclaw\/plugin-sdk\/([a-z0-9][a-z0-9-]*)["']/g; + + for (const file of collectExtensionFiles(resolve(REPO_ROOT, "extensions"))) { + const repoRelativePath = relative(REPO_ROOT, file).replaceAll("\\", "/"); + if ( + /(?:^|\/)(?:__tests__|tests|test-support)(?:\/|$)/.test(repoRelativePath) || + /(?:^|\/)test-support\.[cm]?tsx?$/.test(repoRelativePath) || + /\.test-support\.[cm]?tsx?$/.test(repoRelativePath) || + /\.test\.[cm]?tsx?$/.test(repoRelativePath) + ) { + continue; + } + const pluginId = repoRelativePath.split("/")[1]; + const source = readFileSync(file, "utf8"); + for (const match of source.matchAll(importPattern)) { + const subpath = match[1]; + if (!subpath || !reserved.has(subpath)) { + continue; + } + const owner = resolvePluginOwnerFromEntrypoint(subpath); + if (owner === pluginId) { + continue; + } + leaks.push({ + file: repoRelativePath, + specifier: `openclaw/plugin-sdk/${subpath}`, + owner, + }); + } + } + return leaks; +} + describe("plugin-sdk package contract guardrails", () => { it("keeps plugin-sdk entrypoint metadata unique", () => { const counts = new Map(); @@ -350,6 +397,10 @@ describe("plugin-sdk package contract guardrails", () => { expect(collectExtensionCoreImportLeaks()).toEqual([]); }); + it("keeps reserved SDK compatibility subpaths inside their owning bundled plugins", () => { + expect(collectCrossOwnerReservedSdkImports()).toEqual([]); + }); + it("keeps generic core poll helpers free of plugin owner names", () => { expect(collectGenericCoreOwnerNameLeaks()).toEqual([]); });