refactor: dedupe channel entrypoints and test bridges

This commit is contained in:
Peter Steinberger
2026-03-16 23:51:41 -07:00
parent 80a2af1d65
commit f6868b7e42
77 changed files with 360 additions and 376 deletions

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { registerSingleProviderPlugin } from "../../src/test-utils/plugin-registration.js";
import { registerSingleProviderPlugin } from "../test-utils/plugin-registration.js";
import amazonBedrockPlugin from "./index.js";
describe("amazon-bedrock provider plugin", () => {

View File

@@ -1,17 +1,11 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/bluebubbles";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/bluebubbles";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { bluebubblesPlugin } from "./src/channel.js";
import { setBlueBubblesRuntime } from "./src/runtime.js";
const plugin = {
export default defineChannelPluginEntry({
id: "bluebubbles",
name: "BlueBubbles",
description: "BlueBubbles channel plugin (macOS app)",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setBlueBubblesRuntime(api.runtime);
api.registerChannel({ plugin: bluebubblesPlugin });
},
};
export default plugin;
plugin: bluebubblesPlugin,
setRuntime: setBlueBubblesRuntime,
});

View File

@@ -1,5 +1,4 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { bluebubblesPlugin } from "./src/channel.js";
export default {
plugin: bluebubblesPlugin,
};
export default defineSetupPluginEntry(bluebubblesPlugin);

View File

@@ -1,7 +1,7 @@
import type { IncomingMessage } from "node:http";
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/diffs";
import { describe, expect, it, vi } from "vitest";
import { createMockServerResponse } from "../../src/test-utils/mock-http-response.js";
import { createMockServerResponse } from "../test-utils/mock-http-response.js";
import { createTestPluginApi } from "../test-utils/plugin-api.js";
import plugin from "./index.js";

View File

@@ -1,6 +1,6 @@
import type { IncomingMessage } from "node:http";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { createMockServerResponse } from "../../../src/test-utils/mock-http-response.js";
import { createMockServerResponse } from "../../test-utils/mock-http-response.js";
import { createDiffsHttpHandler } from "./http.js";
import { DiffArtifactStore } from "./store.js";
import { createDiffStoreHarness } from "./test-helpers.js";

View File

@@ -1,22 +1,13 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { discordPlugin } from "./src/channel.js";
import { setDiscordRuntime } from "./src/runtime.js";
import { registerDiscordSubagentHooks } from "./src/subagent-hooks.js";
const plugin = {
export default defineChannelPluginEntry({
id: "discord",
name: "Discord",
description: "Discord channel plugin",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setDiscordRuntime(api.runtime);
api.registerChannel({ plugin: discordPlugin });
if (api.registrationMode !== "full") {
return;
}
registerDiscordSubagentHooks(api);
},
};
export default plugin;
plugin: discordPlugin,
setRuntime: setDiscordRuntime,
registerFull: registerDiscordSubagentHooks,
});

View File

