fix: repair ci test and loader regressions

This commit is contained in:
Peter Steinberger 2026-03-27 18:39:29 +00:00
parent 3cec3bd48b
commit c9d68fb9c2
23 changed files with 553 additions and 224 deletions

View File

@ -2,6 +2,12 @@ import { Type } from "@sinclair/typebox";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../runtime-api.js";
import { createChannelReplyPipeline } from "../runtime-api.js";
vi.mock("../../../src/config/bundled-channel-config-runtime.js", () => ({
getBundledChannelRuntimeMap: () => new Map(),
getBundledChannelConfigSchemaMap: () => new Map(),
}));
const { sendMessageMattermostMock, mockFetchGuard } = vi.hoisted(() => ({
sendMessageMattermostMock: vi.fn(),
mockFetchGuard: vi.fn(async (p: { url: string; init?: RequestInit }) => {
@ -19,14 +25,15 @@ vi.mock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => {
return { ...original, fetchWithSsrFGuard: mockFetchGuard };
});
import { mattermostPlugin } from "./channel.js";
import { resetMattermostReactionBotUserCacheForTests } from "./mattermost/reactions.js";
import {
createMattermostReactionFetchMock,
createMattermostTestConfig,
withMockedGlobalFetch,
} from "./mattermost/reactions.test-helpers.js";
let mattermostPlugin: typeof import("./channel.js").mattermostPlugin;
let resetMattermostReactionBotUserCacheForTests: typeof import("./mattermost/reactions.js").resetMattermostReactionBotUserCacheForTests;
type MattermostHandleAction = NonNullable<
NonNullable<typeof mattermostPlugin.actions>["handleAction"]
>;
@ -93,7 +100,10 @@ function createMattermostActionContext(
}
describe("mattermostPlugin", () => {
beforeEach(() => {
beforeEach(async () => {
vi.resetModules();
({ mattermostPlugin } = await import("./channel.js"));
({ resetMattermostReactionBotUserCacheForTests } = await import("./mattermost/reactions.js"));
sendMessageMattermostMock.mockReset();
sendMessageMattermostMock.mockResolvedValue({
messageId: "post-1",

View File

@ -28,11 +28,15 @@ vi.mock("./client.js", () => {
};
});
import { listMattermostDirectoryGroups, listMattermostDirectoryPeers } from "./directory.js";
let listMattermostDirectoryGroups: typeof import("./directory.js").listMattermostDirectoryGroups;
let listMattermostDirectoryPeers: typeof import("./directory.js").listMattermostDirectoryPeers;
describe("mattermost directory", () => {
beforeEach(() => {
beforeEach(async () => {
vi.resetModules();
vi.clearAllMocks();
({ listMattermostDirectoryGroups, listMattermostDirectoryPeers } =
await import("./directory.js"));
});
it("deduplicates channels across enabled accounts and skips failing accounts", async () => {

View File

@ -1,5 +1,5 @@
import type { ChannelSetupAdapter } from "openclaw/plugin-sdk/channel-setup";
import { createSetupInputPresenceValidator } from "openclaw/plugin-sdk/setup";
import { createSetupInputPresenceValidator } from "openclaw/plugin-sdk/setup-runtime";
import { resolveMattermostAccount, type ResolvedMattermostAccount } from "./mattermost/accounts.js";
import { normalizeMattermostBaseUrl } from "./mattermost/client.js";
import {

View File

@ -1,9 +1,12 @@
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/setup";
import { afterEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../../test/helpers/extensions/plugin-api.js";
import plugin from "../index.js";
import type { OpenClawConfig, OpenClawPluginApi } from "../runtime-api.js";
import { mattermostSetupWizard } from "./setup-surface.js";
vi.mock("../../../src/config/bundled-channel-config-runtime.js", () => ({
getBundledChannelRuntimeMap: () => new Map(),
getBundledChannelConfigSchemaMap: () => new Map(),
}));
const resolveMattermostAccount = vi.hoisted(() => vi.fn());
const normalizeMattermostBaseUrl = vi.hoisted(() => vi.fn((value: string | undefined) => value));
@ -51,7 +54,16 @@ function createApi(
});
}
let plugin: typeof import("../index.js").default;
let mattermostSetupWizard: typeof import("./setup-surface.js").mattermostSetupWizard;
describe("mattermost setup", () => {
beforeEach(async () => {
vi.resetModules();
({ default: plugin } = await import("../index.js"));
({ mattermostSetupWizard } = await import("./setup-surface.js"));
});
afterEach(() => {
resolveMattermostAccount.mockReset();
normalizeMattermostBaseUrl.mockReset();

View File

@ -1,9 +1,7 @@
import { mkdtemp, rm } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { nextcloudTalkPlugin } from "./channel.js";
import { NextcloudTalkConfigSchema } from "./config-schema.js";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
escapeNextcloudTalkMarkdown,
formatNextcloudTalkCodeBlock,
@ -28,6 +26,11 @@ import {
} from "./signature.js";
import type { CoreConfig } from "./types.js";
vi.mock("../../../src/config/bundled-channel-config-runtime.js", () => ({
getBundledChannelRuntimeMap: () => new Map(),
getBundledChannelConfigSchemaMap: () => new Map(),
}));
const fetchWithSsrFGuard = vi.hoisted(() => vi.fn());
const readFileSync = vi.hoisted(() => vi.fn());
@ -39,6 +42,14 @@ vi.mock("../runtime-api.js", async (importOriginal) => {
};
});
vi.mock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/infra-runtime")>();
return {
...actual,
fetchWithSsrFGuard,
};
});
vi.mock("node:fs", async (importOriginal) => {
const actual = await importOriginal<typeof import("node:fs")>();
return {
@ -48,6 +59,14 @@ vi.mock("node:fs", async (importOriginal) => {
});
const tempDirs: string[] = [];
let nextcloudTalkPlugin: typeof import("./channel.js").nextcloudTalkPlugin;
let NextcloudTalkConfigSchema: typeof import("./config-schema.js").NextcloudTalkConfigSchema;
beforeEach(async () => {
vi.resetModules();
({ nextcloudTalkPlugin } = await import("./channel.js"));
({ NextcloudTalkConfigSchema } = await import("./config-schema.js"));
});
afterEach(async () => {
fetchWithSsrFGuard.mockReset();
@ -406,6 +425,7 @@ describe("nextcloud talk core", () => {
});
it("resolves direct rooms from the room info endpoint", async () => {
vi.resetModules();
const release = vi.fn(async () => {});
fetchWithSsrFGuard.mockResolvedValue({
response: {
@ -445,6 +465,7 @@ describe("nextcloud talk core", () => {
});
it("reads the api password from a file and logs non-ok room info responses", async () => {
vi.resetModules();
const release = vi.fn(async () => {});
const log = vi.fn();
const error = vi.fn();
@ -480,6 +501,7 @@ describe("nextcloud talk core", () => {
});
it("returns undefined from room info without credentials or base url", async () => {
vi.resetModules();
const { resolveNextcloudTalkRoomKind } = await import("./room-info.js");
await expect(

View File

@ -2,21 +2,18 @@ import type { ChannelSetupAdapter, ChannelSetupInput } from "openclaw/plugin-sdk
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/routing";
import {
applyAccountNameToChannelSection,
createSetupInputPresenceValidator,
patchScopedAccountConfig,
} from "openclaw/plugin-sdk/setup";
import {
mergeAllowFromEntries,
createTopLevelChannelDmPolicy,
promptParsedAllowFromForAccount,
resolveSetupAccountId,
setSetupChannelEnabled,
} from "openclaw/plugin-sdk/setup";
import type { ChannelSetupDmPolicy } from "openclaw/plugin-sdk/setup";
import { type ChannelSetupWizard } from "openclaw/plugin-sdk/setup";
import { formatDocsLink } from "openclaw/plugin-sdk/setup";
import type { WizardPrompter } from "openclaw/plugin-sdk/setup";
type ChannelSetupDmPolicy,
type ChannelSetupWizard,
type WizardPrompter,
} from "openclaw/plugin-sdk/setup-runtime";
import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
import { applyAccountNameToChannelSection, patchScopedAccountConfig } from "../runtime-api.js";
import {
listNextcloudTalkAccountIds,
resolveDefaultNextcloudTalkAccountId,

View File

@ -14,19 +14,14 @@ import {
startAccountAndTrackLifecycle,
waitForStartedMocks,
} from "../../../test/helpers/extensions/start-account-lifecycle.js";
import { resolveNextcloudTalkAccount, type ResolvedNextcloudTalkAccount } from "./accounts.js";
import { nextcloudTalkPlugin } from "./channel.js";
import {
clearNextcloudTalkAccountFields,
nextcloudTalkDmPolicy,
nextcloudTalkSetupAdapter,
normalizeNextcloudTalkBaseUrl,
setNextcloudTalkAccountConfig,
validateNextcloudTalkBaseUrl,
} from "./setup-core.js";
import { nextcloudTalkSetupWizard } from "./setup-surface.js";
import type { ResolvedNextcloudTalkAccount } from "./accounts.js";
import type { CoreConfig } from "./types.js";
vi.mock("../../../src/config/bundled-channel-config-runtime.js", () => ({
getBundledChannelRuntimeMap: () => new Map(),
getBundledChannelConfigSchemaMap: () => new Map(),
}));
const hoisted = vi.hoisted(() => ({
monitorNextcloudTalkProvider: vi.fn(),
loadConfig: vi.fn(),
@ -49,9 +44,13 @@ vi.mock("./monitor.js", async () => {
};
});
vi.mock("./runtime.js", () => ({
getNextcloudTalkRuntime: () => createSendCfgThreadingRuntime(hoisted),
}));
vi.mock("./runtime.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./runtime.js")>();
return {
...actual,
getNextcloudTalkRuntime: () => createSendCfgThreadingRuntime(hoisted),
};
});
vi.mock("./accounts.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./accounts.js")>();
@ -80,7 +79,17 @@ vi.mock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => {
const accountsActual = await vi.importActual<typeof import("./accounts.js")>("./accounts.js");
hoisted.resolveNextcloudTalkAccount.mockImplementation(accountsActual.resolveNextcloudTalkAccount);
import { sendMessageNextcloudTalk, sendReactionNextcloudTalk } from "./send.js";
let resolveNextcloudTalkAccount: typeof import("./accounts.js").resolveNextcloudTalkAccount;
let nextcloudTalkPlugin: typeof import("./channel.js").nextcloudTalkPlugin;
let clearNextcloudTalkAccountFields: typeof import("./setup-core.js").clearNextcloudTalkAccountFields;
let nextcloudTalkDmPolicy: typeof import("./setup-core.js").nextcloudTalkDmPolicy;
let nextcloudTalkSetupAdapter: typeof import("./setup-core.js").nextcloudTalkSetupAdapter;
let normalizeNextcloudTalkBaseUrl: typeof import("./setup-core.js").normalizeNextcloudTalkBaseUrl;
let setNextcloudTalkAccountConfig: typeof import("./setup-core.js").setNextcloudTalkAccountConfig;
let validateNextcloudTalkBaseUrl: typeof import("./setup-core.js").validateNextcloudTalkBaseUrl;
let nextcloudTalkSetupWizard: typeof import("./setup-surface.js").nextcloudTalkSetupWizard;
let sendMessageNextcloudTalk: typeof import("./send.js").sendMessageNextcloudTalk;
let sendReactionNextcloudTalk: typeof import("./send.js").sendReactionNextcloudTalk;
function buildAccount(): ResolvedNextcloudTalkAccount {
return {
@ -114,6 +123,25 @@ function startNextcloudAccount(abortSignal?: AbortSignal) {
}
describe("nextcloud talk setup", () => {
beforeEach(async () => {
vi.resetModules();
({ resolveNextcloudTalkAccount } = await import("./accounts.js"));
({ nextcloudTalkPlugin } = await import("./channel.js"));
({
clearNextcloudTalkAccountFields,
nextcloudTalkDmPolicy,
nextcloudTalkSetupAdapter,
normalizeNextcloudTalkBaseUrl,
setNextcloudTalkAccountConfig,
validateNextcloudTalkBaseUrl,
} = await import("./setup-core.js"));
({ nextcloudTalkSetupWizard } = await import("./setup-surface.js"));
({ sendMessageNextcloudTalk, sendReactionNextcloudTalk } = await import("./send.js"));
hoisted.resolveNextcloudTalkAccount.mockImplementation(
accountsActual.resolveNextcloudTalkAccount,
);
});
afterEach(() => {
vi.clearAllMocks();
hoisted.resolveNextcloudTalkAccount.mockImplementation(

View File

@ -5,20 +5,18 @@ import {
createPatchedAccountSetupAdapter,
createSetupInputPresenceValidator,
createTopLevelChannelDmPolicy,
normalizeE164,
parseSetupEntriesAllowingWildcard,
promptParsedAllowFromForAccount,
setAccountAllowFromForChannel,
setSetupChannelEnabled,
type OpenClawConfig,
type WizardPrompter,
} from "openclaw/plugin-sdk/setup";
import type {
ChannelSetupAdapter,
ChannelSetupWizard,
ChannelSetupWizardTextInput,
} from "openclaw/plugin-sdk/setup";
type ChannelSetupAdapter,
type ChannelSetupWizard,
type ChannelSetupWizardTextInput,
} from "openclaw/plugin-sdk/setup-runtime";
import { formatCliCommand, formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
import { normalizeE164 } from "openclaw/plugin-sdk/text-runtime";
import {
listSignalAccountIds,
resolveDefaultSignalAccountId,

View File

@ -9,7 +9,7 @@ import {
getChatChannelMeta,
type ChannelPlugin,
} from "openclaw/plugin-sdk/core";
import { normalizeE164 } from "openclaw/plugin-sdk/setup";
import { normalizeE164 } from "openclaw/plugin-sdk/text-runtime";
import {
listSignalAccountIds,
resolveDefaultSignalAccountId,

View File

@ -1,8 +1,8 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const sendMessageSlackMock = vi.fn();
const hasHooksMock = vi.fn();
const runMessageSendingMock = vi.fn();
const sendMessageSlackMock = vi.hoisted(() => vi.fn());
const hasHooksMock = vi.hoisted(() => vi.fn());
const runMessageSendingMock = vi.hoisted(() => vi.fn());
vi.mock("./send.js", () => ({
sendMessageSlack: (...args: unknown[]) => sendMessageSlackMock(...args),
@ -15,7 +15,7 @@ vi.mock("openclaw/plugin-sdk/plugin-runtime", () => ({
}),
}));
import { slackOutbound } from "./outbound-adapter.js";
let slackOutbound: typeof import("./outbound-adapter.js").slackOutbound;
describe("slackOutbound", () => {
const cfg = {
@ -27,11 +27,13 @@ describe("slackOutbound", () => {
},
};
beforeEach(() => {
beforeEach(async () => {
vi.resetModules();
sendMessageSlackMock.mockReset();
hasHooksMock.mockReset();
runMessageSendingMock.mockReset();
hasHooksMock.mockReturnValue(false);
({ slackOutbound } = await import("./outbound-adapter.js"));
});
it("sends payload media first, then finalizes with blocks", async () => {

View File

@ -1,17 +1,18 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../../src/config/config.js";
const sendMessageSlackMock = vi.hoisted(() => vi.fn());
const getGlobalHookRunnerMock = vi.hoisted(() => vi.fn());
vi.mock("./send.js", () => ({
sendMessageSlack: vi.fn().mockResolvedValue({ messageId: "1234.5678", channelId: "C123" }),
sendMessageSlack: sendMessageSlackMock,
}));
vi.mock("openclaw/plugin-sdk/plugin-runtime", () => ({
getGlobalHookRunner: vi.fn(),
getGlobalHookRunner: getGlobalHookRunnerMock,
}));
import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
import { slackOutbound } from "./outbound-adapter.js";
import { sendMessageSlack } from "./send.js";
let slackOutbound: typeof import("./outbound-adapter.js").slackOutbound;
type SlackSendTextCtx = {
to: string;
@ -64,12 +65,19 @@ const expectSlackSendCalledWith = (
cfg: expect.any(Object),
...(options?.identity ? { identity: expect.objectContaining(options.identity) } : {}),
};
expect(sendMessageSlack).toHaveBeenCalledWith("C123", text, expect.objectContaining(expected));
expect(sendMessageSlackMock).toHaveBeenCalledWith(
"C123",
text,
expect.objectContaining(expected),
);
};
describe("slack outbound hook wiring", () => {
beforeEach(() => {
beforeEach(async () => {
vi.resetModules();
vi.clearAllMocks();
sendMessageSlackMock.mockResolvedValue({ messageId: "1234.5678", channelId: "C123" });
({ slackOutbound } = await import("./outbound-adapter.js"));
});
afterEach(() => {
@ -77,14 +85,14 @@ describe("slack outbound hook wiring", () => {
});
it("calls send without hooks when no hooks registered", async () => {
vi.mocked(getGlobalHookRunner).mockReturnValue(null);
getGlobalHookRunnerMock.mockReturnValue(null);
await sendSlackTextWithDefaults({ text: "hello" });
expectSlackSendCalledWith("hello");
});
it("forwards identity opts when present", async () => {
vi.mocked(getGlobalHookRunner).mockReturnValue(null);
getGlobalHookRunnerMock.mockReturnValue(null);
await sendSlackTextWithDefaults({
text: "hello",
@ -101,7 +109,7 @@ describe("slack outbound hook wiring", () => {
});
it("forwards icon_emoji only when icon_url is absent", async () => {
vi.mocked(getGlobalHookRunner).mockReturnValue(null);
getGlobalHookRunnerMock.mockReturnValue(null);
await sendSlackTextWithDefaults({
text: "hello",
@ -118,7 +126,7 @@ describe("slack outbound hook wiring", () => {
hasHooks: vi.fn().mockReturnValue(true),
runMessageSending: vi.fn().mockResolvedValue(undefined),
};
vi.mocked(getGlobalHookRunner).mockReturnValue(mockRunner as never);
getGlobalHookRunnerMock.mockReturnValue(mockRunner);
await sendSlackTextWithDefaults({ text: "hello" });
@ -135,11 +143,11 @@ describe("slack outbound hook wiring", () => {
hasHooks: vi.fn().mockReturnValue(true),
runMessageSending: vi.fn().mockResolvedValue({ cancel: true }),
};
vi.mocked(getGlobalHookRunner).mockReturnValue(mockRunner as never);
getGlobalHookRunnerMock.mockReturnValue(mockRunner);
const result = await sendSlackTextWithDefaults({ text: "hello" });
expect(sendMessageSlack).not.toHaveBeenCalled();
expect(sendMessageSlackMock).not.toHaveBeenCalled();
expect(result.channel).toBe("slack");
});
@ -148,7 +156,7 @@ describe("slack outbound hook wiring", () => {
hasHooks: vi.fn().mockReturnValue(true),
runMessageSending: vi.fn().mockResolvedValue({ content: "modified" }),
};
vi.mocked(getGlobalHookRunner).mockReturnValue(mockRunner as never);
getGlobalHookRunnerMock.mockReturnValue(mockRunner);
await sendSlackTextWithDefaults({ text: "original" });
expectSlackSendCalledWith("modified");
@ -159,11 +167,11 @@ describe("slack outbound hook wiring", () => {
hasHooks: vi.fn().mockReturnValue(false),
runMessageSending: vi.fn(),
};
vi.mocked(getGlobalHookRunner).mockReturnValue(mockRunner as never);
getGlobalHookRunnerMock.mockReturnValue(mockRunner);
await sendSlackTextWithDefaults({ text: "hello" });
expect(mockRunner.runMessageSending).not.toHaveBeenCalled();
expect(sendMessageSlack).toHaveBeenCalled();
expect(sendMessageSlackMock).toHaveBeenCalled();
});
});

View File

@ -2,15 +2,29 @@ import type { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/ch
import { beforeEach, describe, expect, it, vi } from "vitest";
const createChannelPairingChallengeIssuerMock = vi.hoisted(() => vi.fn());
const upsertChannelPairingRequestMock = vi.hoisted(() => vi.fn(async () => undefined));
const upsertChannelPairingRequestMock = vi.hoisted(() =>
vi.fn(async () => ({ code: "123456", created: true })),
);
const withTelegramApiErrorLoggingMock = vi.hoisted(() => vi.fn(async ({ fn }) => await fn()));
const createPairingPrefixStripperMock = vi.hoisted(
() => (prefix: RegExp, normalize: (value: string) => string) => (value: string) =>
normalize(value.replace(prefix, "")),
);
vi.mock("openclaw/plugin-sdk/channel-pairing", () => ({
createChannelPairingChallengeIssuer: createChannelPairingChallengeIssuerMock,
createPairingPrefixStripper: createPairingPrefixStripperMock,
createLoggedPairingApprovalNotifier: () => undefined,
createTextPairingAdapter: () => undefined,
createChannelPairingController: () => ({}),
}));
vi.mock("openclaw/plugin-sdk/conversation-runtime", () => ({
upsertChannelPairingRequest: upsertChannelPairingRequestMock,
createStaticReplyToModeResolver: (mode: string) => () => mode,
createTopLevelChannelReplyToModeResolver: () => () => "off",
createScopedAccountReplyToModeResolver: () => () => "off",
resolvePinnedMainDmOwnerFromAllowlist: () => undefined,
}));
vi.mock("./api-logging.js", () => ({
@ -56,6 +70,7 @@ describe("enforceTelegramDmAccess", () => {
accountId: "main",
bot: bot as never,
logger: { info: vi.fn() },
upsertPairingRequest: upsertChannelPairingRequestMock,
});
expect(allowed).toBe(true);
@ -72,6 +87,7 @@ describe("enforceTelegramDmAccess", () => {
accountId: "main",
bot: { api: { sendMessage: vi.fn(async () => undefined) } } as never,
logger: { info: vi.fn() },
upsertPairingRequest: upsertChannelPairingRequestMock,
});
expect(allowed).toBe(false);
@ -87,6 +103,7 @@ describe("enforceTelegramDmAccess", () => {
accountId: "main",
bot: { api: { sendMessage: vi.fn(async () => undefined) } } as never,
logger: { info: vi.fn() },
upsertPairingRequest: upsertChannelPairingRequestMock,
});
expect(allowed).toBe(true);
@ -116,6 +133,7 @@ describe("enforceTelegramDmAccess", () => {
accountId: "main",
bot: { api: { sendMessage } } as never,
logger,
upsertPairingRequest: upsertChannelPairingRequestMock,
});
expect(allowed).toBe(false);

View File

@ -56,20 +56,24 @@ vi.mock("undici", () => ({
setGlobalDispatcher,
}));
vi.mock("openclaw/plugin-sdk/runtime-env", () => ({
createSubsystemLogger: () => ({
info: loggerInfo,
debug: loggerDebug,
warn: vi.fn(),
error: vi.fn(),
child: () => ({
vi.mock("openclaw/plugin-sdk/runtime-env", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/runtime-env")>();
return {
...actual,
createSubsystemLogger: () => ({
info: loggerInfo,
debug: loggerDebug,
warn: vi.fn(),
error: vi.fn(),
child: () => ({
info: loggerInfo,
debug: loggerDebug,
warn: vi.fn(),
error: vi.fn(),
}),
}),
}),
}));
};
});
let resolveFetch: typeof import("../../../src/infra/fetch.js").resolveFetch;
let resolveTelegramFetch: typeof import("./fetch.js").resolveTelegramFetch;

View File

@ -1,7 +1,14 @@
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { createJiti } from "jiti";
import { buildChannelConfigSchema } from "../src/channels/plugins/config-schema.js";
import {
buildPluginLoaderJitiOptions,
resolvePluginSdkAliasFile,
resolvePluginSdkScopedAliasMap,
} from "../src/plugins/sdk-alias.js";
function isBuiltChannelConfigSchema(
value: unknown,
@ -182,9 +189,88 @@ export async function loadChannelConfigSurfaceModule(
options?: { repoRoot?: string },
): Promise<{ schema: Record<string, unknown>; uiHints?: Record<string, unknown> } | null> {
const repoRoot = options?.repoRoot ?? resolveRepoRoot();
const loaderRepoRoot = resolveRepoRoot();
const bunBuildChannelConfigSchemaUrl = pathToFileURL(
path.join(loaderRepoRoot, "src/channels/plugins/config-schema.ts"),
).href;
const loadViaBun = (candidatePath: string) => {
const script = `
import { pathToFileURL } from "node:url";
const { buildChannelConfigSchema } = await import(${JSON.stringify(bunBuildChannelConfigSchemaUrl)});
const modulePath = process.env.OPENCLAW_CONFIG_SURFACE_MODULE;
if (!modulePath) {
throw new Error("missing OPENCLAW_CONFIG_SURFACE_MODULE");
}
const imported = await import(pathToFileURL(modulePath).href);
const isBuilt = (value) => Boolean(
value &&
typeof value === "object" &&
value.schema &&
typeof value.schema === "object"
);
const resolve = (mod) => {
for (const [name, value] of Object.entries(mod)) {
if (name.endsWith("ChannelConfigSchema") && isBuilt(value)) return value;
}
for (const [name, value] of Object.entries(mod)) {
if (!name.endsWith("ConfigSchema") || name.endsWith("AccountConfigSchema")) continue;
if (isBuilt(value)) return value;
if (value && typeof value === "object") return buildChannelConfigSchema(value);
}
for (const value of Object.values(mod)) {
if (isBuilt(value)) return value;
}
return null;
};
process.stdout.write(JSON.stringify(resolve(imported)));
`;
const result = spawnSync("bun", ["-e", script], {
cwd: repoRoot,
encoding: "utf8",
env: {
...process.env,
OPENCLAW_CONFIG_SURFACE_MODULE: path.resolve(candidatePath),
},
});
if (result.status !== 0) {
throw new Error(result.stderr || result.stdout || `bun loader failed for ${candidatePath}`);
}
return JSON.parse(result.stdout || "null") as {
schema: Record<string, unknown>;
uiHints?: Record<string, unknown>;
} | null;
};
const loadViaJiti = (candidatePath: string) => {
const resolvedPath = path.resolve(candidatePath);
const pluginSdkAlias = resolvePluginSdkAliasFile({
srcFile: "root-alias.cjs",
distFile: "root-alias.cjs",
modulePath: resolvedPath,
pluginSdkResolution: "src",
});
const aliasMap = {
...(pluginSdkAlias ? { "openclaw/plugin-sdk": pluginSdkAlias } : {}),
...resolvePluginSdkScopedAliasMap({
modulePath: resolvedPath,
pluginSdkResolution: "src",
}),
};
const jiti = createJiti(import.meta.url, {
...buildPluginLoaderJitiOptions(aliasMap),
interopDefault: true,
tryNative: false,
moduleCache: false,
fsCache: false,
});
return jiti(resolvedPath) as Record<string, unknown>;
};
try {
const imported = (await import(pathToFileURL(modulePath).href)) as Record<string, unknown>;
const bunLoaded = loadViaBun(modulePath);
if (bunLoaded && isBuiltChannelConfigSchema(bunLoaded)) {
return bunLoaded;
}
const imported = loadViaJiti(modulePath);
return resolveConfigSchemaExport(imported);
} catch (error) {
if (!shouldRetryViaIsolatedCopy(error)) {
@ -193,9 +279,11 @@ export async function loadChannelConfigSurfaceModule(
const isolatedCopy = copyModuleImportGraphWithoutNodeModules({ modulePath, repoRoot });
try {
const imported = (await import(
`${pathToFileURL(isolatedCopy.copiedModulePath).href}?isolated=${Date.now()}`
)) as Record<string, unknown>;
const bunLoaded = loadViaBun(isolatedCopy.copiedModulePath);
if (bunLoaded && isBuiltChannelConfigSchema(bunLoaded)) {
return bunLoaded;
}
const imported = loadViaJiti(isolatedCopy.copiedModulePath);
return resolveConfigSchemaExport(imported);
} finally {
isolatedCopy.cleanup();

135
src/channels/chat-meta.ts Normal file
View File

@ -0,0 +1,135 @@
import { CHAT_CHANNEL_ORDER, type ChatChannelId } from "./ids.js";
import type { ChannelMeta } from "./plugins/types.js";
export type ChatChannelMeta = ChannelMeta;
const WEBSITE_URL = "https://openclaw.ai";
const CHAT_CHANNEL_META: Record<ChatChannelId, ChannelMeta> = {
telegram: {
id: "telegram",
label: "Telegram",
selectionLabel: "Telegram (Bot API)",
detailLabel: "Telegram Bot",
docsPath: "/channels/telegram",
docsLabel: "telegram",
blurb: "simplest way to get started — register a bot with @BotFather and get going.",
systemImage: "paperplane",
selectionDocsPrefix: "",
selectionDocsOmitLabel: true,
selectionExtras: [WEBSITE_URL],
},
whatsapp: {
id: "whatsapp",
label: "WhatsApp",
selectionLabel: "WhatsApp (QR link)",
detailLabel: "WhatsApp Web",
docsPath: "/channels/whatsapp",
docsLabel: "whatsapp",
blurb: "works with your own number; recommend a separate phone + eSIM.",
systemImage: "message",
},
discord: {
id: "discord",
label: "Discord",
selectionLabel: "Discord (Bot API)",
detailLabel: "Discord Bot",
docsPath: "/channels/discord",
docsLabel: "discord",
blurb: "very well supported right now.",
systemImage: "bubble.left.and.bubble.right",
},
irc: {
id: "irc",
label: "IRC",
selectionLabel: "IRC (Server + Nick)",
detailLabel: "IRC",
docsPath: "/channels/irc",
docsLabel: "irc",
blurb: "classic IRC networks with DM/channel routing and pairing controls.",
systemImage: "network",
},
googlechat: {
id: "googlechat",
label: "Google Chat",
selectionLabel: "Google Chat (Chat API)",
detailLabel: "Google Chat",
docsPath: "/channels/googlechat",
docsLabel: "googlechat",
blurb: "Google Workspace Chat app with HTTP webhook.",
systemImage: "message.badge",
},
slack: {
id: "slack",
label: "Slack",
selectionLabel: "Slack (Socket Mode)",
detailLabel: "Slack Bot",
docsPath: "/channels/slack",
docsLabel: "slack",
blurb: "supported (Socket Mode).",
systemImage: "number",
},
signal: {
id: "signal",
label: "Signal",
selectionLabel: "Signal (signal-cli)",
detailLabel: "Signal REST",
docsPath: "/channels/signal",
docsLabel: "signal",
blurb: 'signal-cli linked device; more setup (David Reagans: "Hop on Discord.").',
systemImage: "antenna.radiowaves.left.and.right",
},
imessage: {
id: "imessage",
label: "iMessage",
selectionLabel: "iMessage (imsg)",
detailLabel: "iMessage",
docsPath: "/channels/imessage",
docsLabel: "imessage",
blurb: "this is still a work in progress.",
systemImage: "message.fill",
},
line: {
id: "line",
label: "LINE",
selectionLabel: "LINE (Messaging API)",
detailLabel: "LINE Bot",
docsPath: "/channels/line",
docsLabel: "line",
blurb: "LINE Messaging API webhook bot.",
systemImage: "message",
},
};
export const CHAT_CHANNEL_ALIASES: Record<string, ChatChannelId> = {
imsg: "imessage",
"internet-relay-chat": "irc",
"google-chat": "googlechat",
gchat: "googlechat",
};
function normalizeChannelKey(raw?: string | null): string | undefined {
const normalized = raw?.trim().toLowerCase();
return normalized || undefined;
}
export function listChatChannels(): ChatChannelMeta[] {
return CHAT_CHANNEL_ORDER.map((id) => CHAT_CHANNEL_META[id]);
}
export function listChatChannelAliases(): string[] {
return Object.keys(CHAT_CHANNEL_ALIASES);
}
export function getChatChannelMeta(id: ChatChannelId): ChatChannelMeta {
return CHAT_CHANNEL_META[id];
}
export function normalizeChatChannelId(raw?: string | null): ChatChannelId | null {
const normalized = normalizeChannelKey(raw);
if (!normalized) {
return null;
}
const resolved = CHAT_CHANNEL_ALIASES[normalized] ?? normalized;
return CHAT_CHANNEL_ORDER.includes(resolved) ? resolved : null;
}

View File

@ -1,14 +1,17 @@
import { getActivePluginRegistry } from "../plugins/runtime.js";
import {
CHAT_CHANNEL_ALIASES,
getChatChannelMeta,
listChatChannelAliases,
listChatChannels,
normalizeChatChannelId,
type ChatChannelMeta,
} from "./chat-meta.js";
import { CHANNEL_IDS, CHAT_CHANNEL_ORDER, type ChatChannelId } from "./ids.js";
import type { ChannelMeta } from "./plugins/types.js";
import type { ChannelId } from "./plugins/types.js";
export { CHANNEL_IDS, CHAT_CHANNEL_ORDER } from "./ids.js";
export type { ChatChannelId } from "./ids.js";
export type ChatChannelMeta = ChannelMeta;
const WEBSITE_URL = "https://openclaw.ai";
type RegisteredChannelPluginEntry = {
plugin: {
id?: string | null;
@ -36,134 +39,17 @@ function findRegisteredChannelPluginEntry(
});
}
const CHAT_CHANNEL_META: Record<ChatChannelId, ChannelMeta> = {
telegram: {
id: "telegram",
label: "Telegram",
selectionLabel: "Telegram (Bot API)",
detailLabel: "Telegram Bot",
docsPath: "/channels/telegram",
docsLabel: "telegram",
blurb: "simplest way to get started — register a bot with @BotFather and get going.",
systemImage: "paperplane",
selectionDocsPrefix: "",
selectionDocsOmitLabel: true,
selectionExtras: [WEBSITE_URL],
},
whatsapp: {
id: "whatsapp",
label: "WhatsApp",
selectionLabel: "WhatsApp (QR link)",
detailLabel: "WhatsApp Web",
docsPath: "/channels/whatsapp",
docsLabel: "whatsapp",
blurb: "works with your own number; recommend a separate phone + eSIM.",
systemImage: "message",
},
discord: {
id: "discord",
label: "Discord",
selectionLabel: "Discord (Bot API)",
detailLabel: "Discord Bot",
docsPath: "/channels/discord",
docsLabel: "discord",
blurb: "very well supported right now.",
systemImage: "bubble.left.and.bubble.right",
},
irc: {
id: "irc",
label: "IRC",
selectionLabel: "IRC (Server + Nick)",
detailLabel: "IRC",
docsPath: "/channels/irc",
docsLabel: "irc",
blurb: "classic IRC networks with DM/channel routing and pairing controls.",
systemImage: "network",
},
googlechat: {
id: "googlechat",
label: "Google Chat",
selectionLabel: "Google Chat (Chat API)",
detailLabel: "Google Chat",
docsPath: "/channels/googlechat",
docsLabel: "googlechat",
blurb: "Google Workspace Chat app with HTTP webhook.",
systemImage: "message.badge",
},
slack: {
id: "slack",
label: "Slack",
selectionLabel: "Slack (Socket Mode)",
detailLabel: "Slack Bot",
docsPath: "/channels/slack",
docsLabel: "slack",
blurb: "supported (Socket Mode).",
systemImage: "number",
},
signal: {
id: "signal",
label: "Signal",
selectionLabel: "Signal (signal-cli)",
detailLabel: "Signal REST",
docsPath: "/channels/signal",
docsLabel: "signal",
blurb: 'signal-cli linked device; more setup (David Reagans: "Hop on Discord.").',
systemImage: "antenna.radiowaves.left.and.right",
},
imessage: {
id: "imessage",
label: "iMessage",
selectionLabel: "iMessage (imsg)",
detailLabel: "iMessage",
docsPath: "/channels/imessage",
docsLabel: "imessage",
blurb: "this is still a work in progress.",
systemImage: "message.fill",
},
line: {
id: "line",
label: "LINE",
selectionLabel: "LINE (Messaging API)",
detailLabel: "LINE Bot",
docsPath: "/channels/line",
docsLabel: "line",
blurb: "LINE Messaging API webhook bot.",
systemImage: "message",
},
};
export const CHAT_CHANNEL_ALIASES: Record<string, ChatChannelId> = {
imsg: "imessage",
"internet-relay-chat": "irc",
"google-chat": "googlechat",
gchat: "googlechat",
};
const normalizeChannelKey = (raw?: string | null): string | undefined => {
const normalized = raw?.trim().toLowerCase();
return normalized || undefined;
};
export function listChatChannels(): ChatChannelMeta[] {
return CHAT_CHANNEL_ORDER.map((id) => CHAT_CHANNEL_META[id]);
}
export function listChatChannelAliases(): string[] {
return Object.keys(CHAT_CHANNEL_ALIASES);
}
export function getChatChannelMeta(id: ChatChannelId): ChatChannelMeta {
return CHAT_CHANNEL_META[id];
}
export function normalizeChatChannelId(raw?: string | null): ChatChannelId | null {
const normalized = normalizeChannelKey(raw);
if (!normalized) {
return null;
}
const resolved = CHAT_CHANNEL_ALIASES[normalized] ?? normalized;
return CHAT_CHANNEL_ORDER.includes(resolved) ? resolved : null;
}
export {
CHAT_CHANNEL_ALIASES,
getChatChannelMeta,
listChatChannelAliases,
listChatChannels,
normalizeChatChannelId,
};
// Channel docking: prefer this helper in shared code. Importing from
// `src/channels/plugins/*` can eagerly load channel implementations.

View File

@ -26,6 +26,10 @@ import type { OpenClawConfig } from "../config/config.js";
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
import { normalizeStringEntries } from "../shared/string-normalization.js";
// `node --import tsx` can emit `__name(...)` wrappers for function expressions.
// Keep a local no-op helper so direct TS loads (used by config-surface tooling) stay stable.
const __name = <T extends Function>(value: T): T => value;
export {
authorizeConfigWrite,
canBypassConfigWritePolicy,

View File

@ -22,4 +22,4 @@ export {
export { formatPairingApproveHint } from "../channels/plugins/helpers.js";
export { PAIRING_APPROVED_MESSAGE } from "../channels/plugins/pairing-message.js";
export { getChatChannelMeta } from "../channels/registry.js";
export { getChatChannelMeta } from "../channels/chat-meta.js";

View File

@ -1,3 +1,4 @@
import { getChatChannelMeta } from "../channels/chat-meta.js";
import {
createScopedAccountReplyToModeResolver,
createTopLevelChannelReplyToModeResolver,
@ -10,20 +11,18 @@ import type {
import type {
ChannelMessagingAdapter,
ChannelOutboundSessionRoute,
ChannelPollResult,
ChannelThreadingAdapter,
} from "../channels/plugins/types.core.js";
import type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
import { getChatChannelMeta } from "../channels/registry.js";
import type { OpenClawConfig } from "../config/config.js";
import type { ReplyToMode } from "../config/types.base.js";
import { buildOutboundBaseSessionKey } from "../infra/outbound/base-session-key.js";
import type { OutboundDeliveryResult } from "../infra/outbound/deliver.js";
import { emptyPluginConfigSchema } from "../plugins/config-schema.js";
import type { PluginRuntime } from "../plugins/runtime/types.js";
import type { OpenClawPluginApi, OpenClawPluginConfigSchema } from "../plugins/types.js";
import { createScopedDmSecurityResolver } from "./channel-config-helpers.js";
import { createTextPairingAdapter } from "./channel-pairing.js";
import { createAttachedChannelResultAdapter } from "./channel-send-result.js";
import { definePluginEntry } from "./plugin-entry.js";
export type {
AnyAgentTool,
@ -75,6 +74,25 @@ export type {
ChannelOutboundSessionRoute,
ChannelMessagingAdapter,
} from "../channels/plugins/types.core.js";
function createInlineTextPairingAdapter(params: {
idLabel: string;
message: string;
normalizeAllowEntry?: ChannelPairingAdapter["normalizeAllowEntry"];
notify: (
params: Parameters<NonNullable<ChannelPairingAdapter["notifyApproval"]>>[0] & {
message: string;
},
) => Promise<void> | void;
}): ChannelPairingAdapter {
return {
idLabel: params.idLabel,
normalizeAllowEntry: params.normalizeAllowEntry,
notifyApproval: async (ctx) => {
await params.notify({ ...ctx, message: params.message });
},
};
}
export type {
ProviderUsageSnapshot,
UsageProviderId,
@ -103,7 +121,7 @@ export {
formatPairingApproveHint,
parseOptionalDelimitedEntries,
} from "../channels/plugins/helpers.js";
export { getChatChannelMeta } from "../channels/registry.js";
export { getChatChannelMeta } from "../channels/chat-meta.js";
export {
channelTargetSchema,
channelTargetsSchema,
@ -200,7 +218,12 @@ type DefineChannelPluginEntryOptions<TPlugin = ChannelPlugin> = {
registerFull?: (api: OpenClawPluginApi) => void;
};
type DefinedChannelPluginEntry<TPlugin> = ReturnType<typeof definePluginEntry> & {
type DefinedChannelPluginEntry<TPlugin> = {
id: string;
name: string;
description: string;
configSchema: OpenClawPluginConfigSchema;
register: (api: OpenClawPluginApi) => void;
channelPlugin: TPlugin;
setChannelRuntime?: (runtime: PluginRuntime) => void;
};
@ -257,11 +280,12 @@ export function defineChannelPluginEntry<TPlugin>({
setRuntime,
registerFull,
}: DefineChannelPluginEntryOptions<TPlugin>): DefinedChannelPluginEntry<TPlugin> {
const entry = definePluginEntry({
const resolvedConfigSchema = typeof configSchema === "function" ? configSchema() : configSchema;
const entry = {
id,
name,
description,
configSchema,
configSchema: resolvedConfigSchema,
register(api: OpenClawPluginApi) {
setRuntime?.(api.runtime);
api.registerChannel({ plugin: plugin as ChannelPlugin });
@ -270,7 +294,7 @@ export function defineChannelPluginEntry<TPlugin>({
}
registerFull?.(api);
},
});
};
return {
...entry,
channelPlugin: plugin,
@ -320,7 +344,11 @@ type ChatChannelPairingOptions = {
idLabel: string;
message: string;
normalizeAllowEntry?: ChannelPairingAdapter["normalizeAllowEntry"];
notify: Parameters<typeof createTextPairingAdapter>[0]["notify"];
notify: (
params: Parameters<NonNullable<ChannelPairingAdapter["notifyApproval"]>>[0] & {
message: string;
},
) => Promise<void> | void;
};
};
@ -346,9 +374,47 @@ type ChatChannelThreadingOptions<TResolvedAccount> =
type ChatChannelAttachedOutboundOptions = {
base: Omit<ChannelOutboundAdapter, "sendText" | "sendMedia" | "sendPoll">;
attachedResults: Parameters<typeof createAttachedChannelResultAdapter>[0];
attachedResults: {
channel: string;
sendText?: (
ctx: Parameters<NonNullable<ChannelOutboundAdapter["sendText"]>>[0],
) => MaybePromise<Omit<OutboundDeliveryResult, "channel">>;
sendMedia?: (
ctx: Parameters<NonNullable<ChannelOutboundAdapter["sendMedia"]>>[0],
) => MaybePromise<Omit<OutboundDeliveryResult, "channel">>;
sendPoll?: (
ctx: Parameters<NonNullable<ChannelOutboundAdapter["sendPoll"]>>[0],
) => MaybePromise<Omit<ChannelPollResult, "channel">>;
};
};
type MaybePromise<T> = T | Promise<T>;
function createInlineAttachedChannelResultAdapter(
params: ChatChannelAttachedOutboundOptions["attachedResults"],
) {
return {
sendText: params.sendText
? async (ctx: Parameters<NonNullable<ChannelOutboundAdapter["sendText"]>>[0]) => ({
channel: params.channel,
...(await params.sendText!(ctx)),
})
: undefined,
sendMedia: params.sendMedia
? async (ctx: Parameters<NonNullable<ChannelOutboundAdapter["sendMedia"]>>[0]) => ({
channel: params.channel,
...(await params.sendMedia!(ctx)),
})
: undefined,
sendPoll: params.sendPoll
? async (ctx: Parameters<NonNullable<ChannelOutboundAdapter["sendPoll"]>>[0]) => ({
channel: params.channel,
...(await params.sendPoll!(ctx)),
})
: undefined,
} satisfies Pick<ChannelOutboundAdapter, "sendText" | "sendMedia" | "sendPoll">;
}
function resolveChatChannelSecurity<TResolvedAccount extends { accountId?: string | null }>(
security:
| ChannelSecurityAdapter<TResolvedAccount>
@ -376,7 +442,7 @@ function resolveChatChannelPairing(
if (!("text" in pairing)) {
return pairing;
}
return createTextPairingAdapter(pairing.text);
return createInlineTextPairingAdapter(pairing.text);
}
function resolveChatChannelThreading<TResolvedAccount>(
@ -415,7 +481,7 @@ function resolveChatChannelOutbound(
}
return {
...outbound.base,
...createAttachedChannelResultAdapter(outbound.attachedResults),
...createInlineAttachedChannelResultAdapter(outbound.attachedResults),
};
}

View File

@ -36,6 +36,7 @@ export {
export {
applyAccountNameToChannelSection,
applySetupAccountConfigPatch,
createSetupInputPresenceValidator,
migrateBaseNameToDefaultAccount,
} from "../channels/plugins/setup-helpers.js";
export { createAccountStatusSink } from "./channel-lifecycle.js";

View File

@ -27,11 +27,14 @@ export {
} from "../channels/plugins/setup-wizard-helpers.js";
export {
applyAccountNameToChannelSection,
createSetupInputPresenceValidator,
patchScopedAccountConfig,
} from "../channels/plugins/setup-helpers.js";
export { createAccountListHelpers } from "../channels/plugins/account-helpers.js";
export type { ChannelGroupContext, ChannelSetupInput } from "../channels/plugins/types.js";
export type { ChannelSetupDmPolicy } from "../channels/plugins/setup-wizard-types.js";
export type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
export type { ChannelSetupWizard } from "../channels/plugins/setup-wizard.js";
export { createChannelReplyPipeline } from "./channel-reply-pipeline.js";
export type { OpenClawConfig } from "../config/config.js";
export { mapAllowFromEntries } from "./channel-config-helpers.js";
@ -78,12 +81,12 @@ export type { PluginRuntime } from "../plugins/runtime/types.js";
export type { OpenClawPluginApi } from "../plugins/types.js";
export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
export type { RuntimeEnv } from "../runtime.js";
export type { WizardPrompter } from "../wizard/prompts.js";
export {
readStoreAllowFromForDmPolicy,
resolveDmGroupAccessWithCommandGate,
} from "../security/dm-policy-shared.js";
export { formatDocsLink } from "../terminal/links.js";
export type { WizardPrompter } from "../wizard/prompts.js";
export {
listConfiguredAccountIds,
resolveAccountWithDefaultFallback,
@ -103,3 +106,9 @@ export {
buildBaseChannelStatusSummary,
buildRuntimeAccountStatusSnapshot,
} from "./status-helpers.js";
export {
createTopLevelChannelDmPolicy,
promptParsedAllowFromForAccount,
resolveSetupAccountId,
setSetupChannelEnabled,
} from "../channels/plugins/setup-wizard-helpers.js";

View File

@ -5,22 +5,38 @@ export type { ChannelSetupDmPolicy } from "../channels/plugins/setup-wizard-type
export type {
ChannelSetupWizard,
ChannelSetupWizardAllowFromEntry,
ChannelSetupWizardTextInput,
} from "../channels/plugins/setup-wizard.js";
export { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js";
export { createEnvPatchedAccountSetupAdapter } from "../channels/plugins/setup-helpers.js";
export {
createEnvPatchedAccountSetupAdapter,
createPatchedAccountSetupAdapter,
createSetupInputPresenceValidator,
} from "../channels/plugins/setup-helpers.js";
export {
createAccountScopedAllowFromSection,
createAccountScopedGroupAccessSection,
createTopLevelChannelDmPolicy,
createLegacyCompatChannelDmPolicy,
createStandardChannelSetupStatus,
mergeAllowFromEntries,
parseSetupEntriesAllowingWildcard,
parseMentionOrPrefixedId,
patchChannelConfigForAccount,
promptLegacyChannelAllowFromForAccount,
promptParsedAllowFromForAccount,
resolveEntriesWithOptionalToken,
resolveSetupAccountId,
setAccountAllowFromForChannel,
setSetupChannelEnabled,
} from "../channels/plugins/setup-wizard-helpers.js";
export { createAllowlistSetupWizardProxy } from "../channels/plugins/setup-wizard-proxy.js";
export {
createCliPathTextInput,
createDelegatedTextInputShouldPrompt,
} from "../channels/plugins/setup-wizard-binary.js";
export { createDelegatedSetupWizardProxy } from "../channels/plugins/setup-wizard-proxy.js";

View File

@ -11,6 +11,11 @@ export type {
OpenClawPluginApi,
PluginRuntime,
} from "./channel-plugin-common.js";
export type { ChannelSetupAdapter } from "../channels/plugins/types.adapters.js";
export type {
ChannelSetupWizard,
ChannelSetupWizardTextInput,
} from "../channels/plugins/setup-wizard.js";
export {
DEFAULT_ACCOUNT_ID,
PAIRING_APPROVED_MESSAGE,
@ -24,6 +29,10 @@ export {
normalizeAccountId,
setAccountEnabledInConfigSection,
} from "./channel-plugin-common.js";
export {
createPatchedAccountSetupAdapter,
createSetupInputPresenceValidator,
} from "../channels/plugins/setup-helpers.js";
export { formatCliCommand } from "../cli/command-format.js";
export { formatDocsLink } from "../terminal/links.js";
@ -42,6 +51,18 @@ export { SignalConfigSchema } from "../config/zod-schema.providers-core.js";
export { normalizeE164 } from "../utils.js";
export { resolveChannelMediaMaxBytes } from "../channels/plugins/media-limits.js";
export {
createCliPathTextInput,
createDelegatedTextInputShouldPrompt,
} from "../channels/plugins/setup-wizard-binary.js";
export { createDelegatedSetupWizardProxy } from "../channels/plugins/setup-wizard-proxy.js";
export {
createTopLevelChannelDmPolicy,
parseSetupEntriesAllowingWildcard,
promptParsedAllowFromForAccount,
setAccountAllowFromForChannel,
setSetupChannelEnabled,
} from "../channels/plugins/setup-wizard-helpers.js";
export {
buildBaseAccountStatusSnapshot,