Files
openclaw/src/plugins/contracts/plugin-sdk-subpaths.test.ts
Peter Steinberger 1507a9701b refactor: centralize inbound supplemental context
* refactor: centralize inbound supplemental context

* refactor: trim supplemental finalizer typing

* docs: clarify supplemental context projection

* refactor: move inbound finalization into core

* refactor: simplify channel inbound facts

* refactor: fold supplemental media into inbound finalizer

* refactor: migrate channel inbound callers to builder

* docs: mark inbound finalizer compat types deprecated

* refactor: wire runtime turn context builder

* refactor: replace channel turn runtime API

* fix: respect discord quote visibility

* fix: avoid deprecated line dispatch helper

* refactor: deprecate channel message SDK seams

* docs: trim channel outbound SDK page

* test: migrate irc inbound assertion

* refactor: deprecate outbound SDK facades

* refactor: deprecate channel helper SDK facades

* refactor: deprecate channel streaming SDK facade

* refactor: move direct dm helpers into inbound SDK

* chore: mark legacy test-utils SDK alias deprecated

* refactor: remove unused allow-from read helper

* refactor: route remaining channel dispatch through core

* refactor: enforce modern extension SDK imports

* test: give slow image root tests more time

* ci: support node fallback on windows

* fix: add transcripts tool display metadata

* refactor: trim legacy channel test seams

* fix: preserve channel compat after rebase

* fix: keep deprecated channel inbound aliases

* fix: preserve discord thread context visibility

* fix: clean final rebase conflicts

* fix: preserve channel message dispatch aliases

* fix: sync channel refactor after rebase

* fix: sync channel refactor after latest main

* fix: dedupe memory-core subagent mock

* test: align clickclack inbound dispatch assertions

* fix: sync plugin sdk api hash after rebase

* fix: sync channel refactor after latest main

* fix: sync plugin sdk api hash after rebase

* fix: sync plugin sdk api hash after latest main

* test: remove stale inbound context awaits
2026-05-27 09:26:06 +01:00

1456 lines
52 KiB
TypeScript

import fs, { readFileSync } 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 * as commandAuthSdk from "openclaw/plugin-sdk/command-auth";
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 * as zalouserSdk from "openclaw/plugin-sdk/zalouser";
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 { expectNoReaddirSyncDuring } from "../../test-utils/fs-scan-assertions.js";
import { listGitTrackedFiles, toRepoRelativePath } from "../../test-utils/repo-files.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 listTrackedRepoTsFiles(dir: string): string[] | null {
const relativeDir = toRepoRelativePath(REPO_ROOT, dir);
if (!relativeDir || relativeDir.startsWith("..")) {
return null;
}
const files = listGitTrackedFiles({ repoRoot: REPO_ROOT, pathspecs: relativeDir });
if (!files) {
return null;
}
return files
.filter(
(line) =>
line.endsWith(".ts") && !line.includes("/dist/") && !line.includes("/node_modules/"),
)
.map((line) => resolve(REPO_ROOT, ...line.split("/")))
.filter((filePath) => fs.existsSync(filePath))
.toSorted();
}
function listRepoTsFiles(dir: string): string[] {
const cached = repoTsFilesCache.get(dir);
if (cached !== undefined) {
return cached;
}
const trackedFiles = listTrackedRepoTsFiles(dir);
if (trackedFiles) {
repoTsFilesCache.set(dir, trackedFiles);
return trackedFiles;
}
const entries = fs.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",
"isImplicitSameChatApprovalAuthorization",
"markImplicitSameChatApprovalAuthorization",
"resolveApprovalApprovers",
]);
expectSourceMentions("reply-chunking", [
"chunkText",
"chunkTextWithMode",
"isSilentReplyPayloadText",
"isSilentReplyText",
]);
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("lists repo source candidates from git without walking SDK boundary roots", () => {
try {
expectNoReaddirSyncDuring(() => {
repoTsFilesCache.clear();
const files = listRepoTsFiles(resolve(REPO_ROOT, "src"));
expect(files.length).toBeGreaterThan(0);
expect(files.every((file) => file.endsWith(".ts"))).toBe(true);
});
} finally {
repoTsFilesCache.clear();
}
});
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-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("channel-inbound", [
"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 repeated silent-token semantics visible through the reply-chunking subpath", async () => {
const replyChunkingSdk = await importResolvedPluginSdkSubpath(
"openclaw/plugin-sdk/reply-chunking",
);
expect(replyChunkingSdk.isSilentReplyText("NO_REPLY\n\nNO_REPLY")).toBe(true);
expect(replyChunkingSdk.isSilentReplyPayloadText("NO_REPLY\n\nNO_REPLY")).toBe(true);
expect(replyChunkingSdk.isSilentReplyText("HEARTBEAT_OK\nHEARTBEAT_OK", "HEARTBEAT_OK")).toBe(
true,
);
expect(replyChunkingSdk.isSilentReplyText("Visible update\n\nNO_REPLY")).toBe(false);
});
it("keeps the Zalouser command-auth compatibility facade importable", () => {
expect(zalouserSdk.resolveSenderCommandAuthorization).toBe(
commandAuthSdk.resolveSenderCommandAuthorization,
);
});
it("exports single-provider plugin entry helpers from the dedicated subpath", () => {
expect(providerEntrySdk.defineSingleProviderPluginEntry).toBe(
providerEntryDirectSdk.defineSingleProviderPluginEntry,
);
});
});