@@ -1,3 +1,4 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { discordSetupPlugin } from "./src/channel.setup.js";
export default { plugin: discordSetupPlugin };
export default defineSetupPluginEntry(discordSetupPlugin);

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { withFetchPreconnect } from "../../../src/test-utils/fetch-mock.js";
import { withFetchPreconnect } from "../../test-utils/fetch-mock.js";
import { fetchDiscord } from "./api.js";
import { jsonResponse } from "./test-http-helpers.js";

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { countLines, hasBalancedFences } from "../../../src/test-utils/chunk-test-helpers.js";
import { countLines, hasBalancedFences } from "../../test-utils/chunk-test-helpers.js";
import { chunkDiscordText, chunkDiscordTextWithMode } from "./chunk.js";
describe("chunkDiscordText", () => {

View File

@@ -1,6 +1,6 @@
import { ChannelType, type Guild } from "@buape/carbon";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { typedCases } from "../../../src/test-utils/typed-cases.js";
import { typedCases } from "../../test-utils/typed-cases.js";
import {
allowListMatches,
buildDiscordMediaPayload,

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { withFetchPreconnect } from "../../../src/test-utils/fetch-mock.js";
import { withFetchPreconnect } from "../../test-utils/fetch-mock.js";
import { resolveDiscordChannelAllowlist } from "./resolve-channels.js";
import { jsonResponse, urlToString } from "./test-http-helpers.js";

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { withFetchPreconnect } from "../../../src/test-utils/fetch-mock.js";
import { withFetchPreconnect } from "../../test-utils/fetch-mock.js";
import { resolveDiscordUserAllowlist } from "./resolve-users.js";
import { jsonResponse, urlToString } from "./test-http-helpers.js";

View File

@@ -1,5 +1,4 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/feishu";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/feishu";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { registerFeishuBitableTools } from "./src/bitable.js";
import { feishuPlugin } from "./src/channel.js";
import { registerFeishuChatTools } from "./src/chat.js";
@@ -46,17 +45,13 @@ export {
} from "./src/mention.js";
export { feishuPlugin } from "./src/channel.js";
const plugin = {
export default defineChannelPluginEntry({
id: "feishu",
name: "Feishu",
description: "Feishu/Lark channel plugin",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setFeishuRuntime(api.runtime);
api.registerChannel({ plugin: feishuPlugin });
if (api.registrationMode !== "full") {
return;
}
plugin: feishuPlugin,
setRuntime: setFeishuRuntime,
registerFull(api) {
registerFeishuSubagentHooks(api);
registerFeishuDocTools(api);
registerFeishuChatTools(api);
@@ -65,6 +60,4 @@ const plugin = {
registerFeishuPermTools(api);
registerFeishuBitableTools(api);
},
};
export default plugin;
});

View File

@@ -1,5 +1,4 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { feishuPlugin } from "./src/channel.js";
export default {
plugin: feishuPlugin,
};
export default defineSetupPluginEntry(feishuPlugin);

View File

@@ -1,8 +1,5 @@
import { describe, expect, it } from "vitest";
import {
createProviderUsageFetch,
makeResponse,
} from "../../src/test-utils/provider-usage-fetch.js";
import { createProviderUsageFetch, makeResponse } from "../test-utils/provider-usage-fetch.js";
import { fetchCopilotUsage } from "./usage.js";
describe("fetchCopilotUsage", () => {

View File

@@ -1,17 +1,11 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/googlechat";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/googlechat";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { googlechatPlugin } from "./src/channel.js";
import { setGoogleChatRuntime } from "./src/runtime.js";
const plugin = {
export default defineChannelPluginEntry({
id: "googlechat",
name: "Google Chat",
description: "OpenClaw Google Chat channel plugin",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setGoogleChatRuntime(api.runtime);
api.registerChannel(googlechatPlugin);
},
};
export default plugin;
plugin: googlechatPlugin,
setRuntime: setGoogleChatRuntime,
});

View File

@@ -1,5 +1,4 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { googlechatPlugin } from "./src/channel.js";
export default {
plugin: googlechatPlugin,
};
export default defineSetupPluginEntry(googlechatPlugin);

View File

@@ -4,7 +4,7 @@ import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk/googlech
import { afterEach, describe, expect, it, vi } from "vitest";
import { createEmptyPluginRegistry } from "../../../src/plugins/registry.js";
import { setActivePluginRegistry } from "../../../src/plugins/runtime.js";
import { createMockServerResponse } from "../../../src/test-utils/mock-http-response.js";
import { createMockServerResponse } from "../../test-utils/mock-http-response.js";
import type { ResolvedGoogleChatAccount } from "./accounts.js";
import { verifyGoogleChatRequest } from "./auth.js";
import { handleGoogleChatWebhookRequest, registerGoogleChatWebhookTarget } from "./monitor.js";

View File

@@ -1,17 +1,11 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { imessagePlugin } from "./src/channel.js";
import { setIMessageRuntime } from "./src/runtime.js";
const plugin = {
export default defineChannelPluginEntry({
id: "imessage",
name: "iMessage",
description: "iMessage channel plugin",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setIMessageRuntime(api.runtime);
api.registerChannel({ plugin: imessagePlugin });
},
};
export default plugin;
plugin: imessagePlugin,
setRuntime: setIMessageRuntime,
});

View File

@@ -1,3 +1,4 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { imessageSetupPlugin } from "./src/channel.setup.js";
export default { plugin: imessageSetupPlugin };
export default defineSetupPluginEntry(imessageSetupPlugin);

View File

@@ -1,17 +1,12 @@
import type { ChannelPlugin, OpenClawPluginApi } from "openclaw/plugin-sdk/irc";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/irc";
import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { ircPlugin } from "./src/channel.js";
import { setIrcRuntime } from "./src/runtime.js";
const plugin = {
export default defineChannelPluginEntry({
id: "irc",
name: "IRC",
description: "IRC channel plugin",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setIrcRuntime(api.runtime);
api.registerChannel({ plugin: ircPlugin as ChannelPlugin });
},
};
export default plugin;
plugin: ircPlugin as ChannelPlugin,
setRuntime: setIrcRuntime,
});

View File

@@ -1,5 +1,4 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { ircPlugin } from "./src/channel.js";
export default {
plugin: ircPlugin,
};
export default defineSetupPluginEntry(ircPlugin);

View File

@@ -1,22 +1,13 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/line";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/line";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { registerLineCardCommand } from "./src/card-command.js";
import { linePlugin } from "./src/channel.js";
import { setLineRuntime } from "./src/runtime.js";
const plugin = {
export default defineChannelPluginEntry({
id: "line",
name: "LINE",
description: "LINE Messaging API channel plugin",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setLineRuntime(api.runtime);
api.registerChannel({ plugin: linePlugin });
if (api.registrationMode !== "full") {
return;
}
registerLineCardCommand(api);
},
};
export default plugin;
plugin: linePlugin,
setRuntime: setLineRuntime,
registerFull: registerLineCardCommand,
});

View File

@@ -1,5 +1,4 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { lineSetupPlugin } from "./src/channel.setup.js";
export default {
plugin: lineSetupPlugin,
};
export default defineSetupPluginEntry(lineSetupPlugin);

View File

@@ -1,17 +1,11 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/matrix";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/matrix";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { matrixPlugin } from "./src/channel.js";
import { setMatrixRuntime } from "./src/runtime.js";
const plugin = {
export default defineChannelPluginEntry({
id: "matrix",
name: "Matrix",
description: "Matrix channel plugin (matrix-js-sdk)",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setMatrixRuntime(api.runtime);
api.registerChannel({ plugin: matrixPlugin });
},
};
export default plugin;
description: "Matrix channel plugin",
plugin: matrixPlugin,
setRuntime: setMatrixRuntime,
});

View File

@@ -1,5 +1,4 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { matrixPlugin } from "./src/channel.js";
export default {
plugin: matrixPlugin,
};
export default defineSetupPluginEntry(matrixPlugin);

View File

@@ -1,26 +1,17 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/mattermost";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/mattermost";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { mattermostPlugin } from "./src/channel.js";
import { getSlashCommandState, registerSlashCommandRoute } from "./src/mattermost/slash-state.js";
import { registerSlashCommandRoute } from "./src/mattermost/slash-state.js";
import { setMattermostRuntime } from "./src/runtime.js";
const plugin = {
export default defineChannelPluginEntry({
id: "mattermost",
name: "Mattermost",
description: "Mattermost channel plugin",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setMattermostRuntime(api.runtime);
api.registerChannel({ plugin: mattermostPlugin });
if (api.registrationMode !== "full") {
return;
}
// Register the HTTP route for slash command callbacks.
// The actual command registration with MM happens in the monitor
// after the bot connects and we know the team ID.
plugin: mattermostPlugin,
setRuntime: setMattermostRuntime,
registerFull(api) {
// Actual slash-command registration happens after the monitor connects and
// knows the team id; the route itself can be wired here.
registerSlashCommandRoute(api);
},
};
export default plugin;
});

View File

@@ -1,5 +1,4 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { mattermostPlugin } from "./src/channel.js";
export default {
plugin: mattermostPlugin,
};
export default defineSetupPluginEntry(mattermostPlugin);

View File

@@ -1,17 +1,11 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/msteams";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/msteams";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { msteamsPlugin } from "./src/channel.js";
import { setMSTeamsRuntime } from "./src/runtime.js";
const plugin = {
export default defineChannelPluginEntry({
id: "msteams",
name: "Microsoft Teams",
description: "Microsoft Teams channel plugin (Bot Framework)",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setMSTeamsRuntime(api.runtime);
api.registerChannel({ plugin: msteamsPlugin });
},
};
export default plugin;
plugin: msteamsPlugin,
setRuntime: setMSTeamsRuntime,
});

