refactor(test): remove legacy extension test seams

This commit is contained in:
Peter Steinberger
2026-04-20 13:16:36 +01:00
parent 869950564f
commit 1e4f3f2123
14 changed files with 221 additions and 282 deletions

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { makeCfg } from "./reply.triggers.trigger-handling.test-harness.js";
import { makeCfg } from "../../test/helpers/auto-reply/trigger-handling-test-harness.js";
import { buildGroupChatContext, buildGroupIntro } from "./reply/groups.js";
type GetReplyFromConfig = typeof import("./reply.js").getReplyFromConfig;

View File

@@ -7,7 +7,7 @@ import {
makeCfg,
requireSessionStorePath,
withTempHome,
} from "./reply.triggers.trigger-handling.test-harness.js";
} from "../../test/helpers/auto-reply/trigger-handling-test-harness.js";
type GetReplyFromConfig = typeof import("./reply.js").getReplyFromConfig;

View File

@@ -1,9 +1,6 @@
import fs from "node:fs/promises";
import { join } from "node:path";
import { describe, expect, it, vi } from "vitest";
import { loadSessionStore, resolveSessionKey } from "../config/sessions.js";
import { registerGroupIntroPromptCases } from "./reply.triggers.group-intro-prompts.cases.js";
import { registerTriggerHandlingUsageSummaryCases } from "./reply.triggers.trigger-handling.filters-usage-summary-current-model-provider.cases.js";
import {
expectInlineCommandHandledAndStripped,
getAbortEmbeddedPiRunMock,
@@ -16,7 +13,10 @@ import {
requireSessionStorePath,
runGreetingPromptForBareNewOrReset,
withTempHome,
} from "./reply.triggers.trigger-handling.test-harness.js";
} from "../../test/helpers/auto-reply/trigger-handling-test-harness.js";
import { loadSessionStore, resolveSessionKey } from "../config/sessions.js";
import { registerGroupIntroPromptCases } from "./reply.triggers.group-intro-prompts.cases.js";
import { registerTriggerHandlingUsageSummaryCases } from "./reply.triggers.trigger-handling.filters-usage-summary-current-model-provider.cases.js";
import { withFullRuntimeReplyConfig } from "./reply/get-reply-fast-path.js";
import { enqueueFollowupRun, getFollowupQueueDepth, type FollowupRun } from "./reply/queue.js";
import { HEARTBEAT_TOKEN } from "./tokens.js";

View File

