| |
| 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 type { |
| ChannelMessageActionContext as CoreChannelMessageActionContext, |
| OpenClawPluginApi as CoreOpenClawPluginApi, |
| PluginRuntime as CorePluginRuntime, |
| } from "openclaw/plugin-sdk/core"; |
| import type { |
| GetReplyOptions as ReplyRuntimeGetReplyOptions, |
| ReplyDispatchBeforeDeliverOptions as ReplyRuntimeBeforeDeliverOptions, |
| ReplyDispatcher as ReplyRuntimeDispatcher, |
| } from "openclaw/plugin-sdk/reply-runtime"; |
| import ts from "typescript"; |
| import { beforeAll, describe, expect, expectTypeOf, it } from "vitest"; |
| import { |
| buildPluginSdkPackageExports, |
| deprecatedPublicPluginSdkEntrypoints, |
| packagedPrivatePluginSdkRuntimeEntrypoints, |
| pluginSdkEntrypoints, |
| privateLocalOnlyPluginSdkEntrypoints, |
| publicPluginSdkSubpaths as pluginSdkSubpaths, |
| } from "../../../scripts/lib/plugin-sdk-entries.mts"; |
| import type { |
| ChannelMessageActionContext, |
| BaseProbeResult, |
| BaseTokenResolution, |
| ChannelAgentTool, |
| ChannelAccountSnapshot, |
| ChannelGroupContext, |
| ChannelMessageActionAdapter, |
| ChannelMessageActionName, |
| ChannelMessageToolDiscovery, |
| ChannelStatusIssue, |
| ChannelThreadingContext, |
| ChannelThreadingToolContext, |
| } from "../../channels/plugins/types.public.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 { 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-context", |
| "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-live", |
| "test-live-auth", |
| "test-media-generation", |
| "test-media-understanding", |
| "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: [ |
| "loadBundledPluginPublicSurfaceModuleSyncCore", |
| "resolveBrowserControlAuth", |
| "shouldAutoGenerateBrowserAuth", |
| "ensureBrowserControlAuth", |
| ], |
| omits: [ |
| "resolveGatewayAuth", |
| "writeConfigFile", |
| "generateBrowserControlToken", |
| "ensureGatewayStartupAuth", |
| ], |
| }, |
| { |
| subpath: "browser-profiles", |
| artifactBasename: "browser-profiles.js", |
| mentions: [ |
| "loadBundledPluginPublicSurfaceModuleSyncCore", |
| "resolveBrowserConfig", |
| "resolveProfile", |
| ], |
| omits: [ |
| "resolveBrowserSsrFPolicy", |
| "ensureDefaultProfile", |
| "ensureDefaultUserBrowserProfile", |
| "normalizeHexColor", |
| ], |
| }, |
| ]; |
|
|
| 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", |
| ], |
| }, |
| ]; |
|
|
| 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, "loadBundledPluginPublicSurfaceModuleSyncCore") |
| ); |
| } |
|
|
| describe("plugin-sdk subpath exports", () => { |
| let deprecatedChannelRuntimeMatches: string[] = []; |
|
|
| beforeAll(() => { |
| deprecatedChannelRuntimeMatches = 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"], |
| }); |
| }); |
|
|
| it("keeps the public entrypoint catalog, package exports, and support-status docs aligned", () => { |
| const docs = readFileSync(resolve(REPO_ROOT, "docs/plugins/sdk-subpaths.md"), "utf8"); |
| const packageExports = buildPluginSdkPackageExports(); |
| const documentedSubpaths = new Set( |
| [...docs.matchAll(/`plugin-sdk\/([a-z0-9-]+)`/gu)].map((match) => match[1]), |
| ); |
|
|
| expect(docs).toContain("scripts/lib/plugin-sdk-entrypoints.json"); |
| expect(docs).toContain("scripts/lib/plugin-sdk-private-local-only-subpaths.json"); |
| expect(docs).toContain("scripts/lib/plugin-sdk-deprecated-public-subpaths.json"); |
| expect(docs).toContain("selected private-local entries"); |
|
|
| for (const subpath of pluginSdkSubpaths) { |
| expect(packageExports).toHaveProperty(`./plugin-sdk/${subpath}`, { |
| types: `./dist/plugin-sdk/${subpath}.d.ts`, |
| default: `./dist/plugin-sdk/${subpath}.js`, |
| }); |
| } |
| const packagedPrivateSubpaths = new Set(packagedPrivatePluginSdkRuntimeEntrypoints); |
| for (const subpath of privateLocalOnlyPluginSdkEntrypoints) { |
| const packageExport = packageExports[`./plugin-sdk/${subpath}`]; |
| if (packagedPrivateSubpaths.has(subpath)) { |
| expect(packageExport).toEqual({ default: `./dist/plugin-sdk/${subpath}.js` }); |
| } else { |
| expect(packageExport).toBeUndefined(); |
| } |
| } |
| for (const subpath of deprecatedPublicPluginSdkEntrypoints) { |
| expect(pluginSdkSubpaths).toContain(subpath); |
| } |
| for (const subpath of documentedSubpaths) { |
| expect(pluginSdkEntrypoints).toContain(subpath); |
| } |
| }); |
|
|
| 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", () => { |
| expectSourceContract("expect-runtime", { |
| mentions: ["expectDefined"], |
| omits: ["first", "last"], |
| }); |
| 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("media-local-roots", [ |
| "getAgentScopedMediaLocalRoots", |
| "getAgentScopedMediaLocalRootsForSources", |
| ]); |
| expectSourceMentions("approval-auth-runtime", [ |
| "createResolvedApproverActionAuthAdapter", |
| "isImplicitSameChatApprovalAuthorization", |
| "markImplicitSameChatApprovalAuthorization", |
| "resolveApprovalApprovers", |
| ]); |
| expectSourceContract("approval-reference-runtime", { |
| mentions: ["buildApprovalResolutionRef"], |
| omits: ["resolveApprovalOverGateway", "withOperatorApprovalsGatewayClient"], |
| }); |
| expectSourceMentions("reply-chunking", [ |
| "chunkText", |
| "chunkTextWithMode", |
| "isSilentReplyPayloadText", |
| "isSilentReplyText", |
| ]); |
| expectSourceMentions("reply-history", [ |
| "buildInboundHistoryFromEntries", |
| "buildInboundHistoryFromMap", |
| "buildPendingHistoryContextFromMap", |
| "clearHistoryEntriesIfEnabled", |
| "createChannelHistoryWindow", |
| "recordPendingHistoryEntryIfEnabled", |
| ]); |
| expectSourceContract("reply-runtime", { |
| omits: [ |
| "buildPendingHistoryContextFromMap", |
| "clearHistoryEntriesIfEnabled", |
| "recordPendingHistoryEntryIfEnabled", |
| "DEFAULT_GROUP_HISTORY_LIMIT", |
| "requestedSessionId", |
| "resumeRequestedSession", |
| ], |
| }); |
| expectSourceMentions("account-helpers", ["createAccountListHelpers"]); |
| expectSourceMentions("channel-actions", ["optionalStringEnum", "stringEnum"]); |
| expectSourceContract("channel-secret-basic-runtime", { |
| mentions: [ |
| "createSimpleChannelSecretContract", |
| "collectSimpleChannelFieldAssignments", |
| "collectConditionalChannelFieldAssignments", |
| "collectSecretInputAssignment", |
| "getChannelSurface", |
| "pushAssignment", |
| "pushInactiveSurfaceWarning", |
| "ResolverContext", |
| "SecretTargetRegistryEntry", |
| ], |
| omits: ["collectNestedChannelTtsAssignments"], |
| }); |
| 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("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("gateway-runtime", ["createOperatorApprovalsGatewayClient"]); |
| expectSourceMentions("conversation-runtime", [ |
| "recordInboundSession", |
| "recordInboundSessionMetaSafe", |
| "resolveConversationLabel", |
| ]); |
| expectSourceMentions("directory-runtime", [ |
| "createChannelDirectoryAdapter", |
| "createRuntimeDirectoryLiveAdapter", |
| "listDirectoryEntriesFromSources", |
| "listResolvedDirectoryEntriesFromSources", |
| ]); |
| expectSourceContract("memory-core-host-engine-foundation", { |
| mentions: ['from "../../packages/memory-host-sdk/src/host/fs-utils.js"'], |
| omits: ['from "../../packages/memory-host-sdk/src/engine-foundation.js"'], |
| }); |
| expectSourceContract("memory-core-host-engine-curated", { |
| mentions: ["extractProjectKeysFromCuratedEntry"], |
| }); |
| expectSourceContract("memory-core-host-engine-fs", { |
| mentions: ["resolveUserPath", 'from "../infra/fs-safe.js"'], |
| }); |
| expectSourceContract("memory-core-host-engine-schema", { |
| mentions: ["ensureMemoryIndexSchema", "loadSqliteVecExtension"], |
| }); |
| expectSourceContract("memory-core-host-runtime-core", { |
| mentions: ["SILENT_REPLY_TOKEN", "resolveMemorySearchConfig", "MemoryPluginRuntime"], |
| omits: [ |
| 'export * from "../../packages/memory-host-sdk/src/runtime-core.js";', |
| "recordMemoryArtifactWriteProvenance", |
| ], |
| }); |
| expectSourceContract("memory-core-host-runtime-cli", { |
| mentions: ["defaultRuntime", "withManager", "withProgressTotals"], |
| }); |
| expectSourceContract("memory-core-host-runtime-files", { |
| mentions: ["listMemoryFiles", "normalizeExtraMemoryPaths", "MemorySearchResult"], |
| omits: ['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", [ |
| "formatEnvelopeTimestamp", |
| "expectPairingReplyText", |
| ]); |
| expectSourceMentions("provider-test-contracts", [ |
| "expectPassthroughReplayPolicy", |
| "runRealtimeSttLiveTest", |
| ]); |
| expectSourceMentions("test-env", [ |
| "withEnv", |
| "withServer", |
| "withTempHome", |
| "createMockIncomingRequest", |
| "withFetchPreconnect", |
| ]); |
| expectSourceOmits("test-env", [ |
| "collectProviderApiKeys", |
| "createRequestCaptureJsonFetch", |
| "isLiveTestEnabled", |
| "resolveConfiguredLiveVideoModels", |
| ]); |
| expectSourceMentions("test-live", [ |
| "createSingleUserPromptMessage", |
| "isLiveTestEnabled", |
| "isBillingErrorMessage", |
| "isModelNotFoundErrorMessage", |
| ]); |
| expectSourceMentions("test-live-auth", [ |
| "collectProviderApiKeys", |
| "getShellEnvAppliedKeys", |
| "maybeLoadShellEnvForGenerationProviders", |
| ]); |
| expectSourceMentions("test-media-understanding", [ |
| "createRequestCaptureJsonFetch", |
| "installPinnedHostnameTestHooks", |
| ]); |
| expectSourceMentions("test-media-generation", [ |
| "encodePngRgba", |
| "parseProviderModelMap", |
| "resolveConfiguredLiveMusicModels", |
| "resolveConfiguredLiveVideoModels", |
| ]); |
| 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", () => { |
| expect(deprecatedChannelRuntimeMatches).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("keeps channel runtime helpers on focused subpaths", () => { |
| expectSourceOmits("channel-runtime-context", [ |
| "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", |
| "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", |
| "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"], |
| }); |
| 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", |
| "DEFAULT_EMOJIS", |
| ]); |
| |
| |
| expectSourceMentions("channel-feedback", [ |
| "shouldAckReactionForWhatsApp", |
| "WhatsAppAckReactionMode", |
| ]); |
| expectRepoSourceOmitsSnippet("src/channels/ack-reactions.ts", "shouldAckReactionForWhatsApp"); |
| expectRepoSourceOmitsSnippet("src/channels/ack-reactions.ts", "WhatsAppAckReactionMode"); |
| expectSourceMentions("status-helpers", [ |
| "appendMatchMetadata", |
| "asString", |
| "collectIssuesForEnabledAccounts", |
| "isRecord", |
| "resolveEnabledConfiguredAccountId", |
| ]); |
| expectSourceMentions("command-auth", [ |
| "buildCommandTextFromArgs", |
| "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", |
| "defineSelfHostedOpenAICompatibleProvider", |
| "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", |
| "resolveFamilyForwardCompatModel", |
| ], |
| omits: ["applyOpenAIConfig", "buildKilocodeModelDefinition", "discoverHuggingfaceModels"], |
| }); |
| expectSourceContract("provider-catalog-shared", { |
| mentions: ["buildSingleProviderApiKeyCatalog", "buildPairedProviderApiKeyCatalog"], |
| omits: ["buildDeepSeekProvider", "buildVeniceProvider"], |
| }); |
|
|
| expectSourceMentions("setup", [ |
| "DEFAULT_ACCOUNT_ID", |
| "createAllowFromSection", |
| "createDelegatedSetupWizardProxy", |
| "createTopLevelChannelDmPolicy", |
| "mergeAllowFromEntries", |
| ]); |
| expectSourceMentions("setup-tools", ["formatCliCommand", "detectBinary", "formatDocsLink"]); |
| expectSourceMentions("lazy-runtime", ["createLazyRuntimeSurface", "createLazyRuntimeModule"]); |
| expectSourceMentions("agent-harness", ["./agent-harness-runtime.js"]); |
| expectSourceMentions("agent-harness-runtime", [ |
| "AgentHarness", |
| "EmbeddedAgentCompactResult", |
| "agentHarnessStructuredInput", |
| ]); |
| expectSourceOmitsSnippet("agent-runtime", "./sglang.js"); |
| expectSourceOmitsSnippet("agent-runtime", "./vllm.js"); |
| expectSourceOmitsSnippet("agent-runtime", "../../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>(); |
| type PrivateResumeOptionKeys = Extract< |
| keyof ReplyRuntimeGetReplyOptions, |
| "requestedSessionId" | "resumeRequestedSession" |
| >; |
| expectTypeOf<PrivateResumeOptionKeys>().toEqualTypeOf<never>(); |
| type ReplyRuntimeAppendBeforeDeliverOptions = Parameters< |
| NonNullable<ReplyRuntimeDispatcher["appendBeforeDeliver"]> |
| >[1]; |
| expectTypeOf<ReplyRuntimeAppendBeforeDeliverOptions>().toEqualTypeOf< |
| ReplyRuntimeBeforeDeliverOptions | undefined |
| >(); |
| }); |
|
|
| it("keeps the supported root SDK contract importable through core", async () => { |
| const coreSdk = await importResolvedPluginSdkSubpath("openclaw/plugin-sdk/core"); |
|
|
| expect(coreSdk.definePluginEntry).toBe(coreDirectSdk.definePluginEntry); |
| expect(coreSdk.optionalStringEnum).toBe(coreDirectSdk.optionalStringEnum); |
| expect(coreSdk.prepareMemorySystemPromptAddition).toBe( |
| coreDirectSdk.prepareMemorySystemPromptAddition, |
| ); |
| }); |
|
|
| it("keeps focused SDK subpaths importable", async () => { |
| const channelActionsSdk = await importResolvedPluginSdkSubpath( |
| "openclaw/plugin-sdk/channel-actions", |
| ); |
| 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(pluginEntrySdk.definePluginEntry).toBe(coreDirectSdk.definePluginEntry); |
| expect(channelActionsSdk.optionalStringEnum).toBe(channelActionsDirectSdk.optionalStringEnum); |
| expect(channelActionsSdk.stringEnum).toBe(channelActionsDirectSdk.stringEnum); |
| expectSourceMentions("error-runtime", [ |
| "formatUncaughtError", |
| "isApprovalNotFoundError", |
| "PlatformMessageNotDispatchedError", |
| ]); |
|
|
| 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 deprecated public SDK shims importable during migrations", async () => { |
| expect(deprecatedPublicPluginSdkEntrypoints.length).toBeGreaterThan(0); |
|
|
| for (const subpath of deprecatedPublicPluginSdkEntrypoints) { |
| const mod = await importResolvedPluginSdkSubpath(`openclaw/plugin-sdk/${subpath}`); |
| expect(typeof mod, `deprecated subpath ${subpath} should resolve`).toBe("object"); |
| } |
| }); |
|
|
| 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); |
| }); |
| }); |
| |
|
|