From 5b2c5ee2bc13c7fa0496c1b4b0d337d23cec3bde Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 17 Mar 2026 19:48:47 -0700 Subject: [PATCH] refactor: remove remaining extension src imports --- docs/tools/plugin.md | 7 ++ .../discord/src/actions/runtime.messaging.ts | 2 +- .../firecrawl/src/firecrawl-scrape-tool.ts | 2 +- extensions/line/src/setup-core.ts | 6 +- extensions/tlon/src/channel.runtime.ts | 9 +- extensions/tlon/src/channel.ts | 3 +- extensions/tlon/src/config-schema.ts | 2 +- extensions/tlon/src/runtime.ts | 2 +- extensions/tlon/src/types.ts | 2 +- extensions/tlon/src/urbit/base-url.ts | 2 +- package.json | 3 +- scripts/check-no-extension-src-imports.ts | 88 +++++++++++++++++++ src/plugin-sdk/agent-runtime.ts | 2 + src/plugin-sdk/channel-runtime.ts | 1 + src/plugin-sdk/core.ts | 6 ++ src/plugin-sdk/discord-core.ts | 1 + src/plugin-sdk/line-core.ts | 3 + src/plugin-sdk/subpaths.test.ts | 8 ++ src/plugin-sdk/telegram-core.ts | 1 + src/plugin-sdk/whatsapp-core.ts | 2 +- 20 files changed, 136 insertions(+), 16 deletions(-) create mode 100644 scripts/check-no-extension-src-imports.ts diff --git a/docs/tools/plugin.md b/docs/tools/plugin.md index b50797537a6..e9f33b00ab5 100644 --- a/docs/tools/plugin.md +++ b/docs/tools/plugin.md @@ -1147,11 +1147,18 @@ authoring plugins: - Domain subpaths such as `openclaw/plugin-sdk/channel-config-helpers`, `openclaw/plugin-sdk/channel-config-schema`, `openclaw/plugin-sdk/channel-policy`, + `openclaw/plugin-sdk/channel-runtime`, + `openclaw/plugin-sdk/config-runtime`, + `openclaw/plugin-sdk/agent-runtime`, `openclaw/plugin-sdk/lazy-runtime`, `openclaw/plugin-sdk/reply-history`, `openclaw/plugin-sdk/routing`, `openclaw/plugin-sdk/runtime-store`, and `openclaw/plugin-sdk/directory-runtime` for shared runtime/config helpers. +- Narrow channel-core subpaths such as `openclaw/plugin-sdk/discord-core`, + `openclaw/plugin-sdk/telegram-core`, `openclaw/plugin-sdk/whatsapp-core`, + and `openclaw/plugin-sdk/line-core` for channel-specific primitives that + should stay smaller than the full channel helper barrels. - `openclaw/plugin-sdk/compat` remains as a legacy migration surface for older external plugins. Bundled plugins should not use it, and non-test imports emit a one-time deprecation warning outside test environments. diff --git a/extensions/discord/src/actions/runtime.messaging.ts b/extensions/discord/src/actions/runtime.messaging.ts index d5cf900207b..e954fd3534e 100644 --- a/extensions/discord/src/actions/runtime.messaging.ts +++ b/extensions/discord/src/actions/runtime.messaging.ts @@ -1,8 +1,8 @@ import type { AgentToolResult } from "@mariozechner/pi-agent-core"; import { readBooleanParam } from "openclaw/plugin-sdk/boolean-param"; import { - type ActionGate, assertMediaNotDataUrl, + type ActionGate, jsonResult, readNumberParam, readReactionParams, diff --git a/extensions/firecrawl/src/firecrawl-scrape-tool.ts b/extensions/firecrawl/src/firecrawl-scrape-tool.ts index 70f0691d3d7..88f36769210 100644 --- a/extensions/firecrawl/src/firecrawl-scrape-tool.ts +++ b/extensions/firecrawl/src/firecrawl-scrape-tool.ts @@ -1,6 +1,6 @@ import { Type } from "@sinclair/typebox"; -import { optionalStringEnum } from "openclaw/plugin-sdk/agent-runtime"; import { jsonResult, readNumberParam, readStringParam } from "openclaw/plugin-sdk/agent-runtime"; +import { optionalStringEnum } from "openclaw/plugin-sdk/core"; import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-runtime"; import { runFirecrawlScrape } from "./firecrawl-client.js"; diff --git a/extensions/line/src/setup-core.ts b/extensions/line/src/setup-core.ts index d6e8612cd92..363b4dcb2a1 100644 --- a/extensions/line/src/setup-core.ts +++ b/extensions/line/src/setup-core.ts @@ -1,11 +1,11 @@ import { DEFAULT_ACCOUNT_ID, + listLineAccountIds, normalizeAccountId, resolveLineAccount, - type ChannelSetupAdapter, type LineConfig, - type OpenClawConfig, } from "openclaw/plugin-sdk/line-core"; +import type { ChannelSetupAdapter, OpenClawConfig } from "openclaw/plugin-sdk/setup"; const channel = "line" as const; @@ -158,4 +158,4 @@ export const lineSetupAdapter: ChannelSetupAdapter = { }, }; -export { listLineAccountIds } from "openclaw/plugin-sdk/line-core"; +export { listLineAccountIds }; diff --git a/extensions/tlon/src/channel.runtime.ts b/extensions/tlon/src/channel.runtime.ts index 98da82480fa..a657768db6e 100644 --- a/extensions/tlon/src/channel.runtime.ts +++ b/extensions/tlon/src/channel.runtime.ts @@ -1,12 +1,13 @@ import crypto from "node:crypto"; import { configureClient } from "@tloncorp/api"; +import { createReplyPrefixOptions } from "openclaw/plugin-sdk/channel-runtime"; import type { ChannelAccountSnapshot, ChannelOutboundAdapter, - ChannelPlugin, - OpenClawConfig, -} from "../api.js"; -import { createLoggerBackedRuntime, createReplyPrefixOptions } from "../api.js"; +} from "openclaw/plugin-sdk/channel-runtime"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; +import type { ChannelPlugin } from "openclaw/plugin-sdk/core"; +import { createLoggerBackedRuntime } from "openclaw/plugin-sdk/runtime"; import { monitorTlonProvider } from "./monitor/index.js"; import { tlonSetupWizard } from "./setup-surface.js"; import { diff --git a/extensions/tlon/src/channel.ts b/extensions/tlon/src/channel.ts index 92d22feedd5..ea23ab19815 100644 --- a/extensions/tlon/src/channel.ts +++ b/extensions/tlon/src/channel.ts @@ -1,5 +1,6 @@ +import type { ChannelAccountSnapshot, ChannelPlugin } from "openclaw/plugin-sdk/channel-runtime"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; -import type { ChannelAccountSnapshot, ChannelPlugin, OpenClawConfig } from "../api.js"; import { tlonChannelConfigSchema } from "./config-schema.js"; import { applyTlonSetupConfig, diff --git a/extensions/tlon/src/config-schema.ts b/extensions/tlon/src/config-schema.ts index e7ec5ef2ecf..9bf07b94720 100644 --- a/extensions/tlon/src/config-schema.ts +++ b/extensions/tlon/src/config-schema.ts @@ -1,5 +1,5 @@ +import { buildChannelConfigSchema } from "openclaw/plugin-sdk/core"; import { z } from "zod"; -import { buildChannelConfigSchema } from "../api.js"; const ShipSchema = z.string().min(1); const ChannelNestSchema = z.string().min(1); diff --git a/extensions/tlon/src/runtime.ts b/extensions/tlon/src/runtime.ts index bf284e214a8..8544684dc14 100644 --- a/extensions/tlon/src/runtime.ts +++ b/extensions/tlon/src/runtime.ts @@ -1,5 +1,5 @@ +import type { PluginRuntime } from "openclaw/plugin-sdk/plugin-runtime"; import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store"; -import type { PluginRuntime } from "../api.js"; const { setRuntime: setTlonRuntime, getRuntime: getTlonRuntime } = createPluginRuntimeStore("Tlon runtime not initialized"); diff --git a/extensions/tlon/src/types.ts b/extensions/tlon/src/types.ts index 7aa0690c14f..ce4deaeca68 100644 --- a/extensions/tlon/src/types.ts +++ b/extensions/tlon/src/types.ts @@ -1,4 +1,4 @@ -import type { OpenClawConfig } from "../api.js"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; export type TlonResolvedAccount = { accountId: string; diff --git a/extensions/tlon/src/urbit/base-url.ts b/extensions/tlon/src/urbit/base-url.ts index 15321d3e391..61674b23321 100644 --- a/extensions/tlon/src/urbit/base-url.ts +++ b/extensions/tlon/src/urbit/base-url.ts @@ -1,4 +1,4 @@ -import { isBlockedHostnameOrIp } from "../../api.js"; +import { isBlockedHostnameOrIp } from "openclaw/plugin-sdk/infra-runtime"; export type UrbitBaseUrlValidation = | { ok: true; baseUrl: string; hostname: string } diff --git a/package.json b/package.json index 2a0431f0281..03c12ff3b45 100644 --- a/package.json +++ b/package.json @@ -487,7 +487,7 @@ "build:plugin-sdk:dts": "tsc -p tsconfig.plugin-sdk.dts.json || true", "build:strict-smoke": "pnpm canvas:a2ui:bundle && node scripts/tsdown-build.mjs && node scripts/runtime-postbuild.mjs && pnpm build:plugin-sdk:dts", "canvas:a2ui:bundle": "bash scripts/bundle-a2ui.sh", - "check": "pnpm check:host-env-policy:swift && pnpm format:check && pnpm tsgo && pnpm plugin-sdk:check-exports && pnpm lint && pnpm lint:tmp:no-random-messaging && pnpm lint:tmp:channel-agnostic-boundaries && pnpm lint:tmp:no-raw-channel-fetch && pnpm lint:agent:ingress-owner && pnpm lint:plugins:no-register-http-handler && pnpm lint:plugins:no-monolithic-plugin-sdk-entry-imports && pnpm lint:plugins:no-extension-test-core-imports && pnpm lint:webhook:no-low-level-body-read && pnpm lint:auth:no-pairing-store-group && pnpm lint:auth:pairing-account-scope", + "check": "pnpm check:host-env-policy:swift && pnpm format:check && pnpm tsgo && pnpm plugin-sdk:check-exports && pnpm lint && pnpm lint:tmp:no-random-messaging && pnpm lint:tmp:channel-agnostic-boundaries && pnpm lint:tmp:no-raw-channel-fetch && pnpm lint:agent:ingress-owner && pnpm lint:plugins:no-register-http-handler && pnpm lint:plugins:no-monolithic-plugin-sdk-entry-imports && pnpm lint:plugins:no-extension-src-imports && pnpm lint:plugins:no-extension-test-core-imports && pnpm lint:webhook:no-low-level-body-read && pnpm lint:auth:no-pairing-store-group && pnpm lint:auth:pairing-account-scope", "check:docs": "pnpm format:docs:check && pnpm lint:docs && pnpm docs:check-i18n-glossary && pnpm docs:check-links", "check:host-env-policy:swift": "node scripts/generate-host-env-security-policy-swift.mjs --check", "check:loc": "node --import tsx scripts/check-ts-max-loc.ts --max 500", @@ -539,6 +539,7 @@ "lint:docs": "pnpm dlx markdownlint-cli2", "lint:docs:fix": "pnpm dlx markdownlint-cli2 --fix", "lint:fix": "oxlint --type-aware --fix && pnpm format", + "lint:plugins:no-extension-src-imports": "node --import tsx scripts/check-no-extension-src-imports.ts", "lint:plugins:no-extension-test-core-imports": "node --import tsx scripts/check-no-extension-test-core-imports.ts", "lint:plugins:no-monolithic-plugin-sdk-entry-imports": "node --import tsx scripts/check-no-monolithic-plugin-sdk-entry-imports.ts", "lint:plugins:no-register-http-handler": "node scripts/check-no-register-http-handler.mjs", diff --git a/scripts/check-no-extension-src-imports.ts b/scripts/check-no-extension-src-imports.ts new file mode 100644 index 00000000000..e6399f45048 --- /dev/null +++ b/scripts/check-no-extension-src-imports.ts @@ -0,0 +1,88 @@ +import fs from "node:fs"; +import path from "node:path"; + +const FORBIDDEN_REPO_SRC_IMPORT = /["'](?:\.\.\/)+(?:src\/)[^"']+["']/; + +function isSourceFile(filePath: string): boolean { + if (filePath.endsWith(".d.ts")) { + return false; + } + return /\.(?:[cm]?ts|[cm]?js|tsx|jsx)$/u.test(filePath); +} + +function isProductionExtensionFile(filePath: string): boolean { + return !( + filePath.includes(".test.") || + filePath.includes(".spec.") || + filePath.includes(".fixture.") || + filePath.includes(".snap") || + filePath.includes("test-harness") || + filePath.includes("test-support") || + filePath.includes("/__tests__/") || + filePath.includes("/coverage/") || + filePath.includes("/dist/") || + filePath.includes("/node_modules/") + ); +} + +function collectExtensionSourceFiles(rootDir: string): string[] { + const files: string[] = []; + const stack = [rootDir]; + while (stack.length > 0) { + const current = stack.pop(); + if (!current) { + continue; + } + let entries: fs.Dirent[] = []; + try { + entries = fs.readdirSync(current, { withFileTypes: true }); + } catch { + continue; + } + for (const entry of entries) { + const fullPath = path.join(current, entry.name); + if (entry.isDirectory()) { + if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "coverage") { + continue; + } + stack.push(fullPath); + continue; + } + if (entry.isFile() && isSourceFile(fullPath) && isProductionExtensionFile(fullPath)) { + files.push(fullPath); + } + } + } + return files; +} + +function main() { + const extensionsDir = path.join(process.cwd(), "extensions"); + const files = collectExtensionSourceFiles(extensionsDir); + const offenders: string[] = []; + + for (const file of files) { + const content = fs.readFileSync(file, "utf8"); + if (FORBIDDEN_REPO_SRC_IMPORT.test(content)) { + offenders.push(file); + } + } + + if (offenders.length > 0) { + console.error("Production extension files must not import the repo src/ tree directly."); + for (const offender of offenders.toSorted()) { + const relative = path.relative(process.cwd(), offender) || offender; + console.error(`- ${relative}`); + } + console.error( + "Publish a focused openclaw/plugin-sdk/ seam or use the extension's own public barrel instead.", + ); + process.exit(1); + } + + console.log( + `OK: production extension files avoid direct repo src/ imports (${files.length} checked).`, + ); +} + +main(); diff --git a/src/plugin-sdk/agent-runtime.ts b/src/plugin-sdk/agent-runtime.ts index e267a458e16..c5313f681cc 100644 --- a/src/plugin-sdk/agent-runtime.ts +++ b/src/plugin-sdk/agent-runtime.ts @@ -3,6 +3,7 @@ export * from "../agents/agent-scope.js"; export * from "../agents/auth-profiles.js"; export * from "../agents/current-time.js"; +export * from "../agents/date-time.js"; export * from "../agents/defaults.js"; export * from "../agents/identity-avatar.js"; export * from "../agents/identity.js"; @@ -13,6 +14,7 @@ export * from "../agents/model-selection.js"; export * from "../agents/pi-embedded-block-chunker.js"; export * from "../agents/pi-embedded-utils.js"; export * from "../agents/provider-id.js"; +export * from "../agents/sandbox-paths.js"; export * from "../agents/schema/typebox.js"; export * from "../agents/sglang-defaults.js"; export * from "../agents/tools/common.js"; diff --git a/src/plugin-sdk/channel-runtime.ts b/src/plugin-sdk/channel-runtime.ts index 089e10609af..cf916194580 100644 --- a/src/plugin-sdk/channel-runtime.ts +++ b/src/plugin-sdk/channel-runtime.ts @@ -42,6 +42,7 @@ export * from "../channels/plugins/outbound/interactive.js"; export * from "../channels/plugins/status-issues/shared.js"; export * from "../channels/plugins/whatsapp-heartbeat.js"; export * from "../infra/outbound/send-deps.js"; +export * from "../polls.js"; export * from "../utils/message-channel.js"; export * from "./channel-lifecycle.js"; export type { diff --git a/src/plugin-sdk/core.ts b/src/plugin-sdk/core.ts index 56f0bdafa26..3571edf9772 100644 --- a/src/plugin-sdk/core.ts +++ b/src/plugin-sdk/core.ts @@ -72,6 +72,12 @@ export { export { formatPairingApproveHint } from "../channels/plugins/helpers.js"; export { getChatChannelMeta } from "../channels/registry.js"; export { buildOauthProviderAuthResult } from "./provider-auth-result.js"; +export { + channelTargetSchema, + channelTargetsSchema, + optionalStringEnum, + stringEnum, +} from "../agents/schema/typebox.js"; export { DEFAULT_SECRET_FILE_MAX_BYTES, loadSecretFileSync, diff --git a/src/plugin-sdk/discord-core.ts b/src/plugin-sdk/discord-core.ts index 5a3a20cea28..a82a1a1cf38 100644 --- a/src/plugin-sdk/discord-core.ts +++ b/src/plugin-sdk/discord-core.ts @@ -1,4 +1,5 @@ export type { ChannelPlugin } from "./channel-plugin-common.js"; +export type { DiscordActionConfig } from "../config/types.js"; export { buildChannelConfigSchema, getChatChannelMeta } from "./channel-plugin-common.js"; export type { OpenClawConfig } from "../config/config.js"; export type { DiscordActionConfig } from "../config/types.js"; diff --git a/src/plugin-sdk/line-core.ts b/src/plugin-sdk/line-core.ts index 8f2b9f1949d..5c2ec5dce15 100644 --- a/src/plugin-sdk/line-core.ts +++ b/src/plugin-sdk/line-core.ts @@ -11,6 +11,9 @@ export { export type { ChannelSetupAdapter, ChannelSetupDmPolicy, ChannelSetupWizard } from "./setup.js"; export { listLineAccountIds, + normalizeAccountId, resolveDefaultLineAccountId, resolveLineAccount, } from "../line/accounts.js"; +export type { ResolvedLineAccount } from "../line/types.js"; +export { LineConfigSchema } from "../line/config-schema.js"; diff --git a/src/plugin-sdk/subpaths.test.ts b/src/plugin-sdk/subpaths.test.ts index 4e73ce9c26e..dc72414a1f1 100644 --- a/src/plugin-sdk/subpaths.test.ts +++ b/src/plugin-sdk/subpaths.test.ts @@ -9,6 +9,7 @@ import * as discordSdk from "openclaw/plugin-sdk/discord"; import * as imessageSdk from "openclaw/plugin-sdk/imessage"; import * as lazyRuntimeSdk from "openclaw/plugin-sdk/lazy-runtime"; import * as lineSdk from "openclaw/plugin-sdk/line"; +import * as lineCoreSdk from "openclaw/plugin-sdk/line-core"; import * as msteamsSdk from "openclaw/plugin-sdk/msteams"; import * as nostrSdk from "openclaw/plugin-sdk/nostr"; import * as ollamaSetupSdk from "openclaw/plugin-sdk/ollama-setup"; @@ -67,6 +68,7 @@ describe("plugin-sdk subpath exports", () => { expect(typeof coreSdk.definePluginEntry).toBe("function"); expect(typeof coreSdk.defineChannelPluginEntry).toBe("function"); expect(typeof coreSdk.defineSetupPluginEntry).toBe("function"); + expect(typeof coreSdk.optionalStringEnum).toBe("function"); expect("runPassiveAccountLifecycle" in asExports(coreSdk)).toBe(false); expect("createLoggerBackedRuntime" in asExports(coreSdk)).toBe(false); expect("registerSandboxBackend" in asExports(coreSdk)).toBe(false); @@ -207,6 +209,12 @@ describe("plugin-sdk subpath exports", () => { expect(typeof lineSdk.lineSetupAdapter).toBe("object"); }); + it("exports narrow LINE core helpers", () => { + expect(typeof lineCoreSdk.resolveLineAccount).toBe("function"); + expect(typeof lineCoreSdk.listLineAccountIds).toBe("function"); + expect(typeof lineCoreSdk.LineConfigSchema).toBe("object"); + }); + it("exports Microsoft Teams helpers", () => { expect(typeof msteamsSdk.resolveControlCommandGate).toBe("function"); expect(typeof msteamsSdk.loadOutboundMediaFromUrl).toBe("function"); diff --git a/src/plugin-sdk/telegram-core.ts b/src/plugin-sdk/telegram-core.ts index 6745072c497..2314a4cf2b5 100644 --- a/src/plugin-sdk/telegram-core.ts +++ b/src/plugin-sdk/telegram-core.ts @@ -1,6 +1,7 @@ export type { OpenClawConfig } from "../config/config.js"; export type { TelegramActionConfig } from "../config/types.js"; export type { ChannelPlugin } from "./channel-plugin-common.js"; +export type { TelegramActionConfig } from "../config/types.js"; export { buildChannelConfigSchema, getChatChannelMeta } from "./channel-plugin-common.js"; export { normalizeAccountId } from "../routing/session-key.js"; export { diff --git a/src/plugin-sdk/whatsapp-core.ts b/src/plugin-sdk/whatsapp-core.ts index d2045f007d9..03a396e38f4 100644 --- a/src/plugin-sdk/whatsapp-core.ts +++ b/src/plugin-sdk/whatsapp-core.ts @@ -23,5 +23,5 @@ export { readStringParam, } from "../agents/tools/common.js"; export { WhatsAppConfigSchema } from "../config/zod-schema.providers-whatsapp.js"; -export { normalizeE164 } from "../utils.js"; export { resolveWhatsAppOutboundTarget } from "../whatsapp/resolve-outbound-target.js"; +export { normalizeE164 } from "../utils.js";