@@ -44,9 +44,6 @@ const GUARDED_CHANNEL_EXTENSIONS = new Set([
"zalo",
"zalouser",
]);
// Shared config validation intentionally consumes this curated Telegram contract.
const ALLOWED_CORE_CHANNEL_SDK_SUBPATHS = new Set(["telegram-command-config"]);
function bundledPluginFile(pluginId: string, relativePath: string): string {
const rootDir = bundledPluginRoots.get(pluginId);
if (!rootDir) {
@@ -509,9 +506,6 @@ function expectCoreSourceStaysOffPluginSpecificSdkFacades(file: string, imports:
continue;
}
const targetSubpath = specifier.split("/plugin-sdk/")[1]?.replace(/\.[cm]?[jt]sx?$/u, "") ?? "";
if (ALLOWED_CORE_CHANNEL_SDK_SUBPATHS.has(targetSubpath)) {
continue;
}
const targetExtensionId =
[...GUARDED_CHANNEL_EXTENSIONS].find(
(extensionId) =>

View File

@@ -52,11 +52,6 @@ export { installCommonResolveTargetErrorCases } from "../test-helpers/resolve-ta
export { sanitizeTerminalText } from "../terminal/safe-text.js";
export { withStateDirEnv } from "../test-helpers/state-dir-env.js";
export { countLines, hasBalancedFences } from "../test-utils/chunk-test-helpers.js";
export {
loadBundledPluginPublicSurfaceSync,
loadBundledPluginTestApiSync,
resolveRelativeBundledPluginPublicModuleId,
} from "../test-utils/bundled-plugin-public-surface.js";
export { expectGeneratedTokenPersistedToGatewayAuth } from "../test-utils/auth-token-assertions.js";
export { captureEnv, withEnv, withEnvAsync } from "../test-utils/env.js";
export { withFetchPreconnect, type FetchMock } from "../test-utils/fetch-mock.js";

View File

@@ -8,33 +8,6 @@ const REPO_ROOT = resolve(SRC_ROOT, "..");
const sourceCache = new Map<string, string>();
const tsFilesCache = new Map<string, string[]>();
const ALLOWED_BUNDLED_CAPABILITY_METADATA_CONSUMERS = new Set([
"src/media-generation/provider-capabilities.contract.test.ts",
"src/plugins/bundled-capability-metadata.test.ts",
"src/plugins/contracts/boundary-invariants.test.ts",
]);
const ALLOWED_EXTENSION_PATH_STRING_TESTS = new Set([
"src/plugin-sdk/browser-maintenance.test.ts",
"src/channels/plugins/bundled.shape-guard.test.ts",
"src/cli/capability-cli.test.ts",
"src/commands/doctor-legacy-config.migrations.test.ts",
"src/plugins/contracts/bundled-extension-config-api-guardrails.test.ts",
"src/scripts/test-projects.test.ts",
]);
const ALLOWED_CONTRACT_BUNDLED_PATH_HELPERS = new Set([
"src/plugins/contracts/boundary-invariants.test.ts",
"src/plugins/contracts/plugin-sdk-index.bundle.test.ts",
"src/plugins/contracts/plugin-sdk-runtime-api-guardrails.test.ts",
]);
const ALLOWED_CHANNEL_BUNDLED_METADATA_CONSUMERS = new Set([
"src/channels/plugins/bundled.ts",
"src/channels/plugins/contracts/runtime-artifacts.ts",
"src/channels/plugins/session-conversation.bundled-fallback.test.ts",
]);
type FileFilter = {
excludeTests?: boolean;
testOnly?: boolean;
@@ -90,7 +63,11 @@ describe("plugin contract boundary invariants", () => {
it("keeps bundled-capability-metadata confined to contract/test inventory", () => {
const files = listTsFiles("src");
const offenders = files.filter((file) => {
if (ALLOWED_BUNDLED_CAPABILITY_METADATA_CONSUMERS.has(file)) {
if (
file === "src/plugins/contracts/boundary-invariants.test.ts" ||
file.endsWith(".contract.test.ts") ||
file.endsWith("-capability-metadata.test.ts")
) {
return false;
}
return readRepoSource(file).includes("contracts/inventory/bundled-capability-metadata");
@@ -109,9 +86,6 @@ describe("plugin contract boundary invariants", () => {
it("keeps core tests off bundled extension deep imports", () => {
const files = listTsFiles("src", { testOnly: true });
const offenders = files.filter((file) => {
if (ALLOWED_EXTENSION_PATH_STRING_TESTS.has(file)) {
return false;
}
const source = readRepoSource(file);
return (
/from\s+["'][^"']*extensions\/.+(?:api|runtime-api|test-api)\.js["']/u.test(source) ||
@@ -125,7 +99,7 @@ describe("plugin contract boundary invariants", () => {
it("keeps plugin contract tests off bundled path helpers unless the test is explicitly about paths", () => {
const files = listTsFiles("src/plugins/contracts", { testOnly: true });
const offenders = files.filter((file) => {
if (ALLOWED_CONTRACT_BUNDLED_PATH_HELPERS.has(file)) {
if (file === "src/plugins/contracts/boundary-invariants.test.ts") {
return false;
}
return readRepoSource(file).includes("test/helpers/bundled-plugin-paths");
@@ -136,9 +110,6 @@ describe("plugin contract boundary invariants", () => {
it("keeps channel production code off bundled-plugin-metadata helpers", () => {
const files = listTsFiles("src/channels", { excludeTests: true });
const offenders = files.filter((file) => {
if (ALLOWED_CHANNEL_BUNDLED_METADATA_CONSUMERS.has(file)) {
return false;
}
return readRepoSource(file).includes("plugins/bundled-plugin-metadata");
});
expect(offenders).toEqual([]);

View File

@@ -1,30 +0,0 @@
import { loadBundledPluginApiSync } from "../../test-utils/bundled-plugin-public-surface.js";
import type { ProviderPlugin } from "../types.js";
export type ProviderContractEntry = {
pluginId: string;
provider: ProviderPlugin;
};
let providerContractRegistryCache: ProviderContractEntry[] | null = null;
type ProviderApiSurface<TFactoryName extends string> = Record<TFactoryName, () => ProviderPlugin>;
type AnthropicApiSurface = ProviderApiSurface<"buildAnthropicProvider">;
type GoogleApiSurface = ProviderApiSurface<"buildGoogleProvider" | "buildGoogleGeminiCliProvider">;
type OpenAIApiSurface = ProviderApiSurface<
"buildOpenAIProvider" | "buildOpenAICodexProviderPlugin"
>;
export function loadVitestProviderContractRegistry(): ProviderContractEntry[] {
const anthropicApi = loadBundledPluginApiSync<AnthropicApiSurface>("anthropic");
const googleApi = loadBundledPluginApiSync<GoogleApiSurface>("google");
const openAIApi = loadBundledPluginApiSync<OpenAIApiSurface>("openai");
providerContractRegistryCache ??= [
{ pluginId: "anthropic", provider: anthropicApi.buildAnthropicProvider() },
{ pluginId: "google", provider: googleApi.buildGoogleProvider() },
{ pluginId: "google", provider: googleApi.buildGoogleGeminiCliProvider() },
{ pluginId: "openai", provider: openAIApi.buildOpenAIProvider() },
{ pluginId: "openai", provider: openAIApi.buildOpenAICodexProviderPlugin() },
];
return providerContractRegistryCache;
}

View File

@@ -46,32 +46,32 @@ describe("plugin contract registry scoped retries", () => {
.mockReturnValueOnce(
createMockRuntimeRegistry({
plugin: {
id: "xai",
id: "arcee",
status: "error",
error: "transient xai load failure",
error: "transient arcee load failure",
providerIds: [],
webFetchProviderIds: [],
webSearchProviderIds: [],
},
diagnostics: [{ pluginId: "xai", message: "transient xai load failure" }],
diagnostics: [{ pluginId: "arcee", message: "transient arcee load failure" }],
}),
)
.mockReturnValueOnce(
createMockRuntimeRegistry({
plugin: {
id: "xai",
id: "arcee",
status: "loaded",
providerIds: ["xai"],
providerIds: ["arcee"],
webFetchProviderIds: [],
webSearchProviderIds: ["grok"],
webSearchProviderIds: [],
},
providers: [
{
pluginId: "xai",
pluginId: "arcee",
provider: {
id: "xai",
label: "xAI",
docsPath: "/providers/xai",
id: "arcee",
label: "Arcee",
docsPath: "/providers/arcee",
auth: [],
} as ProviderPlugin,
},
@@ -82,12 +82,15 @@ describe("plugin contract registry scoped retries", () => {
vi.doMock("../bundled-capability-runtime.js", () => ({
loadBundledCapabilityRuntimeRegistry,
}));
vi.doMock("../provider-contract-public-artifacts.js", () => ({
resolveBundledExplicitProviderContractsFromPublicArtifacts: () => null,
}));
const { resolveProviderContractProvidersForPluginIds } = await import("./registry.js");
expect(
resolveProviderContractProvidersForPluginIds(["xai"]).map((provider) => provider.id),
).toEqual(["xai"]);
resolveProviderContractProvidersForPluginIds(["arcee"]).map((provider) => provider.id),
).toEqual(["arcee"]);
expect(loadBundledCapabilityRuntimeRegistry).toHaveBeenCalledTimes(2);
});
@@ -97,36 +100,36 @@ describe("plugin contract registry scoped retries", () => {
.mockReturnValueOnce(
createMockRuntimeRegistry({
plugin: {
id: "xai",
id: "searxng",
status: "error",
error: "transient grok load failure",
error: "transient searxng load failure",
providerIds: [],
webFetchProviderIds: [],
webSearchProviderIds: [],
},
diagnostics: [{ pluginId: "xai", message: "transient grok load failure" }],
diagnostics: [{ pluginId: "searxng", message: "transient searxng load failure" }],
}),
)
.mockReturnValueOnce(
createMockRuntimeRegistry({
plugin: {
id: "xai",
id: "searxng",
status: "loaded",
providerIds: ["xai"],
providerIds: [],
webFetchProviderIds: [],
webSearchProviderIds: ["grok"],
webSearchProviderIds: ["searxng"],
},
webSearchProviders: [
{
pluginId: "xai",
pluginId: "searxng",
provider: {
id: "grok",
label: "Grok Search",
hint: "Search the web with Grok",
envVars: ["XAI_API_KEY"],
placeholder: "XAI_API_KEY",
signupUrl: "https://x.ai",
credentialPath: "plugins.entries.xai.config.webSearch.apiKey",
id: "searxng",
label: "SearXNG",
hint: "Search the web with SearXNG",
envVars: ["SEARXNG_URL"],
placeholder: "https://search.example.test",
signupUrl: "https://docs.searxng.org",
credentialPath: "plugins.entries.searxng.config.webSearch.url",
requiresCredential: true,
getCredentialValue: () => undefined,
setCredentialValue() {},
@@ -144,12 +147,17 @@ describe("plugin contract registry scoped retries", () => {
vi.doMock("../bundled-capability-runtime.js", () => ({
loadBundledCapabilityRuntimeRegistry,
}));
vi.doMock("../web-provider-public-artifacts.explicit.js", () => ({
resolveBundledExplicitWebSearchProvidersFromPublicArtifacts: () => null,
}));
const { resolveWebSearchProviderContractEntriesForPluginId } = await import("./registry.js");
expect(
resolveWebSearchProviderContractEntriesForPluginId("xai").map((entry) => entry.provider.id),
).toEqual(["grok"]);
resolveWebSearchProviderContractEntriesForPluginId("searxng").map(
(entry) => entry.provider.id,
),
).toEqual(["searxng"]);
expect(loadBundledCapabilityRuntimeRegistry).toHaveBeenCalledTimes(2);
});
@@ -180,6 +188,9 @@ describe("plugin contract registry scoped retries", () => {
vi.doMock("../bundled-capability-runtime.js", () => ({
loadBundledCapabilityRuntimeRegistry,
}));
vi.doMock("../provider-contract-public-artifacts.js", () => ({
resolveBundledExplicitProviderContractsFromPublicArtifacts: () => null,
}));
const { requireProviderContractProvider } = await import("./registry.js");
@@ -189,9 +200,9 @@ describe("plugin contract registry scoped retries", () => {
it("uses provider public artifacts before falling back to the bundled runtime registry", async () => {
const loadBundledCapabilityRuntimeRegistry = vi.fn(() => {
throw new Error("provider contract vitest fast path should not hit bundled runtime registry");
throw new Error("provider contract public artifact should not hit bundled runtime registry");
});
const loadVitestProviderContractRegistry = vi.fn(() => [
const resolveBundledExplicitProviderContractsFromPublicArtifacts = vi.fn(() => [
{
pluginId: "openai",
provider: {
@@ -229,8 +240,8 @@ describe("plugin contract registry scoped retries", () => {
vi.doMock("../bundled-capability-runtime.js", () => ({
loadBundledCapabilityRuntimeRegistry,
}));
vi.doMock("./provider-vitest-registry.js", () => ({
loadVitestProviderContractRegistry,
vi.doMock("../provider-contract-public-artifacts.js", () => ({
resolveBundledExplicitProviderContractsFromPublicArtifacts,
}));
const { resolveProviderContractProvidersForPluginIds } = await import("./registry.js");
@@ -238,24 +249,34 @@ describe("plugin contract registry scoped retries", () => {
expect(
resolveProviderContractProvidersForPluginIds(["openai"]).map((provider) => provider.id),
).toEqual(["openai", "openai-codex"]);
expect(loadVitestProviderContractRegistry).toHaveBeenCalledTimes(1);
expect(resolveBundledExplicitProviderContractsFromPublicArtifacts).toHaveBeenCalledTimes(1);
expect(loadBundledCapabilityRuntimeRegistry).not.toHaveBeenCalled();
});
it("uses web search public artifacts before falling back to the bundled runtime registry", async () => {
const loadBundledCapabilityRuntimeRegistry = vi.fn(() => {
throw new Error(
"web search contract vitest fast path should not hit bundled runtime registry",
"web search contract public artifact should not hit bundled runtime registry",
);
});
const loadVitestWebSearchProviderContractRegistry = vi.fn(() => [
const resolveBundledExplicitWebSearchProvidersFromPublicArtifacts = vi.fn(() => [
{
pluginId: "google",
provider: {
id: "gemini",
label: "Gemini",
credentialPath: "plugins.entries.google.config.webSearch.apiKey",
} as WebSearchProviderPlugin,
id: "gemini",
label: "Gemini",
hint: "Search with Gemini",
envVars: ["GEMINI_API_KEY"],
placeholder: "GEMINI_API_KEY",
signupUrl: "https://aistudio.google.com",
credentialPath: "plugins.entries.google.config.webSearch.apiKey",
requiresCredential: true,
getCredentialValue: () => undefined,
setCredentialValue() {},
createTool: () => ({
description: "search",
parameters: {},
execute: async () => ({}),
}),
credentialValue: "AIzaSyDUMMY",
},
]);
@@ -263,8 +284,8 @@ describe("plugin contract registry scoped retries", () => {
vi.doMock("../bundled-capability-runtime.js", () => ({
loadBundledCapabilityRuntimeRegistry,
}));
vi.doMock("./web-provider-vitest-registry.js", () => ({
loadVitestWebSearchProviderContractRegistry,
vi.doMock("../web-provider-public-artifacts.explicit.js", () => ({
resolveBundledExplicitWebSearchProvidersFromPublicArtifacts,
}));
const { resolveWebSearchProviderContractEntriesForPluginId } = await import("./registry.js");
@@ -274,7 +295,7 @@ describe("plugin contract registry scoped retries", () => {
(entry) => entry.provider.id,
),
).toEqual(["gemini"]);
expect(loadVitestWebSearchProviderContractRegistry).toHaveBeenCalledTimes(1);
expect(resolveBundledExplicitWebSearchProvidersFromPublicArtifacts).toHaveBeenCalledTimes(1);
expect(loadBundledCapabilityRuntimeRegistry).not.toHaveBeenCalled();
});

View File

@@ -4,6 +4,7 @@ import {
loadPluginManifestRegistry,
resolveManifestContractPluginIds,
} from "../manifest-registry.js";
import { resolveBundledExplicitProviderContractsFromPublicArtifacts } from "../provider-contract-public-artifacts.js";
import type {
ImageGenerationProviderPlugin,
MediaUnderstandingProviderPlugin,
@@ -16,8 +17,8 @@ import type {
WebFetchProviderPlugin,
WebSearchProviderPlugin,
} from "../types.js";
import { resolveBundledExplicitWebSearchProvidersFromPublicArtifacts } from "../web-provider-public-artifacts.explicit.js";
import { BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS } from "./inventory/bundled-capability-metadata.js";
import { loadVitestProviderContractRegistry } from "./provider-vitest-registry.js";
import { uniqueStrings } from "./shared.js";
import {
loadVitestImageGenerationProviderContractRegistry,
@@ -28,7 +29,6 @@ import {
loadVitestSpeechProviderContractRegistry,
loadVitestVideoGenerationProviderContractRegistry,
} from "./speech-vitest-registry.js";
import { loadVitestWebSearchProviderContractRegistry } from "./web-provider-vitest-registry.js";
type BundledCapabilityRuntimeRegistry = ReturnType<typeof loadBundledCapabilityRuntimeRegistry>;
type CapabilityContractEntry<T> = {
@@ -316,14 +316,12 @@ function loadProviderContractEntriesForPluginId(pluginId: string): ProviderContr
return cached;
}
if (process.env.VITEST) {
const vitestEntries = loadVitestProviderContractRegistry().filter(
(entry) => entry.pluginId === pluginId,
);
if (vitestEntries.length > 0) {
cache.set(pluginId, vitestEntries);
return vitestEntries;
}
const publicArtifactEntries = resolveBundledExplicitProviderContractsFromPublicArtifacts({
onlyPluginIds: [pluginId],
});
if (publicArtifactEntries) {
cache.set(pluginId, publicArtifactEntries);
return publicArtifactEntries;
}
try {
@@ -356,8 +354,14 @@ function loadProviderContractRegistry(): ProviderContractEntry[] {
if (!providerContractRegistryCache) {
try {
providerContractLoadError = undefined;
const vitestEntries = process.env.VITEST ? loadVitestProviderContractRegistry() : [];
const coveredPluginIds = new Set(vitestEntries.map((entry) => entry.pluginId));
const pluginIds = resolveBundledProviderContractPluginIds();
const publicArtifactEntries = pluginIds.flatMap(
(pluginId) =>
resolveBundledExplicitProviderContractsFromPublicArtifacts({
onlyPluginIds: [pluginId],
}) ?? [],
);
const coveredPluginIds = new Set(publicArtifactEntries.map((entry) => entry.pluginId));
const remainingPluginIds = resolveBundledProviderContractPluginIds().filter(
(pluginId) => !coveredPluginIds.has(pluginId),
);
@@ -371,7 +375,7 @@ function loadProviderContractRegistry(): ProviderContractEntry[] {
provider: entry.provider,
}))
: [];
providerContractRegistryCache = [...vitestEntries, ...runtimeEntries];
providerContractRegistryCache = [...publicArtifactEntries, ...runtimeEntries];
} catch (error) {
providerContractLoadError = error instanceof Error ? error : new Error(String(error));
providerContractRegistryCache = [];
@@ -475,8 +479,19 @@ export function resolveWebFetchProviderContractEntriesForPluginId(
function loadWebSearchProviderContractRegistry(): WebSearchProviderContractEntry[] {
if (!webSearchProviderContractRegistryCache) {
const vitestEntries = process.env.VITEST ? loadVitestWebSearchProviderContractRegistry() : [];
const coveredPluginIds = new Set(vitestEntries.map((entry) => entry.pluginId));
const pluginIds = resolveBundledManifestContractPluginIds("webSearchProviders");
const publicArtifactEntries = pluginIds.flatMap((pluginId) =>
(
resolveBundledExplicitWebSearchProvidersFromPublicArtifacts({
onlyPluginIds: [pluginId],
}) ?? []
).map((provider) => ({
pluginId: provider.pluginId,
provider,
credentialValue: resolveWebSearchCredentialValue(provider),
})),
);
const coveredPluginIds = new Set(publicArtifactEntries.map((entry) => entry.pluginId));
const remainingPluginIds = resolveBundledManifestContractPluginIds("webSearchProviders").filter(
(pluginId) => !coveredPluginIds.has(pluginId),
);
@@ -491,7 +506,7 @@ function loadWebSearchProviderContractRegistry(): WebSearchProviderContractEntry
credentialValue: resolveWebSearchCredentialValue(entry.provider),
}))
: [];
webSearchProviderContractRegistryCache = [...vitestEntries, ...runtimeEntries];
webSearchProviderContractRegistryCache = [...publicArtifactEntries, ...runtimeEntries];
}
return webSearchProviderContractRegistryCache;
}
@@ -512,14 +527,16 @@ export function resolveWebSearchProviderContractEntriesForPluginId(
return cached;
}
if (process.env.VITEST) {
const vitestEntries = loadVitestWebSearchProviderContractRegistry().filter(
(entry) => entry.pluginId === pluginId,
);
if (vitestEntries.length > 0) {
cache.set(pluginId, vitestEntries);
return vitestEntries;
}
const publicArtifactEntries = resolveBundledExplicitWebSearchProvidersFromPublicArtifacts({
onlyPluginIds: [pluginId],
})?.map((provider) => ({
pluginId: provider.pluginId,
provider,
credentialValue: resolveWebSearchCredentialValue(provider),
}));
if (publicArtifactEntries) {
cache.set(pluginId, publicArtifactEntries);
return publicArtifactEntries;
}
const entries = loadScopedCapabilityRuntimeRegistryEntries({

View File

@@ -1,30 +0,0 @@
import { loadBundledPluginPublicSurfaceSync } from "../../test-utils/bundled-plugin-public-surface.js";
import type { WebSearchProviderPlugin } from "../types.js";
export type WebSearchProviderContractEntry = {
pluginId: string;
provider: WebSearchProviderPlugin;
credentialValue: unknown;
};
let webSearchProviderContractRegistryCache: WebSearchProviderContractEntry[] | null = null;
type GoogleWebSearchContractApiSurface = {
createGeminiWebSearchProvider: () => WebSearchProviderPlugin;
};
export function loadVitestWebSearchProviderContractRegistry(): WebSearchProviderContractEntry[] {
const googleWebSearchContractApi =
loadBundledPluginPublicSurfaceSync<GoogleWebSearchContractApiSurface>({
pluginId: "google",
artifactBasename: "web-search-contract-api.js",
});
webSearchProviderContractRegistryCache ??= [
{
pluginId: "google",
provider: googleWebSearchContractApi.createGeminiWebSearchProvider(),
credentialValue: "AIzaSyDUMMY",
},
];
return webSearchProviderContractRegistryCache;
}

View File

@@ -0,0 +1,81 @@
import { loadBundledPluginPublicArtifactModuleSync } from "./public-surface-loader.js";
import type { ProviderPlugin } from "./types.js";
type ProviderContractEntry = {
pluginId: string;
provider: ProviderPlugin;
};
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function isProviderPlugin(value: unknown): value is ProviderPlugin {
return (
isRecord(value) &&
typeof value.id === "string" &&
typeof value.label === "string" &&
Array.isArray(value.auth)
);
}
function tryLoadProviderContractApi(pluginId: string): Record<string, unknown> | null {
try {
return loadBundledPluginPublicArtifactModuleSync<Record<string, unknown>>({
dirName: pluginId,
artifactBasename: "provider-contract-api.js",
});
} catch (error) {
if (
error instanceof Error &&
error.message.startsWith("Unable to resolve bundled plugin public surface ")
) {
return null;
}
throw error;
}
}
function collectProviderContractEntries(params: {
pluginId: string;
mod: Record<string, unknown>;
}): ProviderContractEntry[] {
const providers: ProviderContractEntry[] = [];
for (const [name, exported] of Object.entries(params.mod).toSorted(([left], [right]) =>
left.localeCompare(right),
)) {
if (
typeof exported !== "function" ||
exported.length !== 0 ||
!name.startsWith("create") ||
!name.endsWith("Provider")
) {
continue;
}
const candidate = exported();
if (isProviderPlugin(candidate)) {
providers.push({ pluginId: params.pluginId, provider: candidate });
}
}
return providers;
}
export function resolveBundledExplicitProviderContractsFromPublicArtifacts(params: {
onlyPluginIds: readonly string[];
}): ProviderContractEntry[] | null {
const providers: ProviderContractEntry[] = [];
for (const pluginId of [...new Set(params.onlyPluginIds)].toSorted((left, right) =>
left.localeCompare(right),
)) {
const mod = tryLoadProviderContractApi(pluginId);
if (!mod) {
return null;
}
const entries = collectProviderContractEntries({ pluginId, mod });
if (entries.length === 0) {
return null;
}
providers.push(...entries);
}
return providers;
}

View File

@@ -9,32 +9,6 @@ const ALLOWED_EXTENSION_PUBLIC_SURFACE_BASENAMES = new Set(
GUARDED_EXTENSION_PUBLIC_SURFACE_BASENAMES,
);
const allowedNonExtensionTests = new Set<string>([
"src/agents/pi-embedded-runner-extraparams-moonshot.test.ts",
"src/agents/pi-embedded-runner-extraparams.test.ts",
"src/agents/pi-embedded-runner-extraparams-moonshot.test.ts",
"src/channels/plugins/contracts/dm-policy.contract.test.ts",
"src/channels/plugins/contracts/group-policy.contract.test.ts",
"src/commands/channels.surfaces-signal-runtime-errors-channels-status-output.test.ts",
"src/commands/onboard-channels.e2e.test.ts",
"src/gateway/hooks.test.ts",
"src/infra/outbound/deliver.test.ts",
"src/plugins/interactive.test.ts",
"src/plugins/contracts/discovery.contract.test.ts",
"src/plugin-sdk/telegram-command-config.test.ts",
"src/security/audit-channel-slack-command-findings.test.ts",
"src/security/audit-feishu-doc-risk.test.ts",
"src/secrets/runtime-channel-inactive-variants.test.ts",
"src/secrets/runtime-discord-surface.test.ts",
"src/secrets/runtime-inactive-telegram-surfaces.test.ts",
"src/secrets/runtime-legacy-x-search.test.ts",
"src/secrets/runtime-matrix-shadowing.test.ts",
"src/secrets/runtime-matrix-top-level.test.ts",
"src/secrets/runtime-nextcloud-talk-file-precedence.test.ts",
"src/secrets/runtime-telegram-token-inheritance.test.ts",
"src/secrets/runtime-zalo-token-activity.test.ts",
]);
function walk(dir: string, entries: string[] = []): string[] {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const fullPath = path.join(dir, entry.name);
@@ -136,7 +110,7 @@ describe("non-extension test boundaries", () => {
if (imports.length === 0) {
return null;
}
if (allowedNonExtensionTests.has(file) || isAllowedCoreContractSuite(file, imports)) {
if (isAllowedCoreContractSuite(file, imports)) {
return null;
}
return {
@@ -170,20 +144,12 @@ describe("non-extension test boundaries", () => {
expect(imports).toEqual([]);
});
it("keeps bundled plugin public-surface imports on an explicit core allowlist", () => {
const allowed = new Set([
"src/auto-reply/reply.triggers.trigger-handling.test-harness.ts",
"src/agents/models-config.providers.ollama.test.ts",
"src/commands/channel-test-registry.ts",
"src/plugins/contracts/provider-vitest-registry.ts",
"src/plugins/contracts/web-provider-vitest-registry.ts",
"src/plugin-sdk/testing.ts",
]);
it("keeps bundled plugin public-surface imports out of core source", () => {
const files = walkCode(path.join(repoRoot, "src"));
const offenders = files.filter((file) => {
const source = fs.readFileSync(path.join(repoRoot, file), "utf8");
return findBundledPluginPublicSurfaceImports(source).length > 0 && !allowed.has(file);
return findBundledPluginPublicSurfaceImports(source).length > 0;
});
expect(offenders).toEqual([]);

View File

@@ -3,11 +3,11 @@ import fs from "node:fs/promises";
import os from "node:os";
import { join } from "node:path";
import { afterAll, afterEach, beforeAll, expect, vi } from "vitest";
import { clearRuntimeAuthProfileStoreSnapshots } from "../agents/auth-profiles.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resetProviderRuntimeHookCacheForTest } from "../plugins/provider-runtime.js";
import { resolveRelativeBundledPluginPublicModuleId } from "../test-utils/bundled-plugin-public-surface.js";
import { withFastReplyConfig } from "./reply/get-reply-fast-path.js";
import { clearRuntimeAuthProfileStoreSnapshots } from "../../../src/agents/auth-profiles.js";
import { withFastReplyConfig } from "../../../src/auto-reply/reply/get-reply-fast-path.js";
import type { OpenClawConfig } from "../../../src/config/types.openclaw.js";
import { resetProviderRuntimeHookCacheForTest } from "../../../src/plugins/provider-runtime.js";
import { resolveRelativeBundledPluginPublicModuleId } from "../../../src/test-utils/bundled-plugin-public-surface.js";
// Avoid exporting vitest mock types (TS2742 under pnpm + d.ts emit).
type AnyMock = any;
@@ -49,7 +49,7 @@ export function getQueueEmbeddedPiMessageMock(): AnyMock {
}
const installPiEmbeddedMock = () =>
vi.doMock("../agents/pi-embedded.js", () => ({
vi.doMock("../../../src/agents/pi-embedded.js", () => ({
abortEmbeddedPiRun: (...args: unknown[]) => piEmbeddedMocks.abortEmbeddedPiRun(...args),
compactEmbeddedPiSession: (...args: unknown[]) =>
piEmbeddedMocks.compactEmbeddedPiSession(...args),
@@ -65,7 +65,7 @@ const installPiEmbeddedMock = () =>
installPiEmbeddedMock();
vi.doMock("../agents/pi-embedded-runner/runs.js", () => ({
vi.doMock("../../../src/agents/pi-embedded-runner/runs.js", () => ({
abortEmbeddedPiRun: (...args: unknown[]) => piEmbeddedMocks.abortEmbeddedPiRun(...args),
}));
@@ -83,7 +83,7 @@ export function getProviderUsageMocks(): AnyMocks {
return providerUsageMocks;
}
vi.mock("../infra/provider-usage.js", () => providerUsageMocks);
vi.mock("../../../src/infra/provider-usage.js", () => providerUsageMocks);
const modelCatalogMocks = getSharedMocks("openclaw.trigger-handling.model-catalog-mocks", () => ({
loadModelCatalog: vi.fn().mockResolvedValue([
@@ -112,15 +112,15 @@ export function getModelCatalogMocks(): AnyMocks {
}
const installModelCatalogMock = () =>
vi.doMock("../agents/model-catalog.js", () => modelCatalogMocks);
vi.doMock("../../../src/agents/model-catalog.js", () => modelCatalogMocks);
installModelCatalogMock();
vi.doMock("../agents/model-catalog.runtime.js", () => ({
vi.doMock("../../../src/agents/model-catalog.runtime.js", () => ({
loadModelCatalog: (...args: unknown[]) => modelCatalogMocks.loadModelCatalog(...args),
}));
vi.doMock("../plugins/provider-runtime.runtime.js", () => ({
vi.doMock("../../../src/plugins/provider-runtime.runtime.js", () => ({
augmentModelCatalogWithProviderPlugins: async (params: { catalog?: unknown[] }) =>
params.catalog ?? [],
buildProviderAuthDoctorHintWithPlugin: () => undefined,
@@ -150,11 +150,11 @@ export function getModelFallbackMocks(): AnyMocks {
}
const installModelFallbackMock = () =>
vi.doMock("../agents/model-fallback.js", () => modelFallbackMocks);
vi.doMock("../../../src/agents/model-fallback.js", () => modelFallbackMocks);
installModelFallbackMock();
vi.doMock("../infra/git-commit.js", () => ({
vi.doMock("../../../src/infra/git-commit.js", () => ({
resolveCommitHash: vi.fn(() => "abcdef0"),
}));
@@ -302,12 +302,12 @@ export function makeCfg(home: string): OpenClawConfig {
}
export async function loadGetReplyFromConfig() {
return (await import("./reply.js")).getReplyFromConfig;
return (await import("../../../src/auto-reply/reply.js")).getReplyFromConfig;
}
export function installTriggerHandlingReplyHarness(
setGetReplyFromConfig: (
getReplyFromConfig: typeof import("./reply.js").getReplyFromConfig,
getReplyFromConfig: typeof import("../../../src/auto-reply/reply.js").getReplyFromConfig,
) => void,
): void {
beforeAll(async () => {
@@ -357,7 +357,7 @@ export function makeWhatsAppElevatedCfg(
export async function runDirectElevatedToggleAndLoadStore(params: {
cfg: OpenClawConfig;
getReplyFromConfig: typeof import("./reply.js").getReplyFromConfig;
getReplyFromConfig: typeof import("../../../src/auto-reply/reply.js").getReplyFromConfig;
body?: string;
}): Promise<{
text: string | undefined;
@@ -386,7 +386,7 @@ export async function runDirectElevatedToggleAndLoadStore(params: {
export async function expectInlineCommandHandledAndStripped(params: {
home: string;
getReplyFromConfig: typeof import("./reply.js").getReplyFromConfig;
getReplyFromConfig: typeof import("../../../src/auto-reply/reply.js").getReplyFromConfig;
body: string;
stripToken: string;
blockReplyContains: string;
@@ -419,7 +419,7 @@ export async function expectInlineCommandHandledAndStripped(params: {
export async function runGreetingPromptForBareNewOrReset(params: {
home: string;
body: "/new" | "/reset";
getReplyFromConfig: typeof import("./reply.js").getReplyFromConfig;
getReplyFromConfig: typeof import("../../../src/auto-reply/reply.js").getReplyFromConfig;
}) {
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
runEmbeddedPiAgentMock.mockClear();

View File

@@ -4,7 +4,7 @@ import {
providerContractLoadError,
resolveProviderContractProvidersForPluginIds,
} from "../../../src/plugins/contracts/registry.js";
import { loadBundledPluginPublicArtifactModuleSync } from "../../../src/plugins/public-surface-loader.js";
import { resolveBundledExplicitProviderContractsFromPublicArtifacts } from "../../../src/plugins/provider-contract-public-artifacts.js";
import type { ProviderPlugin } from "../../../src/plugins/types.js";
import { installProviderPluginContractSuite } from "./provider-contract-suites.js";
@@ -13,56 +13,10 @@ type ProviderContractEntry = {
provider: ProviderPlugin;
};
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function isProviderPlugin(value: unknown): value is ProviderPlugin {
return (
isRecord(value) &&
typeof value.id === "string" &&
typeof value.label === "string" &&
Array.isArray(value.auth)
);
}
function resolveProviderContractProvidersFromPublicArtifact(
pluginId: string,
): ProviderContractEntry[] | null {
let mod: Record<string, unknown>;
try {
mod = loadBundledPluginPublicArtifactModuleSync<Record<string, unknown>>({
dirName: pluginId,
artifactBasename: "provider-contract-api.js",
});
} catch (error) {
if (
error instanceof Error &&
error.message.startsWith("Unable to resolve bundled plugin public surface ")
) {
return null;
}
throw error;
}
const providers: ProviderContractEntry[] = [];
for (const [name, exported] of Object.entries(mod).toSorted(([left], [right]) =>
left.localeCompare(right),
)) {
if (
typeof exported !== "function" ||
exported.length !== 0 ||
!name.startsWith("create") ||
!name.endsWith("Provider")
) {
continue;
}
const provider = exported();
if (isProviderPlugin(provider)) {
providers.push({ pluginId, provider });
}
}
return providers.length > 0 ? providers : null;
return resolveBundledExplicitProviderContractsFromPublicArtifacts({ onlyPluginIds: [pluginId] });
}
export function describeProviderContracts(pluginId: string) {