refactor: narrow extension public seams
This commit is contained in:
parent
bdf2c265a7
commit
6d9bf6de93
@ -957,6 +957,16 @@ authoring plugins:
|
||||
- `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.
|
||||
- Bundled extension internals remain private. External plugins should use only
|
||||
`openclaw/plugin-sdk/*` subpaths. OpenClaw core/test code may use the repo
|
||||
public seams under `extensions/<id>/index.js`, `api.js`, `runtime-api.js`,
|
||||
`setup-entry.js`, and narrowly scoped files such as `login-qr-api.js`. Never
|
||||
import `extensions/<id>/src/*` from core or from another extension.
|
||||
- Repo seam split:
|
||||
`extensions/<id>/api.js` is the helper/types barrel,
|
||||
`extensions/<id>/runtime-api.js` is the runtime-only barrel,
|
||||
`extensions/<id>/index.js` is the bundled plugin entry,
|
||||
and `extensions/<id>/setup-entry.js` is the setup plugin entry.
|
||||
- `openclaw/plugin-sdk/telegram` for Telegram channel plugin types and shared channel-facing helpers. Built-in Telegram implementation internals stay private to the bundled extension.
|
||||
- `openclaw/plugin-sdk/discord` for Discord channel plugin types and shared channel-facing helpers. Built-in Discord implementation internals stay private to the bundled extension.
|
||||
- `openclaw/plugin-sdk/slack` for Slack channel plugin types and shared channel-facing helpers. Built-in Slack implementation internals stay private to the bundled extension.
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
export * from "./runtime-api.js";
|
||||
export * from "./src/account-inspect.js";
|
||||
export * from "./src/accounts.js";
|
||||
export * from "./src/actions/handle-action.guild-admin.js";
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
export * from "./runtime-api.js";
|
||||
export * from "./src/accounts.js";
|
||||
export * from "./src/target-parsing-helpers.js";
|
||||
export * from "./src/targets.js";
|
||||
|
||||
@ -1,2 +1 @@
|
||||
export * from "./runtime-api.js";
|
||||
export * from "./src/accounts.js";
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
export * from "./runtime-api.js";
|
||||
export * from "./src/account-inspect.js";
|
||||
export * from "./src/accounts.js";
|
||||
export * from "./src/actions.js";
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
export * from "./runtime-api.js";
|
||||
export * from "./src/account-inspect.js";
|
||||
export * from "./src/accounts.js";
|
||||
export * from "./src/allow-from.js";
|
||||
|
||||
@ -1,2 +1 @@
|
||||
export * from "./runtime-api.js";
|
||||
export * from "./src/accounts.js";
|
||||
|
||||
@ -101,7 +101,7 @@ export function getWebSessionMocks(): AnyMocks {
|
||||
return webSessionMocks;
|
||||
}
|
||||
|
||||
vi.mock("../../extensions/whatsapp/api.js", () => webSessionMocks);
|
||||
vi.mock("../../extensions/whatsapp/runtime-api.js", () => webSessionMocks);
|
||||
|
||||
export const MAIN_SESSION_KEY = "agent:main:main";
|
||||
|
||||
|
||||
@ -2,10 +2,10 @@ import { expect, vi } from "vitest";
|
||||
import {
|
||||
__testing as discordThreadBindingTesting,
|
||||
createThreadBindingManager as createDiscordThreadBindingManager,
|
||||
} from "../../../../extensions/discord/api.js";
|
||||
} from "../../../../extensions/discord/runtime-api.js";
|
||||
import { createFeishuThreadBindingManager } from "../../../../extensions/feishu/api.js";
|
||||
import { setMatrixRuntime } from "../../../../extensions/matrix/api.js";
|
||||
import { createTelegramThreadBindingManager } from "../../../../extensions/telegram/api.js";
|
||||
import { createTelegramThreadBindingManager } from "../../../../extensions/telegram/runtime-api.js";
|
||||
import type { OpenClawConfig } from "../../../config/config.js";
|
||||
import {
|
||||
getSessionBindingService,
|
||||
|
||||
@ -574,7 +574,7 @@ vi.mock("../commands/health.js", () => ({
|
||||
vi.mock("../commands/status.js", () => ({
|
||||
getStatusSummary: vi.fn().mockResolvedValue({ ok: true }),
|
||||
}));
|
||||
vi.mock("../../extensions/whatsapp/api.js", () => ({
|
||||
vi.mock("../../extensions/whatsapp/runtime-api.js", () => ({
|
||||
sendMessageWhatsApp: (...args: unknown[]) =>
|
||||
(hoisted.sendWhatsAppMock as (...args: unknown[]) => unknown)(...args),
|
||||
sendPollWhatsApp: (...args: unknown[]) =>
|
||||
|
||||
@ -4,6 +4,36 @@ import { fileURLToPath } from "node:url";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const ALLOWED_EXTENSION_PUBLIC_SEAMS = new Set([
|
||||
"api.js",
|
||||
"index.js",
|
||||
"login-qr-api.js",
|
||||
"runtime-api.js",
|
||||
"setup-entry.js",
|
||||
]);
|
||||
const GUARDED_CHANNEL_EXTENSIONS = new Set([
|
||||
"bluebubbles",
|
||||
"discord",
|
||||
"feishu",
|
||||
"googlechat",
|
||||
"imessage",
|
||||
"irc",
|
||||
"line",
|
||||
"matrix",
|
||||
"mattermost",
|
||||
"msteams",
|
||||
"nextcloud-talk",
|
||||
"nostr",
|
||||
"signal",
|
||||
"slack",
|
||||
"synology-chat",
|
||||
"telegram",
|
||||
"tlon",
|
||||
"twitch",
|
||||
"whatsapp",
|
||||
"zalo",
|
||||
"zalouser",
|
||||
]);
|
||||
|
||||
type GuardedSource = {
|
||||
path: string;
|
||||
@ -186,6 +216,27 @@ function collectCoreSourceFiles(): string[] {
|
||||
return files;
|
||||
}
|
||||
|
||||
function collectExtensionImports(text: string): string[] {
|
||||
return [...text.matchAll(/["']([^"']*extensions\/[^"']+\.(?:[cm]?[jt]sx?))["']/g)].map(
|
||||
(match) => match[1] ?? "",
|
||||
);
|
||||
}
|
||||
|
||||
function expectOnlyApprovedExtensionSeams(file: string, imports: string[]): void {
|
||||
for (const specifier of imports) {
|
||||
const normalized = specifier.replaceAll("\\", "/");
|
||||
const extensionId = normalized.match(/extensions\/([^/]+)\//)?.[1] ?? null;
|
||||
if (!extensionId || !GUARDED_CHANNEL_EXTENSIONS.has(extensionId)) {
|
||||
continue;
|
||||
}
|
||||
const basename = normalized.split("/").at(-1) ?? "";
|
||||
expect(
|
||||
ALLOWED_EXTENSION_PUBLIC_SEAMS.has(basename),
|
||||
`${file} should only import approved extension seams, got ${specifier}`,
|
||||
).toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
describe("channel import guardrails", () => {
|
||||
it("keeps channel helper modules off their own SDK barrels", () => {
|
||||
for (const source of SAME_CHANNEL_SDK_GUARDS) {
|
||||
@ -236,4 +287,16 @@ describe("channel import guardrails", () => {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps core extension imports limited to approved public seams", () => {
|
||||
for (const file of collectCoreSourceFiles()) {
|
||||
expectOnlyApprovedExtensionSeams(file, collectExtensionImports(readFileSync(file, "utf8")));
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps extension-to-extension imports limited to approved public seams", () => {
|
||||
for (const file of collectExtensionSourceFiles()) {
|
||||
expectOnlyApprovedExtensionSeams(file, collectExtensionImports(readFileSync(file, "utf8")));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -13,7 +13,7 @@ export type {
|
||||
ThreadBindingManager,
|
||||
ThreadBindingRecord,
|
||||
ThreadBindingTargetKind,
|
||||
} from "../../extensions/discord/api.js";
|
||||
} from "../../extensions/discord/runtime-api.js";
|
||||
export type {
|
||||
ChannelConfiguredBindingProvider,
|
||||
ChannelConfiguredBindingConversationRef,
|
||||
@ -75,20 +75,20 @@ export {
|
||||
normalizeDiscordMessagingTarget,
|
||||
normalizeDiscordOutboundTarget,
|
||||
} from "../../extensions/discord/api.js";
|
||||
export { collectDiscordAuditChannelIds } from "../../extensions/discord/api.js";
|
||||
export { collectDiscordAuditChannelIds } from "../../extensions/discord/runtime-api.js";
|
||||
export { collectDiscordStatusIssues } from "../../extensions/discord/api.js";
|
||||
export {
|
||||
DISCORD_DEFAULT_INBOUND_WORKER_TIMEOUT_MS,
|
||||
DISCORD_DEFAULT_LISTENER_TIMEOUT_MS,
|
||||
} from "../../extensions/discord/api.js";
|
||||
} from "../../extensions/discord/runtime-api.js";
|
||||
export { normalizeExplicitDiscordSessionKey } from "../../extensions/discord/api.js";
|
||||
export {
|
||||
autoBindSpawnedDiscordSubagent,
|
||||
listThreadBindingsBySessionKey,
|
||||
unbindThreadBindingsBySessionKey,
|
||||
} from "../../extensions/discord/api.js";
|
||||
export { getGateway } from "../../extensions/discord/api.js";
|
||||
export { getPresence } from "../../extensions/discord/api.js";
|
||||
} from "../../extensions/discord/runtime-api.js";
|
||||
export { getGateway } from "../../extensions/discord/runtime-api.js";
|
||||
export { getPresence } from "../../extensions/discord/runtime-api.js";
|
||||
export { readDiscordComponentSpec } from "../../extensions/discord/api.js";
|
||||
export { resolveDiscordChannelId } from "../../extensions/discord/api.js";
|
||||
export {
|
||||
@ -134,5 +134,5 @@ export {
|
||||
unpinMessageDiscord,
|
||||
uploadEmojiDiscord,
|
||||
uploadStickerDiscord,
|
||||
} from "../../extensions/discord/api.js";
|
||||
export { discordMessageActions } from "../../extensions/discord/api.js";
|
||||
} from "../../extensions/discord/runtime-api.js";
|
||||
export { discordMessageActions } from "../../extensions/discord/runtime-api.js";
|
||||
|
||||
@ -42,4 +42,4 @@ export { IMessageConfigSchema } from "../config/zod-schema.providers-core.js";
|
||||
|
||||
export { resolveChannelMediaMaxBytes } from "../channels/plugins/media-limits.js";
|
||||
export { collectStatusIssuesFromLastError } from "./status-helpers.js";
|
||||
export { sendMessageIMessage } from "../../extensions/imessage/api.js";
|
||||
export { sendMessageIMessage } from "../../extensions/imessage/runtime-api.js";
|
||||
|
||||
@ -52,6 +52,6 @@ export {
|
||||
listSignalAccountIds,
|
||||
resolveDefaultSignalAccountId,
|
||||
} from "../../extensions/signal/api.js";
|
||||
export { resolveSignalReactionLevel } from "../../extensions/signal/api.js";
|
||||
export { removeReactionSignal, sendReactionSignal } from "../../extensions/signal/api.js";
|
||||
export { sendMessageSignal } from "../../extensions/signal/api.js";
|
||||
export { resolveSignalReactionLevel } from "../../extensions/signal/runtime-api.js";
|
||||
export { removeReactionSignal, sendReactionSignal } from "../../extensions/signal/runtime-api.js";
|
||||
export { sendMessageSignal } from "../../extensions/signal/runtime-api.js";
|
||||
|
||||
@ -60,7 +60,7 @@ export { extractSlackToolSend, listSlackMessageActions } from "../../extensions/
|
||||
export { buildSlackThreadingToolContext } from "../../extensions/slack/api.js";
|
||||
export { parseSlackBlocksInput } from "../../extensions/slack/api.js";
|
||||
export { handleSlackHttpRequest } from "../../extensions/slack/api.js";
|
||||
export { sendMessageSlack } from "../../extensions/slack/api.js";
|
||||
export { sendMessageSlack } from "../../extensions/slack/runtime-api.js";
|
||||
export {
|
||||
deleteSlackMessage,
|
||||
downloadSlackFile,
|
||||
|
||||
@ -19,7 +19,7 @@ export type {
|
||||
} from "../channels/plugins/types.adapters.js";
|
||||
export type { InspectedTelegramAccount } from "../../extensions/telegram/api.js";
|
||||
export type { ResolvedTelegramAccount } from "../../extensions/telegram/api.js";
|
||||
export type { TelegramProbe } from "../../extensions/telegram/api.js";
|
||||
export type { TelegramProbe } from "../../extensions/telegram/runtime-api.js";
|
||||
export type { TelegramButtonStyle, TelegramInlineButtons } from "../../extensions/telegram/api.js";
|
||||
export type { StickerMetadata } from "../../extensions/telegram/api.js";
|
||||
|
||||
@ -96,10 +96,10 @@ export {
|
||||
sendMessageTelegram,
|
||||
sendPollTelegram,
|
||||
sendStickerTelegram,
|
||||
} from "../../extensions/telegram/api.js";
|
||||
} from "../../extensions/telegram/runtime-api.js";
|
||||
export { getCacheStats, searchStickers } from "../../extensions/telegram/api.js";
|
||||
export { resolveTelegramToken } from "../../extensions/telegram/api.js";
|
||||
export { telegramMessageActions } from "../../extensions/telegram/api.js";
|
||||
export { resolveTelegramToken } from "../../extensions/telegram/runtime-api.js";
|
||||
export { telegramMessageActions } from "../../extensions/telegram/runtime-api.js";
|
||||
export { collectTelegramStatusIssues } from "../../extensions/telegram/api.js";
|
||||
export { sendTelegramPayloadMessages } from "../../extensions/telegram/api.js";
|
||||
export {
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
export type { ChannelMessageActionName } from "../channels/plugins/types.js";
|
||||
export type { OpenClawConfig } from "../config/config.js";
|
||||
export type { DmPolicy, GroupPolicy, WhatsAppAccountConfig } from "../config/types.js";
|
||||
export type { WebChannelStatus, WebMonitorTuning } from "../../extensions/whatsapp/api.js";
|
||||
export type { WebInboundMessage, WebListenerCloseReason } from "../../extensions/whatsapp/api.js";
|
||||
export type { WebChannelStatus, WebMonitorTuning } from "../../extensions/whatsapp/runtime-api.js";
|
||||
export type {
|
||||
WebInboundMessage,
|
||||
WebListenerCloseReason,
|
||||
} from "../../extensions/whatsapp/runtime-api.js";
|
||||
export type {
|
||||
ChannelMessageActionContext,
|
||||
ChannelPlugin,
|
||||
@ -73,7 +76,7 @@ export {
|
||||
logoutWeb,
|
||||
pickWebChannel,
|
||||
webAuthExists,
|
||||
} from "../../extensions/whatsapp/api.js";
|
||||
} from "../../extensions/whatsapp/runtime-api.js";
|
||||
export {
|
||||
DEFAULT_WEB_MEDIA_BYTES,
|
||||
HEARTBEAT_PROMPT,
|
||||
@ -81,28 +84,28 @@ export {
|
||||
monitorWebChannel,
|
||||
resolveHeartbeatRecipients,
|
||||
runWebHeartbeatOnce,
|
||||
} from "../../extensions/whatsapp/api.js";
|
||||
} from "../../extensions/whatsapp/runtime-api.js";
|
||||
export {
|
||||
extractMediaPlaceholder,
|
||||
extractText,
|
||||
monitorWebInbox,
|
||||
} from "../../extensions/whatsapp/api.js";
|
||||
export { loginWeb } from "../../extensions/whatsapp/api.js";
|
||||
} from "../../extensions/whatsapp/runtime-api.js";
|
||||
export { loginWeb } from "../../extensions/whatsapp/runtime-api.js";
|
||||
export {
|
||||
getDefaultLocalRoots,
|
||||
loadWebMedia,
|
||||
loadWebMediaRaw,
|
||||
optimizeImageToJpeg,
|
||||
} from "../../extensions/whatsapp/api.js";
|
||||
} from "../../extensions/whatsapp/runtime-api.js";
|
||||
export {
|
||||
sendMessageWhatsApp,
|
||||
sendPollWhatsApp,
|
||||
sendReactionWhatsApp,
|
||||
} from "../../extensions/whatsapp/api.js";
|
||||
} from "../../extensions/whatsapp/runtime-api.js";
|
||||
export {
|
||||
createWaSocket,
|
||||
formatError,
|
||||
getStatusCode,
|
||||
waitForWaConnection,
|
||||
} from "../../extensions/whatsapp/api.js";
|
||||
export { createWhatsAppLoginTool } from "../../extensions/whatsapp/api.js";
|
||||
} from "../../extensions/whatsapp/runtime-api.js";
|
||||
export { createWhatsAppLoginTool } from "../../extensions/whatsapp/runtime-api.js";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { collectTelegramUnmentionedGroupIds } from "../../../extensions/telegram/api.js";
|
||||
import { collectTelegramUnmentionedGroupIds } from "../../../extensions/telegram/runtime-api.js";
|
||||
import { telegramMessageActions } from "../../../extensions/telegram/runtime-api.js";
|
||||
import {
|
||||
setTelegramThreadBindingIdleTimeoutBySessionKey,
|
||||
|
||||
@ -141,7 +141,7 @@ export type PluginRuntimeChannel = {
|
||||
};
|
||||
telegram: {
|
||||
auditGroupMembership: typeof import("../../../extensions/telegram/runtime-api.js").auditTelegramGroupMembership;
|
||||
collectUnmentionedGroupIds: typeof import("../../../extensions/telegram/api.js").collectTelegramUnmentionedGroupIds;
|
||||
collectUnmentionedGroupIds: typeof import("../../../extensions/telegram/runtime-api.js").collectTelegramUnmentionedGroupIds;
|
||||
probeTelegram: typeof import("../../../extensions/telegram/runtime-api.js").probeTelegram;
|
||||
resolveTelegramToken: typeof import("../../../extensions/telegram/runtime-api.js").resolveTelegramToken;
|
||||
sendMessageTelegram: typeof import("../../../extensions/telegram/runtime-api.js").sendMessageTelegram;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user