View File

@@ -1,5 +1,4 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { msteamsPlugin } from "./src/channel.js";
export default {
plugin: msteamsPlugin,
};
export default defineSetupPluginEntry(msteamsPlugin);

View File

@@ -1,5 +1,5 @@
import { describe, expect, it, vi } from "vitest";
import { withFetchPreconnect } from "../../../src/test-utils/fetch-mock.js";
import { withFetchPreconnect } from "../../test-utils/fetch-mock.js";
import { uploadToOneDrive, uploadToSharePoint } from "./graph-upload.js";
describe("graph upload helpers", () => {

View File

@@ -1,17 +1,11 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/nextcloud-talk";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/nextcloud-talk";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { nextcloudTalkPlugin } from "./src/channel.js";
import { setNextcloudTalkRuntime } from "./src/runtime.js";
const plugin = {
export default defineChannelPluginEntry({
id: "nextcloud-talk",
name: "Nextcloud Talk",
description: "Nextcloud Talk channel plugin",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setNextcloudTalkRuntime(api.runtime);
api.registerChannel({ plugin: nextcloudTalkPlugin });
},
};
export default plugin;
plugin: nextcloudTalkPlugin,
setRuntime: setNextcloudTalkRuntime,
});

View File

@@ -1,5 +1,4 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { nextcloudTalkPlugin } from "./src/channel.js";
export default {
plugin: nextcloudTalkPlugin,
};
export default defineSetupPluginEntry(nextcloudTalkPlugin);

View File

@@ -1,24 +1,17 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/nostr";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/nostr";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { nostrPlugin } from "./src/channel.js";
import type { NostrProfile } from "./src/config-schema.js";
import { createNostrProfileHttpHandler } from "./src/nostr-profile-http.js";
import { setNostrRuntime, getNostrRuntime } from "./src/runtime.js";
import { getNostrRuntime, setNostrRuntime } from "./src/runtime.js";
import { resolveNostrAccount } from "./src/types.js";
const plugin = {
export default defineChannelPluginEntry({
id: "nostr",
name: "Nostr",
description: "Nostr DM channel plugin via NIP-04",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setNostrRuntime(api.runtime);
api.registerChannel({ plugin: nostrPlugin });
if (api.registrationMode !== "full") {
return;
}
// Register HTTP handler for profile management
plugin: nostrPlugin,
setRuntime: setNostrRuntime,
registerFull(api) {
const httpHandler = createNostrProfileHttpHandler({
getConfigProfile: (accountId: string) => {
const runtime = getNostrRuntime();
@@ -30,23 +23,18 @@ const plugin = {
const runtime = getNostrRuntime();
const cfg = runtime.config.loadConfig();
// Build the config patch for channels.nostr.profile
const channels = (cfg.channels ?? {}) as Record<string, unknown>;
const nostrConfig = (channels.nostr ?? {}) as Record<string, unknown>;
const updatedNostrConfig = {
...nostrConfig,
profile,
};
const updatedChannels = {
...channels,
nostr: updatedNostrConfig,
};
await runtime.config.writeConfigFile({
...cfg,
channels: updatedChannels,
channels: {
...channels,
nostr: {
...nostrConfig,
profile,
},
},
});
},
getAccountInfo: (accountId: string) => {
@@ -71,6 +59,4 @@ const plugin = {
handler: httpHandler,
});
},
};
export default plugin;
});

View File

@@ -1,5 +1,4 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { nostrPlugin } from "./src/channel.js";
export default {
plugin: nostrPlugin,
};
export default defineSetupPluginEntry(nostrPlugin);

View File

@@ -1,17 +1,11 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { signalPlugin } from "./src/channel.js";
import { setSignalRuntime } from "./src/runtime.js";
const plugin = {
export default defineChannelPluginEntry({
id: "signal",
name: "Signal",
description: "Signal channel plugin",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setSignalRuntime(api.runtime);
api.registerChannel({ plugin: signalPlugin });
},
};
export default plugin;
plugin: signalPlugin,
setRuntime: setSignalRuntime,
});

View File

@@ -1,3 +1,4 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { signalSetupPlugin } from "./src/channel.setup.js";
export default { plugin: signalSetupPlugin };
export default defineSetupPluginEntry(signalSetupPlugin);

View File

@@ -1,17 +1,11 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { slackPlugin } from "./src/channel.js";
import { setSlackRuntime } from "./src/runtime.js";
const plugin = {
export default defineChannelPluginEntry({
id: "slack",
name: "Slack",
description: "Slack channel plugin",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setSlackRuntime(api.runtime);
api.registerChannel({ plugin: slackPlugin });
},
};
export default plugin;
plugin: slackPlugin,
setRuntime: setSlackRuntime,
});

View File

@@ -1,3 +1,4 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { slackSetupPlugin } from "./src/channel.setup.js";
export default { plugin: slackSetupPlugin };
export default defineSetupPluginEntry(slackSetupPlugin);

View File

@@ -4,7 +4,7 @@ import * as mediaFetch from "../../../../src/media/fetch.js";
import type { SavedMedia } from "../../../../src/media/store.js";
import * as mediaStore from "../../../../src/media/store.js";
import { mockPinnedHostnameResolution } from "../../../../src/test-helpers/ssrf.js";
import { type FetchMock, withFetchPreconnect } from "../../../../src/test-utils/fetch-mock.js";
import { type FetchMock, withFetchPreconnect } from "../../../test-utils/fetch-mock.js";
import {
fetchWithSlackAuth,
resolveSlackAttachmentContent,

View File

@@ -1,17 +1,11 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/synology-chat";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/synology-chat";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { synologyChatPlugin } from "./src/channel.js";
import { setSynologyRuntime } from "./src/runtime.js";
const plugin = {
export default defineChannelPluginEntry({
id: "synology-chat",
name: "Synology Chat",
description: "Native Synology Chat channel plugin for OpenClaw",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setSynologyRuntime(api.runtime);
api.registerChannel({ plugin: synologyChatPlugin });
},
};
export default plugin;
plugin: synologyChatPlugin,
setRuntime: setSynologyRuntime,
});

View File

@@ -1,5 +1,4 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { synologyChatPlugin } from "./src/channel.js";
export default {
plugin: synologyChatPlugin,
};
export default defineSetupPluginEntry(synologyChatPlugin);

View File

@@ -1,5 +1,5 @@
import { describe, expect, it, vi } from "vitest";
import type { OpenClawPluginCommandDefinition } from "../../src/plugins/types.js";
import type { OpenClawPluginCommandDefinition } from "../test-utils/plugin-command.js";
import { createPluginRuntimeMock } from "../test-utils/plugin-runtime-mock.js";
import register from "./index.js";

View File

@@ -1,17 +1,12 @@
import type { ChannelPlugin, OpenClawPluginApi } from "openclaw/plugin-sdk/core";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { telegramPlugin } from "./src/channel.js";
import { setTelegramRuntime } from "./src/runtime.js";
const plugin = {
export default defineChannelPluginEntry({
id: "telegram",
name: "Telegram",
description: "Telegram channel plugin",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setTelegramRuntime(api.runtime);
api.registerChannel({ plugin: telegramPlugin as ChannelPlugin });
},
};
export default plugin;
plugin: telegramPlugin as ChannelPlugin,
setRuntime: setTelegramRuntime,
});

View File

@@ -1,3 +1,4 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { telegramSetupPlugin } from "./src/channel.setup.js";
export default { plugin: telegramSetupPlugin };
export default defineSetupPluginEntry(telegramSetupPlugin);

View File

@@ -3,7 +3,7 @@ import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../../../src/config/config.js";
import { withEnv } from "../../../src/test-utils/env.js";
import { withEnv } from "../../test-utils/env.js";
import { inspectTelegramAccount } from "./account-inspect.js";
describe("inspectTelegramAccount SecretRef resolution", () => {

View File

@@ -1,7 +1,7 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../../src/config/config.js";
import * as subsystemModule from "../../../src/logging/subsystem.js";
import { withEnv } from "../../../src/test-utils/env.js";
import { withEnv } from "../../test-utils/env.js";
import {
listTelegramAccountIds,
resetMissingDefaultWarnFlag,

View File

@@ -2,9 +2,9 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import { withEnvAsync } from "../../../src/test-utils/env.js";
import { useFrozenTime, useRealTime } from "../../../src/test-utils/frozen-time.js";
import { escapeRegExp, formatEnvelopeTimestamp } from "../../../test/helpers/envelope-timestamp.js";
import { withEnvAsync } from "../../test-utils/env.js";
import { useFrozenTime, useRealTime } from "../../test-utils/frozen-time.js";
import {
answerCallbackQuerySpy,
botCtorSpy,

View File

@@ -1,11 +1,11 @@
import { rm } from "node:fs/promises";
import type { PluginInteractiveTelegramHandlerContext } from "openclaw/plugin-sdk/core";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { expectChannelInboundContextContract as expectInboundContextContract } from "../../../src/channels/plugins/contracts/suites.js";
import {
clearPluginInteractiveHandlers,
registerPluginInteractiveHandler,
} from "../../../src/plugins/interactive.js";
import type { PluginInteractiveTelegramHandlerContext } from "../../../src/plugins/types.js";
import { escapeRegExp, formatEnvelopeTimestamp } from "../../../test/helpers/envelope-timestamp.js";
import {
answerCallbackQuerySpy,

View File

@@ -1,5 +1,5 @@
import { afterEach, type Mock, describe, expect, it, vi } from "vitest";
import { withFetchPreconnect } from "../../../src/test-utils/fetch-mock.js";
import { withFetchPreconnect } from "../../test-utils/fetch-mock.js";
import { probeTelegram, resetTelegramProbeFetcherCacheForTests } from "./probe.js";
const resolveTelegramFetch = vi.hoisted(() => vi.fn());

View File

@@ -0,0 +1 @@
export { countLines, hasBalancedFences } from "../../src/test-utils/chunk-test-helpers.js";

View File

@@ -0,0 +1 @@
export { captureEnv, withEnv, withEnvAsync } from "../../src/test-utils/env.js";

View File

@@ -0,0 +1 @@
export { withFetchPreconnect, type FetchMock } from "../../src/test-utils/fetch-mock.js";

View File

@@ -0,0 +1 @@
export { useFrozenTime, useRealTime } from "../../src/test-utils/frozen-time.js";

View File

@@ -0,0 +1 @@
export { createMockServerResponse } from "../../src/test-utils/mock-http-response.js";

View File

@@ -0,0 +1 @@
export type { OpenClawPluginCommandDefinition } from "openclaw/plugin-sdk/core";

View File

@@ -0,0 +1 @@
export { registerSingleProviderPlugin } from "../../src/test-utils/plugin-registration.js";

View File

@@ -0,0 +1,4 @@
export {
createProviderUsageFetch,
makeResponse,
} from "../../src/test-utils/provider-usage-fetch.js";

View File

@@ -0,0 +1 @@
export { withTempDir } from "../../src/test-utils/temp-dir.js";

View File

@@ -0,0 +1 @@
export { typedCases } from "../../src/test-utils/typed-cases.js";

View File

@@ -2,14 +2,12 @@ import { spawn } from "node:child_process";
import { existsSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/tlon";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/tlon";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { tlonPlugin } from "./src/channel.js";
import { setTlonRuntime } from "./src/runtime.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
// Whitelist of allowed tlon subcommands
const ALLOWED_TLON_COMMANDS = new Set([
"activity",
"channels",
@@ -24,40 +22,29 @@ const ALLOWED_TLON_COMMANDS = new Set([
"version",
]);
/**
* Find the tlon binary from the skill package
*/
let cachedTlonBinary: string | undefined;
function findTlonBinary(): string {
if (cachedTlonBinary) {
return cachedTlonBinary;
}
// Check in node_modules/.bin
const skillBin = join(__dirname, "node_modules", ".bin", "tlon");
if (existsSync(skillBin)) {
cachedTlonBinary = skillBin;
return skillBin;
}
// Check for platform-specific binary directly
const platform = process.platform;
const arch = process.arch;
const platformPkg = `@tloncorp/tlon-skill-${platform}-${arch}`;
const platformPkg = `@tloncorp/tlon-skill-${process.platform}-${process.arch}`;
const platformBin = join(__dirname, "node_modules", platformPkg, "tlon");
if (existsSync(platformBin)) {
cachedTlonBinary = platformBin;
return platformBin;
}
// Fallback to PATH
cachedTlonBinary = "tlon";
return cachedTlonBinary;
}
/**
* Shell-like argument splitter that respects quotes
*/
function shellSplit(str: string): string[] {
const args: string[] = [];
let cur = "";
@@ -92,18 +79,15 @@ function shellSplit(str: string): string[] {
}
cur += ch;
}
if (cur) args.push(cur);
if (cur) {
args.push(cur);
}
return args;
}
/**
* Run the tlon command and return the result
*/
function runTlonCommand(binary: string, args: string[]): Promise<string> {
return new Promise((resolve, reject) => {
const child = spawn(binary, args, {
env: process.env,
});
const child = spawn(binary, args, { env: process.env });
let stdout = "";
let stderr = "";
@@ -123,25 +107,20 @@ function runTlonCommand(binary: string, args: string[]): Promise<string> {
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr || `tlon exited with code ${code}`));
} else {
resolve(stdout);
return;
}
resolve(stdout);
});
});
}
const plugin = {
export default defineChannelPluginEntry({
id: "tlon",
name: "Tlon",
description: "Tlon/Urbit channel plugin",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setTlonRuntime(api.runtime);
api.registerChannel({ plugin: tlonPlugin });
if (api.registrationMode !== "full") {
return;
}
plugin: tlonPlugin,
setRuntime: setTlonRuntime,
registerFull(api) {
api.logger.debug?.("[tlon] Registering tlon tool");
api.registerTool({
name: "tlon",
@@ -164,9 +143,6 @@ const plugin = {
async execute(_id: string, params: { command: string }) {
try {
const args = shellSplit(params.command);
const tlonBinary = findTlonBinary();
// Validate first argument is a whitelisted tlon subcommand
const subcommand = args[0];
if (!ALLOWED_TLON_COMMANDS.has(subcommand)) {
return {
@@ -180,7 +156,7 @@ const plugin = {
};
}
const output = await runTlonCommand(tlonBinary, args);
const output = await runTlonCommand(findTlonBinary(), args);
return {
content: [{ type: "text" as const, text: output }],
details: undefined,
@@ -194,6 +170,4 @@ const plugin = {
},
});
},
};
export default plugin;
});

View File

@@ -1,5 +1,4 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { tlonPlugin } from "./src/channel.js";
export default {
plugin: tlonPlugin,
};
export default defineSetupPluginEntry(tlonPlugin);

View File

@@ -1,20 +1,13 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/twitch";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/twitch";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { twitchPlugin } from "./src/plugin.js";
import { setTwitchRuntime } from "./src/runtime.js";
export { monitorTwitchProvider } from "./src/monitor.js";
const plugin = {
export default defineChannelPluginEntry({
id: "twitch",
name: "Twitch",
description: "Twitch channel plugin",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setTwitchRuntime(api.runtime);
// oxlint-disable-next-line typescript/no-explicit-any
api.registerChannel({ plugin: twitchPlugin as any });
},
};
export default plugin;
description: "Twitch chat channel plugin",
plugin: twitchPlugin,
setRuntime: setTwitchRuntime,
});

View File

@@ -1,17 +1,11 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { whatsappPlugin } from "./src/channel.js";
import { setWhatsAppRuntime } from "./src/runtime.js";
const plugin = {
export default defineChannelPluginEntry({
id: "whatsapp",
name: "WhatsApp",
description: "WhatsApp channel plugin",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setWhatsAppRuntime(api.runtime);
api.registerChannel({ plugin: whatsappPlugin });
},
};
export default plugin;
plugin: whatsappPlugin,
setRuntime: setWhatsAppRuntime,
});

View File

@@ -1,3 +1,4 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { whatsappSetupPlugin } from "./src/channel.setup.js";
export default { plugin: whatsappSetupPlugin };
export default defineSetupPluginEntry(whatsappSetupPlugin);

View File

@@ -2,7 +2,7 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { captureEnv } from "../../../src/test-utils/env.js";
import { captureEnv } from "../../test-utils/env.js";
import { hasAnyWhatsAppAuth, listWhatsAppAuthDirs } from "./accounts.js";
describe("hasAnyWhatsAppAuth", () => {

View File

@@ -4,8 +4,8 @@ import fs from "node:fs/promises";
import { beforeAll, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../../src/config/config.js";
import { setLoggerOverride } from "../../../src/logging.js";
import { withEnvAsync } from "../../../src/test-utils/env.js";
import { escapeRegExp, formatEnvelopeTimestamp } from "../../../test/helpers/envelope-timestamp.js";
import { withEnvAsync } from "../../test-utils/env.js";
import {
createMockWebListener,
createWebListenerFactoryCapture,

View File

@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
import path from "node:path";
import { describe, expect, it, vi } from "vitest";
import { saveSessionStore } from "../../../../src/config/sessions.js";
import { withTempDir } from "../../../../src/test-utils/temp-dir.js";
import { withTempDir } from "../../../test-utils/temp-dir.js";
import {
debugMention,
isBotMentionedFromTargets,

View File

@@ -7,8 +7,8 @@ import { resolveStateDir } from "../../../src/config/paths.js";
import { resolvePreferredOpenClawTmpDir } from "../../../src/infra/tmp-openclaw-dir.js";
import { optimizeImageToPng } from "../../../src/media/image-ops.js";
import { mockPinnedHostnameResolution } from "../../../src/test-helpers/ssrf.js";
import { captureEnv } from "../../../src/test-utils/env.js";
import { sendVoiceMessageDiscord } from "../../discord/src/send.js";
import { captureEnv } from "../../test-utils/env.js";
import {
LocalMediaAccessError,
loadWebMedia,

View File

@@ -1,17 +1,11 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/zalo";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/zalo";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { zaloPlugin } from "./src/channel.js";
import { setZaloRuntime } from "./src/runtime.js";
const plugin = {
export default defineChannelPluginEntry({
id: "zalo",
name: "Zalo",
description: "Zalo channel plugin (Bot API)",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setZaloRuntime(api.runtime);
api.registerChannel(zaloPlugin);
},
};
export default plugin;
description: "Zalo channel plugin",
plugin: zaloPlugin,
setRuntime: setZaloRuntime,
});

View File

@@ -1,5 +1,4 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { zaloPlugin } from "./src/channel.js";
export default {
plugin: zaloPlugin,
};
export default defineSetupPluginEntry(zaloPlugin);

View File

@@ -1,21 +1,16 @@
import type { AnyAgentTool, OpenClawPluginApi } from "openclaw/plugin-sdk/zalouser";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/zalouser";
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import type { AnyAgentTool } from "openclaw/plugin-sdk/zalouser";
import { zalouserPlugin } from "./src/channel.js";
import { setZalouserRuntime } from "./src/runtime.js";
import { ZalouserToolSchema, executeZalouserTool } from "./src/tool.js";
const plugin = {
export default defineChannelPluginEntry({
id: "zalouser",
name: "Zalo Personal",
description: "Zalo personal account messaging via native zca-js integration",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
setZalouserRuntime(api.runtime);
api.registerChannel(zalouserPlugin);
if (api.registrationMode !== "full") {
return;
}
plugin: zalouserPlugin,
setRuntime: setZalouserRuntime,
registerFull(api) {
api.registerTool({
name: "zalouser",
label: "Zalo Personal",
@@ -27,6 +22,4 @@ const plugin = {
execute: executeZalouserTool,
} as AnyAgentTool);
},
};
export default plugin;
});

View File

@@ -1,5 +1,4 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { zalouserPlugin } from "./src/channel.js";
export default {
plugin: zalouserPlugin,
};
export default defineSetupPluginEntry(zalouserPlugin);

View File

@@ -443,7 +443,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: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-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",
@@ -495,6 +495,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-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",
"lint:swift": "swiftlint lint --config .swiftlint.yml && (cd apps/ios && swiftlint lint --config .swiftlint.yml)",

View File

@@ -0,0 +1,90 @@
import fs from "node:fs";
import path from "node:path";
const FORBIDDEN_PATTERNS: Array<{ pattern: RegExp; hint: string }> = [
{
pattern: /["']openclaw\/plugin-sdk["']/,
hint: "Use openclaw/plugin-sdk/<subpath> instead of the monolithic root entry.",
},
{
pattern: /["']openclaw\/plugin-sdk\/compat["']/,
hint: "Use a focused public plugin-sdk subpath instead of compat.",
},
{
pattern: /["'](?:\.\.\/)+(?:src\/test-utils\/)[^"']+["']/,
hint: "Use extensions/test-utils/* bridges for shared extension test helpers.",
},
{
pattern: /["'](?:\.\.\/)+(?:src\/plugins\/types\.js)["']/,
hint: "Use public plugin-sdk/core types or extensions/test-utils bridges instead.",
},
];
function isExtensionTestFile(filePath: string): boolean {
return /\.test\.[cm]?[jt]sx?$/u.test(filePath) || /\.e2e\.test\.[cm]?[jt]sx?$/u.test(filePath);
}
function collectExtensionTestFiles(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() && isExtensionTestFile(fullPath)) {
files.push(fullPath);
}
}
}
return files;
}
function main() {
const extensionsDir = path.join(process.cwd(), "extensions");
const files = collectExtensionTestFiles(extensionsDir);
const offenders: Array<{ file: string; hint: string }> = [];
for (const file of files) {
const content = fs.readFileSync(file, "utf8");
for (const rule of FORBIDDEN_PATTERNS) {
if (!rule.pattern.test(content)) {
continue;
}
offenders.push({ file, hint: rule.hint });
break;
}
}
if (offenders.length > 0) {
console.error(
"Extension test files must stay on extension test bridges or public plugin-sdk seams.",
);
for (const offender of offenders.toSorted((a, b) => a.file.localeCompare(b.file))) {
const relative = path.relative(process.cwd(), offender.file) || offender.file;
console.error(`- ${relative}: ${offender.hint}`);
}
process.exit(1);
}
console.log(
`OK: extension test files avoid direct core test/internal imports (${files.length} checked).`,
);
}
main();

View File

@@ -1,3 +1,13 @@
import type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
import { emptyPluginConfigSchema } from "../plugins/config-schema.js";
import type { PluginRuntime } from "../plugins/runtime/types.js";
import type {
OpenClawPluginApi,
OpenClawPluginCommandDefinition,
OpenClawPluginConfigSchema,
PluginInteractiveTelegramHandlerContext,
} from "../plugins/types.js";
export type {
AnyAgentTool,
MediaUnderstandingProviderPlugin,
@@ -31,6 +41,8 @@ export type {
ProviderAuthMethodNonInteractiveContext,
ProviderAuthMethod,
ProviderAuthResult,
OpenClawPluginCommandDefinition,
PluginInteractiveTelegramHandlerContext,
} from "../plugins/types.js";
export type { OpenClawConfig } from "../config/config.js";
export type { GatewayRequestHandlerOptions } from "../gateway/server-methods/types.js";
@@ -70,3 +82,44 @@ export {
export { buildOutboundBaseSessionKey } from "../infra/outbound/base-session-key.js";
export { normalizeOutboundThreadId } from "../infra/outbound/thread-id.js";
export { resolveThreadSessionKeys } from "../routing/session-key.js";
type DefineChannelPluginEntryOptions<TPlugin extends ChannelPlugin = ChannelPlugin> = {
id: string;
name: string;
description: string;
plugin: TPlugin;
configSchema?: () => OpenClawPluginConfigSchema;
setRuntime?: (runtime: PluginRuntime) => void;
registerFull?: (api: OpenClawPluginApi) => void;
};
// Shared channel-plugin entry boilerplate for bundled and third-party channels.
export function defineChannelPluginEntry<TPlugin extends ChannelPlugin>({
id,
name,
description,
plugin,
configSchema = emptyPluginConfigSchema,
setRuntime,
registerFull,
}: DefineChannelPluginEntryOptions<TPlugin>) {
return {
id,
name,
description,
configSchema: configSchema(),
register(api: OpenClawPluginApi) {
setRuntime?.(api.runtime);
api.registerChannel({ plugin });
if (api.registrationMode !== "full") {
return;
}
registerFull?.(api);
},
};
}
// Shared setup-entry shape so bundled channels do not duplicate `{ plugin }`.
export function defineSetupPluginEntry<TPlugin>(plugin: TPlugin) {
return { plugin };
}

View File

@@ -49,6 +49,8 @@ describe("plugin-sdk subpath exports", () => {
it("keeps core focused on generic shared exports", () => {
expect(typeof coreSdk.emptyPluginConfigSchema).toBe("function");
expect(typeof coreSdk.defineChannelPluginEntry).toBe("function");
expect(typeof coreSdk.defineSetupPluginEntry).toBe("function");
expect("runPassiveAccountLifecycle" in asExports(coreSdk)).toBe(false);
expect("createLoggerBackedRuntime" in asExports(coreSdk)).toBe(false);
expect("registerSandboxBackend" in asExports(coreSdk)).toBe(false);