Build: isolate optional bundled plugin-sdk clusters

This commit is contained in:
Vincent Koc 2026-03-18 09:54:22 -07:00
parent b4f16bad32
commit 891e2a3da8
10 changed files with 221 additions and 15 deletions

View File

@ -14,3 +14,17 @@ export const optionalBundledClusters = [
];
export const optionalBundledClusterSet = new Set(optionalBundledClusters);
export const OPTIONAL_BUNDLED_BUILD_ENV = "OPENCLAW_INCLUDE_OPTIONAL_BUNDLED";
export function isOptionalBundledCluster(cluster) {
return optionalBundledClusterSet.has(cluster);
}
export function shouldIncludeOptionalBundledClusters(env = process.env) {
return env[OPTIONAL_BUNDLED_BUILD_ENV] === "1";
}
export function shouldBuildBundledCluster(cluster, env = process.env) {
return shouldIncludeOptionalBundledClusters(env) || !isOptionalBundledCluster(cluster);
}

View File

@ -1,6 +1,12 @@
// Narrow plugin-sdk surface for the bundled googlechat plugin.
// Keep this list additive and scoped to symbols used under extensions/googlechat.
import { resolveChannelGroupRequireMention } from "./channel-policy.js";
import {
createOptionalChannelSetupAdapter,
createOptionalChannelSetupWizard,
} from "./optional-channel-setup.js";
export {
createActionGate,
jsonResult,
@ -20,7 +26,6 @@ export {
export { buildComputedAccountStatusSnapshot } from "./status-helpers.js";
export { buildChannelConfigSchema } from "../channels/plugins/config-schema.js";
export { createAccountStatusSink, runPassiveAccountLifecycle } from "./channel-lifecycle.js";
export { resolveGoogleChatGroupRequireMention } from "../../extensions/googlechat/src/group-policy.js";
export { formatPairingApproveHint } from "../channels/plugins/helpers.js";
export { resolveChannelMediaMaxBytes } from "../channels/plugins/media-limits.js";
export {
@ -65,8 +70,6 @@ export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.j
export { resolveDmGroupAccessWithLists } from "../security/dm-policy-shared.js";
export { formatDocsLink } from "../terminal/links.js";
export type { WizardPrompter } from "../wizard/prompts.js";
export { googlechatSetupAdapter } from "../../extensions/googlechat/api.js";
export { googlechatSetupWizard } from "../../extensions/googlechat/api.js";
export { resolveInboundRouteEnvelopeBuilderWithRuntime } from "./inbound-envelope.js";
export { createScopedPairingAccess } from "./pairing-access.js";
export { issuePairingChallenge } from "../pairing/pairing-challenge.js";
@ -88,3 +91,32 @@ export {
resolveWebhookTargetWithAuthOrReject,
withResolvedWebhookRequestPipeline,
} from "./webhook-targets.js";
type GoogleChatGroupContext = {
cfg: import("../config/config.js").OpenClawConfig;
accountId?: string | null;
groupId?: string | null;
};
export function resolveGoogleChatGroupRequireMention(params: GoogleChatGroupContext): boolean {
return resolveChannelGroupRequireMention({
cfg: params.cfg,
channel: "googlechat",
groupId: params.groupId,
accountId: params.accountId,
});
}
export const googlechatSetupAdapter = createOptionalChannelSetupAdapter({
channel: "googlechat",
label: "Google Chat",
npmSpec: "@openclaw/googlechat",
docsPath: "/channels/googlechat",
});
export const googlechatSetupWizard = createOptionalChannelSetupWizard({
channel: "googlechat",
label: "Google Chat",
npmSpec: "@openclaw/googlechat",
docsPath: "/channels/googlechat",
});

View File

@ -1,6 +1,11 @@
// Narrow plugin-sdk surface for the bundled matrix plugin.
// Keep this list additive and scoped to symbols used under extensions/matrix.
import {
createOptionalChannelSetupAdapter,
createOptionalChannelSetupWizard,
} from "./optional-channel-setup.js";
export {
createActionGate,
jsonResult,
@ -108,5 +113,17 @@ export {
buildProbeChannelStatusSummary,
collectStatusIssuesFromLastError,
} from "./status-helpers.js";
export { matrixSetupWizard } from "../../extensions/matrix/api.js";
export { matrixSetupAdapter } from "../../extensions/matrix/api.js";
export const matrixSetupWizard = createOptionalChannelSetupWizard({
channel: "matrix",
label: "Matrix",
npmSpec: "@openclaw/matrix",
docsPath: "/channels/matrix",
});
export const matrixSetupAdapter = createOptionalChannelSetupAdapter({
channel: "matrix",
label: "Matrix",
npmSpec: "@openclaw/matrix",
docsPath: "/channels/matrix",
});

View File

@ -1,6 +1,11 @@
// Narrow plugin-sdk surface for the bundled msteams plugin.
// Keep this list additive and scoped to symbols used under extensions/msteams.
import {
createOptionalChannelSetupAdapter,
createOptionalChannelSetupWizard,
} from "./optional-channel-setup.js";
export type { ChunkMode } from "../auto-reply/chunk.js";
export type { HistoryEntry } from "../auto-reply/reply/history.js";
export {
@ -117,5 +122,17 @@ export {
createDefaultChannelRuntimeState,
} from "./status-helpers.js";
export { normalizeStringEntries } from "../shared/string-normalization.js";
export { msteamsSetupWizard } from "../../extensions/msteams/api.js";
export { msteamsSetupAdapter } from "../../extensions/msteams/api.js";
export const msteamsSetupWizard = createOptionalChannelSetupWizard({
channel: "msteams",
label: "Microsoft Teams",
npmSpec: "@openclaw/msteams",
docsPath: "/channels/msteams",
});
export const msteamsSetupAdapter = createOptionalChannelSetupAdapter({
channel: "msteams",
label: "Microsoft Teams",
npmSpec: "@openclaw/msteams",
docsPath: "/channels/msteams",
});

View File

@ -1,6 +1,11 @@
// Narrow plugin-sdk surface for the bundled nostr plugin.
// Keep this list additive and scoped to symbols used under extensions/nostr.
import {
createOptionalChannelSetupAdapter,
createOptionalChannelSetupWizard,
} from "./optional-channel-setup.js";
export { buildChannelConfigSchema } from "../channels/plugins/config-schema.js";
export type { ChannelSetupAdapter } from "../channels/plugins/types.adapters.js";
export { formatPairingApproveHint } from "../channels/plugins/helpers.js";
@ -19,4 +24,17 @@ export {
} from "./status-helpers.js";
export { createFixedWindowRateLimiter } from "./webhook-memory-guards.js";
export { mapAllowFromEntries } from "./channel-config-helpers.js";
export { nostrSetupAdapter, nostrSetupWizard } from "../../extensions/nostr/setup-api.js";
export const nostrSetupAdapter = createOptionalChannelSetupAdapter({
channel: "nostr",
label: "Nostr",
npmSpec: "@openclaw/nostr",
docsPath: "/channels/nostr",
});
export const nostrSetupWizard = createOptionalChannelSetupWizard({
channel: "nostr",
label: "Nostr",
npmSpec: "@openclaw/nostr",
docsPath: "/channels/nostr",
});

View File

@ -0,0 +1,56 @@
import type { ChannelSetupWizard } from "../channels/plugins/setup-wizard.js";
import type { ChannelSetupAdapter } from "../channels/plugins/types.adapters.js";
import { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js";
import { formatDocsLink } from "../terminal/links.js";
type OptionalChannelSetupParams = {
channel: string;
label: string;
npmSpec?: string;
docsPath?: string;
};
function buildOptionalChannelSetupMessage(params: OptionalChannelSetupParams): string {
const installTarget = params.npmSpec ?? `the ${params.label} plugin`;
const message = [`${params.label} setup requires ${installTarget} to be installed.`];
if (params.docsPath) {
message.push(`Docs: ${formatDocsLink(params.docsPath, params.docsPath.replace(/^\/+/u, ""))}`);
}
return message.join(" ");
}
export function createOptionalChannelSetupAdapter(
params: OptionalChannelSetupParams,
): ChannelSetupAdapter {
const message = buildOptionalChannelSetupMessage(params);
return {
resolveAccountId: ({ accountId }) => accountId ?? DEFAULT_ACCOUNT_ID,
applyAccountConfig: () => {
throw new Error(message);
},
validateInput: () => message,
};
}
export function createOptionalChannelSetupWizard(
params: OptionalChannelSetupParams,
): ChannelSetupWizard {
const message = buildOptionalChannelSetupMessage(params);
return {
channel: params.channel,
status: {
configuredLabel: `${params.label} plugin installed`,
unconfiguredLabel: `install ${params.label} plugin`,
configuredHint: message,
unconfiguredHint: message,
unconfiguredScore: 0,
resolveConfigured: () => false,
resolveStatusLines: () => [message],
resolveSelectionHint: () => message,
},
credentials: [],
finalize: async () => {
throw new Error(message);
},
};
}

View File

@ -1,6 +1,11 @@
// Narrow plugin-sdk surface for the bundled tlon plugin.
// Keep this list additive and scoped to symbols used under extensions/tlon.
import {
createOptionalChannelSetupAdapter,
createOptionalChannelSetupWizard,
} from "./optional-channel-setup.js";
export type { ReplyPayload } from "../auto-reply/types.js";
export { buildChannelConfigSchema } from "../channels/plugins/config-schema.js";
export {
@ -27,4 +32,17 @@ export type { RuntimeEnv } from "../runtime.js";
export { formatDocsLink } from "../terminal/links.js";
export type { WizardPrompter } from "../wizard/prompts.js";
export { createLoggerBackedRuntime } from "./runtime.js";
export { tlonSetupAdapter, tlonSetupWizard } from "../../extensions/tlon/setup-api.js";
export const tlonSetupAdapter = createOptionalChannelSetupAdapter({
channel: "tlon",
label: "Tlon",
npmSpec: "@openclaw/tlon",
docsPath: "/channels/tlon",
});
export const tlonSetupWizard = createOptionalChannelSetupWizard({
channel: "tlon",
label: "Tlon",
npmSpec: "@openclaw/tlon",
docsPath: "/channels/tlon",
});

View File

@ -1,6 +1,11 @@
// Narrow plugin-sdk surface for the bundled twitch plugin.
// Keep this list additive and scoped to symbols used under extensions/twitch.
import {
createOptionalChannelSetupAdapter,
createOptionalChannelSetupWizard,
} from "./optional-channel-setup.js";
export type { ReplyPayload } from "../auto-reply/types.js";
export { buildChannelConfigSchema } from "../channels/plugins/config-schema.js";
export type {
@ -33,7 +38,15 @@ export type { OpenClawPluginApi } from "../plugins/types.js";
export type { RuntimeEnv } from "../runtime.js";
export { formatDocsLink } from "../terminal/links.js";
export type { WizardPrompter } from "../wizard/prompts.js";
export {
twitchSetupAdapter,
twitchSetupWizard,
} from "../../extensions/twitch/src/setup-surface.js";
export const twitchSetupAdapter = createOptionalChannelSetupAdapter({
channel: "twitch",
label: "Twitch",
npmSpec: "@openclaw/twitch",
});
export const twitchSetupWizard = createOptionalChannelSetupWizard({
channel: "twitch",
label: "Twitch",
npmSpec: "@openclaw/twitch",
});

View File

@ -1,6 +1,11 @@
// Narrow plugin-sdk surface for the bundled zalouser plugin.
// Keep this list additive and scoped to symbols used under extensions/zalouser.
import {
createOptionalChannelSetupAdapter,
createOptionalChannelSetupWizard,
} from "./optional-channel-setup.js";
export type { ReplyPayload } from "../auto-reply/types.js";
export { mergeAllowlist, summarizeMapping } from "../channels/allowlists/resolve-utils.js";
export { resolveMentionGatingWithBypass } from "../channels/mention-gating.js";
@ -53,8 +58,6 @@ export type { WizardPrompter } from "../wizard/prompts.js";
export { formatAllowFromLowercase } from "./allow-from.js";
export { resolveSenderCommandAuthorization } from "./command-auth.js";
export { resolveChannelAccountConfigBasePath } from "./config-paths.js";
export { zalouserSetupAdapter } from "../../extensions/zalouser/api.js";
export { zalouserSetupWizard } from "../../extensions/zalouser/api.js";
export {
evaluateGroupRouteAccessForPolicy,
resolveSenderScopedGroupPolicy,
@ -73,3 +76,17 @@ export {
export { formatResolvedUnresolvedNote } from "./resolution-notes.js";
export { buildBaseAccountStatusSnapshot } from "./status-helpers.js";
export { chunkTextForOutbound } from "./text-chunking.js";
export const zalouserSetupAdapter = createOptionalChannelSetupAdapter({
channel: "zalouser",
label: "Zalo Personal",
npmSpec: "@openclaw/zalouser",
docsPath: "/channels/zalouser",
});
export const zalouserSetupWizard = createOptionalChannelSetupWizard({
channel: "zalouser",
label: "Zalo Personal",
npmSpec: "@openclaw/zalouser",
docsPath: "/channels/zalouser",
});

View File

@ -1,6 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import { defineConfig, type UserConfig } from "tsdown";
import { shouldBuildBundledCluster } from "./scripts/lib/optional-bundled-clusters.mjs";
import { buildPluginSdkEntrySources } from "./scripts/lib/plugin-sdk-entries.mjs";
type InputOptionsFactory = Extract<NonNullable<UserConfig["inputOptions"]>, Function>;
@ -81,6 +82,9 @@ function listBundledPluginBuildEntries(): Record<string, string> {
if (!dirent.isDirectory()) {
continue;
}
if (!shouldBuildBundledCluster(dirent.name, process.env)) {
continue;
}
const pluginDir = path.join(extensionsRoot, dirent.name);
const manifestPath = path.join(pluginDir, "openclaw.plugin.json");