Files
openclaw/src/plugins/contracts/plugin-sdk-subpaths.test.ts
2026-05-15 18:44:04 +01:00

1397 lines
50 KiB
TypeScript

import { readFileSync, readdirSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import type {
BaseProbeResult as ContractBaseProbeResult,
BaseTokenResolution as ContractBaseTokenResolution,
ChannelAgentTool as ContractChannelAgentTool,
ChannelAccountSnapshot as ContractChannelAccountSnapshot,
ChannelGroupContext as ContractChannelGroupContext,
ChannelMessageActionAdapter as ContractChannelMessageActionAdapter,
ChannelMessageActionContext as ContractChannelMessageActionContext,
ChannelMessageActionName as ContractChannelMessageActionName,
ChannelMessageToolDiscovery as ContractChannelMessageToolDiscovery,
ChannelStatusIssue as ContractChannelStatusIssue,
ChannelThreadingContext as ContractChannelThreadingContext,
ChannelThreadingToolContext as ContractChannelThreadingToolContext,
} from "openclaw/plugin-sdk/channel-contract";
import type {
ChannelMessageActionContext as CoreChannelMessageActionContext,
OpenClawPluginApi as CoreOpenClawPluginApi,
PluginRuntime as CorePluginRuntime,
} from "openclaw/plugin-sdk/core";
import * as providerEntrySdk from "openclaw/plugin-sdk/provider-entry";
import ts from "typescript";
import { describe, expect, expectTypeOf, it } from "vitest";
import type { ChannelMessageActionContext } from "../../channels/plugins/types.js";
import type {
BaseProbeResult,
BaseTokenResolution,
ChannelAgentTool,
ChannelAccountSnapshot,
ChannelGroupContext,
ChannelMessageActionAdapter,
ChannelMessageActionName,
ChannelMessageToolDiscovery,
ChannelStatusIssue,
ChannelThreadingContext,
ChannelThreadingToolContext,
} from "../../channels/plugins/types.js";
import * as channelActionsDirectSdk from "../../plugin-sdk/channel-actions.js";
import * as channelLifecycleDirectSdk from "../../plugin-sdk/channel-lifecycle.js";
import type {
ChannelMessageActionContext as SharedChannelMessageActionContext,
OpenClawPluginApi as SharedOpenClawPluginApi,
PluginRuntime as SharedPluginRuntime,
} from "../../plugin-sdk/channel-plugin-common.js";
import * as channelReplyPipelineDirectSdk from "../../plugin-sdk/channel-reply-pipeline.js";
import * as coreDirectSdk from "../../plugin-sdk/core.js";
import { publicPluginSdkSubpaths as pluginSdkSubpaths } from "../../plugin-sdk/entrypoints.js";
import * as globalSingletonDirectSdk from "../../plugin-sdk/global-singleton.js";
import * as providerEntryDirectSdk from "../../plugin-sdk/provider-entry.js";
import type { PluginRuntime } from "../runtime/types.js";
import type { OpenClawPluginApi } from "../types.js";
const SRC_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
const REPO_ROOT = resolve(SRC_ROOT, "..");
const PLUGIN_SDK_DIR = resolve(SRC_ROOT, "plugin-sdk");
const sourceCache = new Map<string, string>();
const repoTsFilesCache = new Map<string, string[]>();
const representativeRuntimeSmokeSubpaths = ["channel-runtime", "conversation-runtime"] as const;
const PUBLIC_SDK_TEST_HELPER_SUBPATHS = [
"agent-runtime-test-contracts",
"channel-contract-testing",
"channel-target-testing",
"channel-test-helpers",
"plugin-test-api",
"plugin-test-contracts",
"plugin-test-runtime",
"provider-http-test-mocks",
"provider-test-contracts",
"test-env",
"test-fixtures",
"test-node-mocks",
] as const;
const PUBLIC_SDK_TEST_HELPER_SUBPATHS_WITH_TOP_LEVEL_MOCKS = ["provider-http-test-mocks"] as const;
const importResolvedPluginSdkSubpath = async (specifier: string) => import(specifier);
type BrowserFacadeSourceContract = {
subpath: string;
artifactBasename: string;
mentions: readonly string[];
omits: readonly string[];
};
type BrowserHelperExportParityContract = {
corePath: string;
extensionPath: string;
expectedExports: readonly string[];
};
const BROWSER_FACADE_SOURCE_CONTRACTS: readonly BrowserFacadeSourceContract[] = [
{
subpath: "browser-control-auth",
artifactBasename: "browser-control-auth.js",
mentions: [
"loadBundledPluginPublicSurfaceModuleSync",
"resolveBrowserControlAuth",
"shouldAutoGenerateBrowserAuth",
"ensureBrowserControlAuth",
],
omits: [
"resolveGatewayAuth",
"writeConfigFile",
"generateBrowserControlToken",
"ensureGatewayStartupAuth",
],
},
{
subpath: "browser-profiles",
artifactBasename: "browser-profiles.js",
mentions: [
"loadBundledPluginPublicSurfaceModuleSync",
"resolveBrowserConfig",
"resolveProfile",
],
omits: [
"resolveBrowserSsrFPolicy",
"ensureDefaultProfile",
"ensureDefaultUserBrowserProfile",
"normalizeHexColor",
],
},
{
subpath: "browser-host-inspection",
artifactBasename: "browser-host-inspection.js",
mentions: [
"loadBundledPluginPublicSurfaceModuleSync",
"resolveGoogleChromeExecutableForPlatform",
"readBrowserVersion",
"parseBrowserMajorVersion",
],
omits: ["findFirstChromeExecutable", "findGoogleChromeExecutableLinux", "execText"],
},
];
const BROWSER_HELPER_EXPORT_PARITY_CONTRACTS: readonly BrowserHelperExportParityContract[] = [
{
corePath: "src/plugin-sdk/browser-control-auth.ts",
extensionPath: "extensions/browser/browser-control-auth.ts",
expectedExports: [
"BrowserControlAuth",
"ensureBrowserControlAuth",
"resolveBrowserControlAuth",
"shouldAutoGenerateBrowserAuth",
],
},
{
corePath: "src/plugin-sdk/browser-profiles.ts",
extensionPath: "extensions/browser/browser-profiles.ts",
expectedExports: [
"DEFAULT_AI_SNAPSHOT_MAX_CHARS",
"DEFAULT_BROWSER_ACTION_TIMEOUT_MS",
"DEFAULT_BROWSER_DEFAULT_PROFILE_NAME",
"DEFAULT_BROWSER_EVALUATE_ENABLED",
"DEFAULT_OPENCLAW_BROWSER_COLOR",
"DEFAULT_OPENCLAW_BROWSER_ENABLED",
"DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME",
"DEFAULT_UPLOAD_DIR",
"ResolvedBrowserConfig",
"ResolvedBrowserProfile",
"ResolvedBrowserTabCleanupConfig",
"resolveBrowserConfig",
"resolveProfile",
],
},
{
corePath: "src/plugin-sdk/browser-host-inspection.ts",
extensionPath: "extensions/browser/browser-host-inspection.ts",
expectedExports: [
"BrowserExecutable",
"parseBrowserMajorVersion",
"readBrowserVersion",
"resolveGoogleChromeExecutableForPlatform",
],
},
];
function readCachedSource(absolutePath: string): string {
const cached = sourceCache.get(absolutePath);
if (cached !== undefined) {
return cached;
}
const text = readFileSync(absolutePath, "utf8");
sourceCache.set(absolutePath, text);
return text;
}
function readPluginSdkSource(subpath: string): string {
return readCachedSource(resolve(PLUGIN_SDK_DIR, `${subpath}.ts`));
}
function readRepoSource(relativePath: string): string {
return readCachedSource(resolve(REPO_ROOT, relativePath));
}
function collectNamedExportsFromClause(clause: string): string[] {
return clause
.split(",")
.map((segment) => segment.trim())
.filter((segment) => segment.length > 0)
.map((segment) => segment.replace(/^type\s+/u, ""))
.map((segment) => {
const aliasMatch = segment.match(/\s+as\s+([A-Za-z_$][\w$]*)$/u);
if (aliasMatch?.[1]) {
return aliasMatch[1];
}
return segment;
});
}
function collectNamedExportsFromSource(source: string): string[] {
const names = new Set<string>();
const exportClausePattern =
/export\s+(?:type\s+)?\{([^}]*)\}\s*(?:from\s+["'][^"']+["'])?\s*;?/gms;
for (const match of source.matchAll(exportClausePattern)) {
for (const name of collectNamedExportsFromClause(match[1] ?? "")) {
names.add(name);
}
}
for (const pattern of [
/\bexport\s+(?:declare\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)/gu,
/\bexport\s+(?:declare\s+)?const\s+([A-Za-z_$][\w$]*)/gu,
/\bexport\s+type\s+([A-Za-z_$][\w$]*)\s*=/gu,
/\bexport\s+interface\s+([A-Za-z_$][\w$]*)/gu,
/\bexport\s+class\s+([A-Za-z_$][\w$]*)/gu,
]) {
for (const match of source.matchAll(pattern)) {
if (match[1]) {
names.add(match[1]);
}
}
}
return [...names].toSorted();
}
function collectNamedExportsFromRepoFile(relativePath: string): string[] {
return collectNamedExportsFromSource(readRepoSource(relativePath));
}
function createSourceFile(absolutePath: string): ts.SourceFile {
return ts.createSourceFile(
absolutePath,
readCachedSource(absolutePath),
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.TS,
);
}
function resolveTypeScriptModuleSource(fromFile: string, specifier: string): string | null {
if (!specifier.startsWith(".")) {
return null;
}
const resolved = resolve(dirname(fromFile), specifier);
if (resolved.endsWith(".js")) {
return `${resolved.slice(0, -3)}.ts`;
}
if (resolved.endsWith(".ts")) {
return resolved;
}
return `${resolved}.ts`;
}
function collectReexportedSourceFiles(entrypointPath: string): string[] {
const visited = new Set<string>();
function visit(filePath: string) {
if (visited.has(filePath)) {
return;
}
visited.add(filePath);
const sourceFile = createSourceFile(filePath);
for (const statement of sourceFile.statements) {
if (
!ts.isExportDeclaration(statement) ||
!statement.moduleSpecifier ||
!ts.isStringLiteral(statement.moduleSpecifier)
) {
continue;
}
const target = resolveTypeScriptModuleSource(filePath, statement.moduleSpecifier.text);
if (target) {
visit(target);
}
}
}
visit(entrypointPath);
return [...visited].toSorted();
}
function topLevelVitestModuleMockLines(filePath: string): number[] {
const sourceFile = createSourceFile(filePath);
const lines: number[] = [];
for (const statement of sourceFile.statements) {
if (!ts.isExpressionStatement(statement) || !ts.isCallExpression(statement.expression)) {
continue;
}
const expression = statement.expression.expression;
if (
!ts.isPropertyAccessExpression(expression) ||
!ts.isIdentifier(expression.expression) ||
expression.expression.text !== "vi"
) {
continue;
}
if (!["mock", "doMock", "unmock", "doUnmock"].includes(expression.name.text)) {
continue;
}
lines.push(sourceFile.getLineAndCharacterOfPosition(statement.getStart(sourceFile)).line + 1);
}
return lines;
}
function expectNamedExportParity(params: BrowserHelperExportParityContract) {
const coreExports = collectNamedExportsFromRepoFile(params.corePath);
const extensionExports = collectNamedExportsFromRepoFile(params.extensionPath);
expect(coreExports, `${params.corePath} exports changed`).toEqual([...params.expectedExports]);
expect(extensionExports, `${params.extensionPath} exports changed`).toEqual([
...params.expectedExports,
]);
}
function listRepoTsFiles(dir: string): string[] {
const cached = repoTsFilesCache.get(dir);
if (cached) {
return cached;
}
const entries = readdirSync(dir, { withFileTypes: true });
const files = entries.flatMap((entry) => {
const absolute = resolve(dir, entry.name);
if (entry.isDirectory()) {
if (entry.name === "dist" || entry.name === "node_modules") {
return [];
}
return listRepoTsFiles(absolute);
}
if (!entry.isFile()) {
return [];
}
return absolute.endsWith(".ts") ? [absolute] : [];
});
repoTsFilesCache.set(dir, files);
return files;
}
function findRepoFilesContaining(params: {
roots: readonly string[];
pattern: RegExp;
exclude?: readonly string[];
excludeFilesMatching?: readonly RegExp[];
}) {
const excluded = new Set((params.exclude ?? []).map((entry) => resolve(REPO_ROOT, entry)));
return params.roots
.flatMap((root) => listRepoTsFiles(root))
.filter((file) => !excluded.has(file))
.filter((file) => !(params.excludeFilesMatching ?? []).some((pattern) => pattern.test(file)))
.filter((file) => params.pattern.test(readCachedSource(file)))
.map((file) => file.slice(REPO_ROOT.length + 1))
.toSorted();
}
function isIdentifierCode(code: number): boolean {
return (
(code >= 48 && code <= 57) ||
(code >= 65 && code <= 90) ||
(code >= 97 && code <= 122) ||
code === 36 ||
code === 95
);
}
function sourceMentionsIdentifier(source: string, name: string): boolean {
let fromIndex = 0;
while (true) {
const matchIndex = source.indexOf(name, fromIndex);
if (matchIndex === -1) {
return false;
}
const beforeCode = matchIndex === 0 ? -1 : source.charCodeAt(matchIndex - 1);
const afterIndex = matchIndex + name.length;
const afterCode = afterIndex >= source.length ? -1 : source.charCodeAt(afterIndex);
if (!isIdentifierCode(beforeCode) && !isIdentifierCode(afterCode)) {
return true;
}
fromIndex = matchIndex + 1;
}
}
function expectSourceMentions(subpath: string, names: readonly string[]) {
const source = readPluginSdkSource(subpath);
const missing = names.filter((name) => !sourceMentionsIdentifier(source, name));
expect(missing, `${subpath} missing exports`).toStrictEqual([]);
}
function expectSourceOmits(subpath: string, names: readonly string[]) {
const source = readPluginSdkSource(subpath);
const present = names.filter((name) => sourceMentionsIdentifier(source, name));
expect(present, `${subpath} leaked exports`).toStrictEqual([]);
}
function expectSourceContract(
subpath: string,
params: { mentions?: readonly string[]; omits?: readonly string[] },
) {
const source = readPluginSdkSource(subpath);
const missing = (params.mentions ?? []).filter((name) => !sourceMentionsIdentifier(source, name));
const present = (params.omits ?? []).filter((name) => sourceMentionsIdentifier(source, name));
expect(missing, `${subpath} missing exports`).toStrictEqual([]);
expect(present, `${subpath} leaked exports`).toStrictEqual([]);
}
function expectSourceContains(subpath: string, snippet: string) {
expect(readPluginSdkSource(subpath)).toContain(snippet);
}
function expectSourceOmitsSnippet(subpath: string, snippet: string) {
expect(readPluginSdkSource(subpath)).not.toContain(snippet);
}
function expectRepoSourceOmitsSnippet(relativePath: string, snippet: string) {
expect(readRepoSource(relativePath)).not.toContain(snippet);
}
function expectSourceOmitsImportPattern(subpath: string, specifier: string) {
const escapedSpecifier = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const source = readPluginSdkSource(subpath);
expect(source).not.toMatch(new RegExp(`\\bfrom\\s+["']${escapedSpecifier}["']`, "u"));
expect(source).not.toMatch(new RegExp(`\\bimport\\(\\s*["']${escapedSpecifier}["']\\s*\\)`, "u"));
}
function expectBrowserFacadeSourceContract(contract: BrowserFacadeSourceContract) {
expectSourceMentions(contract.subpath, contract.mentions);
expectSourceContains(contract.subpath, `artifactBasename: "${contract.artifactBasename}"`);
expectSourceOmits(contract.subpath, contract.omits);
}
function isGeneratedBundledFacadeSubpath(subpath: string): boolean {
const source = readPluginSdkSource(subpath);
return (
source.startsWith("// Manual facade.") &&
sourceMentionsIdentifier(source, "loadBundledPluginPublicSurfaceModuleSync")
);
}
describe("plugin-sdk subpath exports", () => {
it("keeps the curated public list free of internal implementation subpaths", () => {
for (const deniedSubpath of [
"acpx",
"device-pair",
"lobster",
"pairing-access",
"provider-model-definitions",
"qa-channel",
"qa-channel-protocol",
"reply-prefix",
"secret-input-schema",
"signal-core",
"synology-chat",
"typing",
"whatsapp",
"whatsapp-action-runtime",
"whatsapp-login-qr",
"zai",
]) {
expect(pluginSdkSubpaths).not.toContain(deniedSubpath);
}
});
it("keeps removed bundled-channel aliases out of the public sdk list", () => {
const removedChannelAliases = new Set(["signal", "slack", "telegram", "whatsapp"]);
const banned = pluginSdkSubpaths.filter((subpath) => removedChannelAliases.has(subpath));
expect(banned).toStrictEqual([]);
});
it("keeps generated bundled-channel facades out of the public sdk list", () => {
const bannedPrefixes = ["discord", "signal", "slack", "telegram", "whatsapp"];
const banned = pluginSdkSubpaths.filter((subpath) =>
bannedPrefixes.some(
(prefix) =>
(subpath === prefix ||
subpath.startsWith(`${prefix}-`) ||
subpath.startsWith(`${prefix}.`)) &&
isGeneratedBundledFacadeSubpath(subpath),
),
);
expect(banned).toStrictEqual([]);
});
it("keeps browser compatibility helper subpaths as thin facades", () => {
for (const contract of BROWSER_FACADE_SOURCE_CONTRACTS) {
expectBrowserFacadeSourceContract(contract);
}
});
it("keeps browser helper facade exports aligned with extension public wrappers", () => {
for (const contract of BROWSER_HELPER_EXPORT_PARITY_CONTRACTS) {
expectNamedExportParity(contract);
}
});
it("keeps helper subpaths aligned", () => {
expectSourceMentions("core", [
"emptyPluginConfigSchema",
"definePluginEntry",
"defineChannelPluginEntry",
"defineSetupPluginEntry",
"createChatChannelPlugin",
"createChannelPluginBase",
"isSecretRef",
"optionalStringEnum",
]);
expectSourceOmits("core", [
"runPassiveAccountLifecycle",
"createLoggerBackedRuntime",
"registerSandboxBackend",
]);
expectSourceContract("routing", {
mentions: [
"buildAgentSessionKey",
"resolveThreadSessionKeys",
"normalizeMessageChannel",
"resolveGatewayMessageChannel",
],
});
expectSourceMentions("reply-payload", [
"buildMediaPayload",
"deliverTextOrMediaReply",
"resolveOutboundMediaUrls",
"resolvePayloadMediaUrls",
"sendPayloadMediaSequenceAndFinalize",
"sendPayloadMediaSequenceOrFallback",
"sendTextMediaPayload",
"sendPayloadWithChunkedTextAndMedia",
]);
expectSourceMentions("media-runtime", [
"createDirectTextMediaOutbound",
"createScopedChannelMediaMaxBytesResolver",
]);
expectSourceMentions("approval-auth-runtime", [
"createResolvedApproverActionAuthAdapter",
"resolveApprovalApprovers",
]);
expectSourceMentions("reply-chunking", ["chunkText", "chunkTextWithMode"]);
expectSourceMentions("reply-history", [
"buildInboundHistoryFromEntries",
"buildInboundHistoryFromMap",
"buildPendingHistoryContextFromMap",
"clearHistoryEntriesIfEnabled",
"createChannelHistoryWindow",
"recordPendingHistoryEntryIfEnabled",
]);
expectSourceMentions("mattermost", [
"buildPendingHistoryContextFromMap",
"clearHistoryEntriesIfEnabled",
"createChannelHistoryWindow",
"formatPairingApproveHint",
"recordPendingHistoryEntryIfEnabled",
"resolveControlCommandGate",
]);
expectSourceMentions("matrix", ["runPluginCommandWithTimeout"]);
expectSourceContract("reply-runtime", {
omits: [
"buildPendingHistoryContextFromMap",
"clearHistoryEntriesIfEnabled",
"recordPendingHistoryEntryIfEnabled",
"DEFAULT_GROUP_HISTORY_LIMIT",
],
});
expectSourceMentions("account-helpers", ["createAccountListHelpers"]);
expectSourceMentions("channel-actions", [
"optionalStringEnum",
"stringEnum",
"createMessageToolButtonsSchema",
"createMessageToolCardSchema",
]);
expectSourceContract("channel-secret-basic-runtime", {
mentions: [
"collectSimpleChannelFieldAssignments",
"collectConditionalChannelFieldAssignments",
"collectSecretInputAssignment",
"getChannelSurface",
"pushAssignment",
"pushInactiveSurfaceWarning",
"ResolverContext",
"SecretTargetRegistryEntry",
],
omits: ["collectNestedChannelTtsAssignments"],
});
expectSourceContract("channel-secret-runtime", {
mentions: [
"collectSimpleChannelFieldAssignments",
"collectConditionalChannelFieldAssignments",
"collectSecretInputAssignment",
"getChannelSurface",
"pushAssignment",
"pushInactiveSurfaceWarning",
"ResolverContext",
"SecretTargetRegistryEntry",
],
omits: [
"buildUntrustedChannelMetadata",
"evaluateSupplementalContextVisibility",
"resolvePinnedMainDmOwnerFromAllowlist",
"safeMatchRegex",
],
});
expectSourceContract("channel-secret-tts-runtime", {
mentions: ["collectNestedChannelTtsAssignments"],
omits: ["collectSimpleChannelFieldAssignments", "collectConditionalChannelFieldAssignments"],
});
expectSourceContract("provider-web-search-contract", {
mentions: [
"createWebSearchProviderContractFields",
"enablePluginInConfig",
"getScopedCredentialValue",
"resolveProviderWebSearchPluginConfig",
"setScopedCredentialValue",
"setProviderWebSearchPluginConfigValue",
"WebSearchProviderPlugin",
],
omits: [
"buildSearchCacheKey",
"withTrustedWebSearchEndpoint",
"writeCachedSearchPayload",
"resolveCitationRedirectUrl",
],
});
expectSourceContract("provider-web-search-config-contract", {
mentions: [
"getScopedCredentialValue",
"resolveProviderWebSearchPluginConfig",
"setScopedCredentialValue",
"setProviderWebSearchPluginConfigValue",
"WebSearchProviderPlugin",
],
omits: [
"enablePluginInConfig",
"buildSearchCacheKey",
"withTrustedWebSearchEndpoint",
"writeCachedSearchPayload",
"resolveCitationRedirectUrl",
],
});
expectSourceContract("provider-web-fetch-contract", {
mentions: ["enablePluginInConfig", "WebFetchProviderPlugin"],
omits: [
"withTrustedWebToolsEndpoint",
"readResponseText",
"resolveCacheTtlMs",
"wrapExternalContent",
],
});
expectSourceContract("tool-payload", {
mentions: ["extractToolPayload", "ToolPayloadCarrier"],
omits: ["createAnthropicToolPayloadCompatibilityWrapper", "extractToolSend"],
});
expectSourceMentions("compat", [
"createPluginRuntimeStore",
"createScopedChannelConfigAdapter",
"collectOpenGroupPolicyConfiguredRouteWarnings",
"resolveControlCommandGate",
"delegateCompactionToRuntime",
"createReplyPrefixContext",
"createChannelReplyPipeline",
]);
expectSourceMentions("device-bootstrap", [
"approveDevicePairing",
"issueDeviceBootstrapToken",
"listDevicePairing",
]);
expectSourceMentions("allowlist-config-edit", [
"buildDmGroupAccountAllowlistAdapter",
"createNestedAllowlistOverrideResolver",
]);
expectSourceContract("allow-from", {
mentions: [
"addAllowlistUserEntriesFromConfigEntry",
"buildAllowlistResolutionSummary",
"canonicalizeAllowlistWithResolvedIds",
"mapAllowlistResolutionInputs",
"mergeAllowlist",
"patchAllowlistUsersInConfigEntries",
"summarizeMapping",
"compileAllowlist",
"firstDefined",
"formatAllowlistMatchMeta",
"isSenderIdAllowed",
"mergeDmAllowFromSources",
"resolveAllowlistMatchSimple",
],
});
expectSourceMentions("runtime", ["createLoggerBackedRuntime"]);
expectSourceMentions("conversation-runtime", [
"recordInboundSession",
"recordInboundSessionMetaSafe",
"resolveConversationLabel",
]);
expectSourceMentions("directory-runtime", [
"createChannelDirectoryAdapter",
"createRuntimeDirectoryLiveAdapter",
"listDirectoryEntriesFromSources",
"listResolvedDirectoryEntriesFromSources",
]);
expectSourceContains(
"memory-core-host-runtime-core",
'export * from "../../packages/memory-host-sdk/src/runtime-core.js";',
);
expectSourceContains(
"memory-core-host-runtime-cli",
'export * from "../../packages/memory-host-sdk/src/runtime-cli.js";',
);
expectSourceContains(
"memory-core-host-runtime-files",
'export * from "../../packages/memory-host-sdk/src/runtime-files.js";',
);
expectSourceMentions("plugin-test-runtime", [
"registerSingleProviderPlugin",
"registerProviderPlugin",
"createRuntimeEnv",
"createPluginSetupWizardStatus",
"runProviderCatalog",
]);
expectSourceMentions("agent-runtime-test-contracts", [
"AUTH_PROFILE_RUNTIME_CONTRACT",
"DELIVERY_NO_REPLY_RUNTIME_CONTRACT",
"createParameterFreeTool",
"QUEUED_USER_MESSAGE_MARKER",
]);
expectSourceMentions("channel-test-helpers", [
"assertBundledChannelEntries",
"formatEnvelopeTimestamp",
"expectPairingReplyText",
]);
expectSourceMentions("provider-test-contracts", [
"expectPassthroughReplayPolicy",
"runRealtimeSttLiveTest",
]);
expectSourceMentions("test-env", [
"withEnv",
"withServer",
"withTempHome",
"createMockIncomingRequest",
"withFetchPreconnect",
"createRequestCaptureJsonFetch",
"installPinnedHostnameTestHooks",
"isLiveTestEnabled",
]);
expectSourceMentions("test-fixtures", [
"createCliRuntimeCapture",
"importFreshModule",
"bundledPluginRoot",
"createSandboxTestContext",
"makeAgentAssistantMessage",
"peekSystemEvents",
"typedCases",
]);
expectSourceMentions("test-node-mocks", [
"mockNodeBuiltinModule",
"mockNodeChildProcessExecFile",
"mockNodeChildProcessSpawnSync",
]);
expectSourceMentions("channel-target-testing", [
"installCommonResolveTargetErrorCases",
"ResolveTargetFn",
]);
expectSourceMentions("provider-http-test-mocks", [
"getProviderHttpMocks",
"installProviderHttpMockCleanup",
]);
});
it("keeps public SDK test helper subpaths free of top-level Vitest module mocks outside opt-in mock helpers", () => {
const optInMockSubpaths = new Set<string>(PUBLIC_SDK_TEST_HELPER_SUBPATHS_WITH_TOP_LEVEL_MOCKS);
const violations = PUBLIC_SDK_TEST_HELPER_SUBPATHS.filter(
(subpath) => !optInMockSubpaths.has(subpath),
)
.flatMap((subpath) =>
collectReexportedSourceFiles(resolve(PLUGIN_SDK_DIR, `${subpath}.ts`)).flatMap((file) =>
topLevelVitestModuleMockLines(file).map(
(line) => `${file.slice(REPO_ROOT.length + 1)}:${line}`,
),
),
)
.toSorted();
expect(violations).toStrictEqual([]);
});
it("keeps the deprecated channel-runtime shim unused in repo imports", () => {
const matches = findRepoFilesContaining({
roots: [
resolve(REPO_ROOT, "src"),
resolve(REPO_ROOT, "extensions"),
resolve(REPO_ROOT, "test"),
],
pattern:
/(?:from\s+|import\s+(?:type\s+)?|import\s*\(\s*)["']openclaw\/plugin-sdk\/channel-runtime(?=["'])/u,
exclude: [
"src/plugins/compat/registry.ts",
"src/plugins/sdk-alias.test.ts",
"src/plugins/contracts/plugin-sdk-root-alias.test.ts",
],
});
expect(matches).toStrictEqual([]);
});
it("keeps deprecated comparable channel target helpers behind compatibility shims", () => {
const matches = findRepoFilesContaining({
roots: [
resolve(REPO_ROOT, "src"),
resolve(REPO_ROOT, "extensions"),
resolve(REPO_ROOT, "test"),
],
pattern:
/\b(?:ComparableChannelTarget|resolveComparableTargetFor(?:Channel|LoadedChannel)|comparableChannelTargets(?:Match|ShareRoute))\b/u,
exclude: [
"src/channels/plugins/target-parsing.ts",
"src/channels/plugins/target-parsing-loaded.ts",
"src/channels/plugins/target-parsing.test.ts",
"src/plugins/compat/registry.ts",
"src/plugins/compat/registry.test.ts",
"src/plugins/contracts/plugin-sdk-subpaths.test.ts",
],
});
expect(matches).toStrictEqual([]);
});
it("keeps deprecated channel route key aliases behind compatibility shims", () => {
const matches = findRepoFilesContaining({
roots: [
resolve(REPO_ROOT, "src"),
resolve(REPO_ROOT, "extensions"),
resolve(REPO_ROOT, "test"),
],
pattern: /\b(?:channelRouteIdentityKey|channelRouteKey)\b/u,
exclude: [
"src/plugin-sdk/channel-route.ts",
"src/plugin-sdk/channel-route.test.ts",
"src/plugins/compat/registry.ts",
"src/plugins/compat/registry.test.ts",
"src/plugins/contracts/plugin-sdk-subpaths.test.ts",
],
});
expect(matches).toStrictEqual([]);
});
it("keeps removed channel-named runtime boundaries out of core imports", () => {
const matches = findRepoFilesContaining({
roots: [resolve(REPO_ROOT, "src")],
pattern:
/plugins\/runtime\/runtime-(?:discord|imessage|line|signal|slack|telegram|whatsapp)(?:[-.][^"']*)?\.js/u,
exclude: [
"src/plugins/runtime/runtime-plugin-boundary.ts",
"src/plugins/runtime/runtime-web-channel-plugin.ts",
],
excludeFilesMatching: [/\.test\.ts$/u, /\.test-harness\.ts$/u],
});
expect(matches).toStrictEqual([]);
});
it("exports channel runtime helpers from the dedicated subpath", () => {
expectSourceOmits("channel-runtime", [
"applyChannelMatchMeta",
"createChannelDirectoryAdapter",
"createEmptyChannelDirectoryAdapter",
"createArmableStallWatchdog",
"createDraftStreamLoop",
"createLoggedPairingApprovalNotifier",
"createPairingPrefixStripper",
"createChannelRunQueue",
"createRunStateMachine",
"createRuntimeDirectoryLiveAdapter",
"createRuntimeOutboundDelegates",
"createStatusReactionController",
"createTextPairingAdapter",
"createFinalizableDraftLifecycle",
"DEFAULT_EMOJIS",
"logAckFailure",
"logTypingFailure",
"logInboundDrop",
"normalizeMessageChannel",
"removeAckReactionAfterReply",
"recordInboundSession",
"recordInboundSessionMetaSafe",
"resolveInboundSessionEnvelopeContext",
"resolveInboundMentionDecision",
"resolveMentionGating",
"resolveMentionGatingWithBypass",
"resolveOutboundSendDep",
"resolveConversationLabel",
"shouldDebounceTextInbound",
"shouldAckReaction",
"shouldAckReactionForWhatsApp",
"toLocationContext",
"resolveThreadBindingConversationIdFromBindingId",
"resolveThreadBindingEffectiveExpiresAt",
"resolveThreadBindingFarewellText",
"resolveThreadBindingIdleTimeoutMs",
"resolveThreadBindingIdleTimeoutMsForChannel",
"resolveThreadBindingIntroText",
"resolveThreadBindingLifecycle",
"resolveThreadBindingMaxAgeMs",
"resolveThreadBindingMaxAgeMsForChannel",
"resolveThreadBindingSpawnPolicy",
"resolveThreadBindingThreadName",
"resolveThreadBindingsEnabled",
"formatThreadBindingDisabledError",
"resolveControlCommandGate",
"resolveCommandAuthorizedFromAuthorizers",
"resolveDualTextControlCommandGate",
"resolveNativeCommandSessionTargets",
"attachChannelToResult",
"buildComputedAccountStatusSnapshot",
"buildMediaPayload",
"createActionGate",
"jsonResult",
"normalizeInteractiveReply",
"PAIRING_APPROVED_MESSAGE",
"projectCredentialSnapshotFields",
"readStringParam",
"compileAllowlist",
"formatAllowlistMatchMeta",
"firstDefined",
"isSenderIdAllowed",
"mergeDmAllowFromSources",
"addAllowlistUserEntriesFromConfigEntry",
"buildAllowlistResolutionSummary",
"canonicalizeAllowlistWithResolvedIds",
"mergeAllowlist",
"patchAllowlistUsersInConfigEntries",
"resolvePayloadMediaUrls",
"resolveScopedChannelMediaMaxBytes",
"sendPayloadMediaSequenceAndFinalize",
"sendPayloadMediaSequenceOrFallback",
"sendTextMediaPayload",
"createScopedChannelMediaMaxBytesResolver",
"runPassiveAccountLifecycle",
"buildChannelKeyCandidates",
"buildMessagingTarget",
"createDirectTextMediaOutbound",
"createScopedAccountReplyToModeResolver",
"createStaticReplyToModeResolver",
"createTopLevelChannelReplyToModeResolver",
"createUnionActionGate",
"ensureTargetId",
"listTokenSourcedAccounts",
"parseMentionPrefixOrAtUserTarget",
"requireTargetKind",
"resolveChannelEntryMatchWithFallback",
"resolveChannelMatchConfig",
"resolveReactionMessageId",
"resolveTargetsWithOptionalToken",
"appendMatchMetadata",
"asString",
"collectIssuesForEnabledAccounts",
"isRecord",
"resolveEnabledConfiguredAccountId",
]);
expectSourceMentions("channel-inbound", [
"buildMentionRegexes",
"createDirectDmPreCryptoGuardPolicy",
"createChannelInboundDebouncer",
"createInboundDebouncer",
"dispatchInboundDirectDmWithRuntime",
"formatInboundEnvelope",
"formatInboundFromLabel",
"formatLocationText",
"implicitMentionKindWhen",
"logInboundDrop",
"matchesMentionPatterns",
"matchesMentionWithExplicit",
"resolveInboundMentionDecision",
"normalizeMentionText",
"resolveInboundDebounceMs",
"resolveEnvelopeFormatOptions",
"resolveInboundSessionEnvelopeContext",
"resolveMentionGating",
"resolveMentionGatingWithBypass",
"shouldDebounceTextInbound",
"toLocationContext",
]);
expectSourceContract("reply-runtime", {
omits: [
"buildMentionRegexes",
"formatInboundEnvelope",
"formatInboundFromLabel",
"matchesMentionPatterns",
"matchesMentionWithExplicit",
"normalizeMentionText",
"resolveEnvelopeFormatOptions",
"hasControlCommand",
"buildCommandTextFromArgs",
"buildCommandsPaginationKeyboard",
"buildModelsProviderData",
"listNativeCommandSpecsForConfig",
"listSkillCommandsForAgents",
"normalizeCommandBody",
"resolveCommandAuthorization",
"resolveStoredModelOverride",
"shouldComputeCommandAuthorized",
"shouldHandleTextCommands",
],
});
expectSourceMentions("channel-setup", [
"createOptionalChannelSetupSurface",
"createTopLevelChannelDmPolicy",
]);
expectSourceContract("channel-actions", {
mentions: [
"createUnionActionGate",
"listTokenSourcedAccounts",
"resolveReactionMessageId",
"createMessageToolButtonsSchema",
"createMessageToolCardSchema",
],
});
expectSourceMentions("channel-targets", [
"applyChannelMatchMeta",
"buildChannelKeyCandidates",
"buildMessagingTarget",
"ChannelId",
"createAllowedChatSenderMatcher",
"ensureTargetId",
"normalizeChannelId",
"parseChatAllowTargetPrefixes",
"parseMentionPrefixOrAtUserTarget",
"parseChatTargetPrefixesOrThrow",
"requireTargetKind",
"resolveChannelEntryMatchWithFallback",
"resolveChannelMatchConfig",
"resolveServicePrefixedAllowTarget",
"resolveServicePrefixedChatTarget",
"resolveServicePrefixedOrChatAllowTarget",
"resolveServicePrefixedTarget",
"resolveTargetsWithOptionalToken",
]);
expectSourceMentions("channel-config-writes", [
"authorizeConfigWrite",
"canBypassConfigWritePolicy",
"formatConfigWriteDeniedMessage",
"resolveChannelConfigWrites",
]);
expectSourceMentions("channel-feedback", [
"createStatusReactionController",
"logAckFailure",
"logTypingFailure",
"removeAckReactionAfterReply",
"shouldAckReaction",
"shouldAckReactionForWhatsApp",
"DEFAULT_EMOJIS",
]);
expectSourceMentions("status-helpers", [
"appendMatchMetadata",
"asString",
"collectIssuesForEnabledAccounts",
"isRecord",
"resolveEnabledConfiguredAccountId",
]);
expectSourceMentions("outbound-runtime", [
"createRuntimeOutboundDelegates",
"resolveOutboundSendDep",
"resolveAgentOutboundIdentity",
]);
expectSourceMentions("command-auth", [
"buildCommandTextFromArgs",
"buildCommandsMessage",
"buildCommandsMessagePaginated",
"buildCommandsPaginationKeyboard",
"buildHelpMessage",
"buildModelsProviderData",
"hasControlCommand",
"listNativeCommandSpecsForConfig",
"listSkillCommandsForAgents",
"normalizeCommandBody",
"createPreCryptoDirectDmAuthorizer",
"resolveCommandAuthorization",
"resolveCommandAuthorizedFromAuthorizers",
"resolveInboundDirectDmAccessWithRuntime",
"resolveControlCommandGate",
"resolveDualTextControlCommandGate",
"resolveNativeCommandSessionTargets",
"resolveStoredModelOverride",
"shouldComputeCommandAuthorized",
"shouldHandleTextCommands",
]);
expectSourceMentions("command-status", [
"buildCommandsMessage",
"buildCommandsMessagePaginated",
"buildHelpMessage",
]);
expectSourceOmitsImportPattern("command-auth", "../auto-reply/status.js");
expectSourceOmitsSnippet("command-auth", "../../extensions/");
expectSourceMentions("channel-send-result", [
"attachChannelToResult",
"buildChannelSendResult",
]);
expectSourceMentions("direct-dm", [
"createDirectDmPreCryptoGuardPolicy",
"createPreCryptoDirectDmAuthorizer",
"dispatchInboundDirectDmWithRuntime",
"resolveInboundDirectDmAccessWithRuntime",
]);
expectSourceMentions("conversation-runtime", [
"formatThreadBindingDisabledError",
"resolveThreadBindingFarewellText",
"resolveThreadBindingConversationIdFromBindingId",
"resolveThreadBindingEffectiveExpiresAt",
"resolveThreadBindingIdleTimeoutMs",
"resolveThreadBindingIdleTimeoutMsForChannel",
"resolveThreadBindingIntroText",
"resolveThreadBindingLifecycle",
"resolveThreadBindingMaxAgeMs",
"resolveThreadBindingMaxAgeMsForChannel",
"resolveThreadBindingSpawnPolicy",
"resolveThreadBindingThreadName",
"resolveThreadBindingsEnabled",
"formatThreadBindingDurationLabel",
"createScopedAccountReplyToModeResolver",
"createStaticReplyToModeResolver",
"createTopLevelChannelReplyToModeResolver",
]);
expectSourceMentions("thread-bindings-runtime", [
"resolveThreadBindingFarewellText",
"resolveThreadBindingLifecycle",
"registerSessionBindingAdapter",
"unregisterSessionBindingAdapter",
"SessionBindingAdapter",
]);
expectSourceMentions("ssrf-runtime", [
"closeDispatcher",
"createPinnedDispatcher",
"resolvePinnedHostnameWithPolicy",
"formatErrorMessage",
"isPrivateIpAddress",
"assertHttpUrlTargetsPrivateNetwork",
"ssrfPolicyFromDangerouslyAllowPrivateNetwork",
"ssrfPolicyFromAllowPrivateNetwork",
]);
expectSourceContract("provider-setup", {
mentions: [
"applyProviderDefaultModel",
"discoverOpenAICompatibleLocalModels",
"discoverOpenAICompatibleSelfHostedProvider",
],
omits: [
"buildOllamaProvider",
"configureOllamaNonInteractive",
"ensureOllamaModelPulled",
"promptAndConfigureOllama",
"promptAndConfigureVllm",
"buildVllmProvider",
"buildSglangProvider",
"OLLAMA_DEFAULT_BASE_URL",
"OLLAMA_DEFAULT_MODEL",
"VLLM_DEFAULT_BASE_URL",
],
});
expectSourceOmitsImportPattern("provider-setup", "./vllm.js");
expectSourceOmitsImportPattern("provider-setup", "./sglang.js");
expectSourceMentions("provider-auth", [
"buildOauthProviderAuthResult",
"generateHexPkceVerifierChallenge",
"generatePkceVerifierChallenge",
"toFormUrlEncoded",
]);
expectSourceOmits("core", ["buildOauthProviderAuthResult"]);
expectSourceContract("provider-model-shared", {
mentions: ["DEFAULT_CONTEXT_TOKENS", "normalizeModelCompat", "cloneFirstTemplateModel"],
omits: ["applyOpenAIConfig", "buildKilocodeModelDefinition", "discoverHuggingfaceModels"],
});
expectSourceContract("provider-catalog-shared", {
mentions: ["buildSingleProviderApiKeyCatalog", "buildPairedProviderApiKeyCatalog"],
omits: ["buildDeepSeekProvider", "buildOpenAICodexProvider", "buildVeniceProvider"],
});
expectSourceMentions("setup", [
"DEFAULT_ACCOUNT_ID",
"createAllowFromSection",
"createDelegatedSetupWizardProxy",
"createTopLevelChannelDmPolicy",
"mergeAllowFromEntries",
]);
expectSourceMentions("setup-tools", ["formatCliCommand", "detectBinary", "formatDocsLink"]);
expectSourceMentions("lazy-runtime", ["createLazyRuntimeSurface", "createLazyRuntimeModule"]);
expectSourceContract("self-hosted-provider-setup", {
mentions: [
"applyProviderDefaultModel",
"discoverOpenAICompatibleLocalModels",
"discoverOpenAICompatibleSelfHostedProvider",
"configureOpenAICompatibleSelfHostedProviderNonInteractive",
],
omits: ["buildVllmProvider", "buildSglangProvider"],
});
expectSourceOmitsImportPattern("self-hosted-provider-setup", "./vllm.js");
expectSourceOmitsImportPattern("self-hosted-provider-setup", "./sglang.js");
expectSourceOmitsSnippet("agent-runtime", "./sglang.js");
expectSourceOmitsSnippet("agent-runtime", "./vllm.js");
expectSourceOmitsSnippet("agent-runtime", "../../extensions/");
expectSourceOmitsSnippet("google-model-id", "./google.js");
expectSourceOmitsSnippet("google-model-id", "./facade-runtime.js");
expectSourceOmitsSnippet("google-model-id", "../../extensions/");
expectRepoSourceOmitsSnippet("extensions/xai/model-id.ts", "./xai.js");
expectRepoSourceOmitsSnippet("extensions/xai/model-id.ts", "./facade-runtime.js");
expectRepoSourceOmitsSnippet("extensions/xai/model-id.ts", "../../extensions/");
expectSourceMentions("sandbox", ["registerSandboxBackend", "runPluginCommandWithTimeout"]);
expectSourceMentions("secret-input", [
"buildSecretInputSchema",
"buildOptionalSecretInputSchema",
"normalizeSecretInputString",
]);
expectSourceMentions("provider-http", [
"assertOkOrThrowHttpError",
"normalizeBaseUrl",
"postJsonRequest",
"postTranscriptionRequest",
"requireTranscriptionText",
]);
expectSourceOmits("speech", [
"buildElevenLabsSpeechProvider",
"buildMicrosoftSpeechProvider",
"buildOpenAISpeechProvider",
"edgeTTS",
"elevenLabsTTS",
"inferEdgeExtension",
"openaiTTS",
"OPENAI_TTS_MODELS",
"OPENAI_TTS_VOICES",
]);
expectSourceOmits("media-understanding", [
"deepgramMediaUnderstandingProvider",
"groqMediaUnderstandingProvider",
"assertOkOrThrowHttpError",
"postJsonRequest",
"postTranscriptionRequest",
]);
expectSourceOmits("image-generation", [
"buildFalImageGenerationProvider",
"buildGoogleImageGenerationProvider",
"buildOpenAIImageGenerationProvider",
]);
expectSourceOmits("config-runtime", [
"hasConfiguredSecretInput",
"normalizeResolvedSecretInputString",
"normalizeSecretInputString",
]);
expectSourceMentions("webhook-ingress", [
"registerPluginHttpRoute",
"resolveWebhookPath",
"readRequestBodyWithLimit",
"readJsonWebhookBodyOrReject",
"requestBodyErrorToText",
"withResolvedWebhookRequestPipeline",
]);
expectSourceMentions("channel-feedback", ["removeAckReactionAfterReply", "shouldAckReaction"]);
});
it("keeps shared plugin-sdk types aligned", () => {
expectTypeOf<ContractBaseProbeResult>().toMatchTypeOf<BaseProbeResult>();
expectTypeOf<ContractBaseTokenResolution>().toMatchTypeOf<BaseTokenResolution>();
expectTypeOf<ContractChannelAgentTool>().toMatchTypeOf<ChannelAgentTool>();
expectTypeOf<ContractChannelAccountSnapshot>().toMatchTypeOf<ChannelAccountSnapshot>();
expectTypeOf<ContractChannelGroupContext>().toMatchTypeOf<ChannelGroupContext>();
expectTypeOf<ContractChannelMessageActionAdapter>().toMatchTypeOf<ChannelMessageActionAdapter>();
expectTypeOf<ContractChannelMessageActionContext>().toMatchTypeOf<ChannelMessageActionContext>();
expectTypeOf<ContractChannelMessageActionName>().toMatchTypeOf<ChannelMessageActionName>();
expectTypeOf<ContractChannelMessageToolDiscovery>().toMatchTypeOf<ChannelMessageToolDiscovery>();
expectTypeOf<ContractChannelStatusIssue>().toMatchTypeOf<ChannelStatusIssue>();
expectTypeOf<ContractChannelThreadingContext>().toMatchTypeOf<ChannelThreadingContext>();
expectTypeOf<ContractChannelThreadingToolContext>().toMatchTypeOf<ChannelThreadingToolContext>();
expectTypeOf<CoreOpenClawPluginApi>().toMatchTypeOf<OpenClawPluginApi>();
expectTypeOf<CorePluginRuntime>().toMatchTypeOf<PluginRuntime>();
expectTypeOf<CoreChannelMessageActionContext>().toMatchTypeOf<ChannelMessageActionContext>();
expectTypeOf<CoreOpenClawPluginApi>().toMatchTypeOf<SharedOpenClawPluginApi>();
expectTypeOf<CorePluginRuntime>().toMatchTypeOf<SharedPluginRuntime>();
expectTypeOf<CoreChannelMessageActionContext>().toMatchTypeOf<SharedChannelMessageActionContext>();
});
it("keeps runtime entry subpaths importable", async () => {
const coreSdk = await importResolvedPluginSdkSubpath("openclaw/plugin-sdk/core");
const channelActionsSdk = await importResolvedPluginSdkSubpath(
"openclaw/plugin-sdk/channel-actions",
);
const globalSingletonSdk = await importResolvedPluginSdkSubpath(
"openclaw/plugin-sdk/global-singleton",
);
const pluginEntrySdk = await importResolvedPluginSdkSubpath("openclaw/plugin-sdk/plugin-entry");
const channelLifecycleSdk = await importResolvedPluginSdkSubpath(
"openclaw/plugin-sdk/channel-lifecycle",
);
const channelPairingSdk = await importResolvedPluginSdkSubpath(
"openclaw/plugin-sdk/channel-pairing",
);
const channelReplyPipelineSdk = await importResolvedPluginSdkSubpath(
"openclaw/plugin-sdk/channel-reply-pipeline",
);
const representativeModules = [];
for (const id of representativeRuntimeSmokeSubpaths) {
representativeModules.push(await importResolvedPluginSdkSubpath(`openclaw/plugin-sdk/${id}`));
}
expect(coreSdk.definePluginEntry).toBe(pluginEntrySdk.definePluginEntry);
expect(coreSdk.optionalStringEnum).toBe(coreDirectSdk.optionalStringEnum);
expect(channelActionsSdk.optionalStringEnum).toBe(channelActionsDirectSdk.optionalStringEnum);
expect(channelActionsSdk.stringEnum).toBe(channelActionsDirectSdk.stringEnum);
expect(globalSingletonSdk.resolveGlobalMap).toBe(globalSingletonDirectSdk.resolveGlobalMap);
expect(globalSingletonSdk.resolveGlobalSingleton).toBe(
globalSingletonDirectSdk.resolveGlobalSingleton,
);
expect(globalSingletonSdk.createScopedExpiringIdCache).toBe(
globalSingletonDirectSdk.createScopedExpiringIdCache,
);
expectSourceMentions("delivery-queue-runtime", ["drainPendingDeliveries"]);
expectSourceContains("delivery-queue-runtime", "../infra/outbound/deliver-runtime.js");
expectSourceMentions("error-runtime", ["formatUncaughtError", "isApprovalNotFoundError"]);
expect(channelLifecycleSdk.createDraftStreamLoop).toBe(
channelLifecycleDirectSdk.createDraftStreamLoop,
);
expect(channelLifecycleSdk.createFinalizableDraftLifecycle).toBe(
channelLifecycleDirectSdk.createFinalizableDraftLifecycle,
);
expect(channelLifecycleSdk.createChannelRunQueue).toBe(
channelLifecycleDirectSdk.createChannelRunQueue,
);
expect(channelLifecycleSdk.runPassiveAccountLifecycle).toBe(
channelLifecycleDirectSdk.runPassiveAccountLifecycle,
);
expect(channelLifecycleSdk.createRunStateMachine).toBe(
channelLifecycleDirectSdk.createRunStateMachine,
);
expect(channelLifecycleSdk.createArmableStallWatchdog).toBe(
channelLifecycleDirectSdk.createArmableStallWatchdog,
);
expectSourceMentions("channel-pairing", [
"createChannelPairingController",
"createChannelPairingChallengeIssuer",
"createLoggedPairingApprovalNotifier",
"createPairingPrefixStripper",
"readChannelAllowFromStoreSync",
"createTextPairingAdapter",
]);
expect("createScopedPairingAccess" in channelPairingSdk).toBe(false);
expectSourceMentions("channel-reply-pipeline", [
"createChannelReplyPipeline",
"createTypingCallbacks",
"createReplyPrefixContext",
"createReplyPrefixOptions",
"resolveChannelSourceReplyDeliveryMode",
]);
expect(channelReplyPipelineSdk.createTypingCallbacks).toBe(
channelReplyPipelineDirectSdk.createTypingCallbacks,
);
expect(channelReplyPipelineSdk.createReplyPrefixContext).toBe(
channelReplyPipelineDirectSdk.createReplyPrefixContext,
);
expect(channelReplyPipelineSdk.createReplyPrefixOptions).toBe(
channelReplyPipelineDirectSdk.createReplyPrefixOptions,
);
expect(channelReplyPipelineSdk.resolveChannelSourceReplyDeliveryMode).toBe(
channelReplyPipelineDirectSdk.resolveChannelSourceReplyDeliveryMode,
);
expect(pluginSdkSubpaths.length).toBeGreaterThan(representativeRuntimeSmokeSubpaths.length);
for (const [index, id] of representativeRuntimeSmokeSubpaths.entries()) {
const mod = representativeModules[index];
expect(typeof mod).toBe("object");
expect(Object.keys(mod as object).length, `subpath ${id} should resolve`).toBeGreaterThan(0);
}
});
it("keeps the Zalouser command-auth compatibility facade importable", async () => {
const zalouserSdk = await importResolvedPluginSdkSubpath("openclaw/plugin-sdk/zalouser");
const commandAuthSdk = await importResolvedPluginSdkSubpath("openclaw/plugin-sdk/command-auth");
expect(zalouserSdk.resolveSenderCommandAuthorization).toBe(
commandAuthSdk.resolveSenderCommandAuthorization,
);
});
it("exports single-provider plugin entry helpers from the dedicated subpath", () => {
expect(providerEntrySdk.defineSingleProviderPluginEntry).toBe(
providerEntryDirectSdk.defineSingleProviderPluginEntry,
);
});
});