Files
openclaw/src/plugins/status.test.ts
Peter Steinberger bb46b79d3c refactor: internalize OpenClaw agent runtime (#85341)
* refactor: extract agent core package

Introduce packages/agent-core as the OpenClaw-owned home for reusable agent loop, harness, session, prompt, and runtime dependency contracts.

* refactor: extract shared llm runtime

Move provider model registries, stream wrappers, OAuth helpers, and LLM utilities into src/llm with plugin-sdk barrels instead of depending on the old embedded runtime layout.

* refactor: remove pi runtime internals

Rename remaining Pi-shaped agent surfaces to OpenClaw agent runtime names, delete obsolete Pi docs and package graph checks, and add the third-party notice for incorporated code.

* refactor: tighten agent session runtime

Make agent-core/runtime dependencies explicit, consolidate compaction and session transcript helpers, and move model/session helpers behind OpenClaw-owned contracts.

* refactor: remove static model and pi auth paths

Drop static model catalogs and Pi auth bridges, move model/provider facts to manifest-owned runtime contracts, and harden internal embedded-agent utilities.

* refactor: remove legacy provider compat paths

* docs: remove agent parity notes

* fix: skip provider wildcard metadata parsing

* refactor: share session extension sdk loading

* refactor: inline acpx proxy error formatter

* refactor: fold edit recovery into edit tool

* fix: accept extension batch separator

* test: align startup provider plugin expectations

* fix: restore provider-scoped release discovery

* test: align static asset packaging expectations

* fix: run static provider catalogs during scoped discovery

* fix: add provider entry catalogs for scoped live discovery

* fix: load lightweight provider catalog entries

* fix: refresh provider-scoped plugin metadata

* fix: keep provider catalog entries on release live path

* fix: keep static manifest models in release live checks

* fix: harden release model discovery

* fix: reduce OpenAI live cache probe reasoning

* fix: disable OpenAI cache probe reasoning

* ci: extend OpenAI gateway live timeout

* fix: extend live gateway model budget

* fix: stabilize release validation regressions

* fix: honor provider aliases in model rows

* fix: stabilize release validation lanes

* fix: stabilize release memory qa

* ci: stabilize release validation lanes

* ci: prefer ipv4 for live docker node calls

* fix: restore shared tool-call stream wrapper

* ci: remove legacy pi test shard alias

* fix: clean up embedded agent test drift

* fix: stabilize runtime alias status

* fix: clean up embedded agent ci drift

* fix: restore release ci invariants

* fix: clean up post-rebase runtime drift

* fix: restore release ci checks

* fix: restore release ci after rebase

* fix: remove stale pi runtime path

* test: align compaction runtime expectations

* test: update plugin prerelease expectations

* fix: handle claude live tool approvals

* fix: stabilize release validation gates

* fix: finish agent runtime import

* test: finish post-rebase agent runtime mocks

* fix: keep codex compaction native

* fix: stabilize codex app-server hook tests

* test: isolate codex diagnostic active run

* test: remove codex diagnostic completion race

# Conflicts:
#	extensions/codex/src/app-server/run-attempt.test.ts

* ci: fix full release manifest performance run id

* refactor: narrow llm plugin sdk boundary

* chore: drop generated google boundary stamps

* fix: repair rebase fallout

* fix: clean up rebased runtime references

* fix: decode codex jwt payloads as base64url

* fix: preserve shipped pi runtime alias

* fix: add scoped sdk virtual modules

* fix: decode llm codex oauth jwt as base64url

* fix: avoid stale vertex adc negative cache

* fix: harden tool arg decoding and codeql path

* fix: keep vertex adc negative checks live

* refactor: consolidate codex jwt and edit helpers

* fix: await codex oauth node runtime imports

* fix: preserve sdk tool and notice contracts

* fix: preserve shipped compat config boundaries

* fix: align codex oauth callback host

* fix: terminate agent-core loop streams on failure

* fix: keep codex oauth callback alive during fallback

* ci: include session tools in critical codeql scans

* fix: keep Cloudflare Anthropic provider auth header

* docs: redirect legacy pi runtime pages

* fix: honor bundled web provider compat discovery

* fix: protect session output spill files

* fix: keep legacy agent dir env blocked

* fix: contain auto-discovered skill symlinks

* fix: harden agent core sdk proxy surfaces

* fix: restore approval reaction sdk compat

* fix: keep live docker runs bounded

* fix: keep codex oauth redirect host aligned

* fix: resolve post-rebase agent runtime drift

* fix: redact anthropic oauth parse failures

* fix: preserve responses strict tool shaping

* fix: repair agent runtime rebase cleanup

* docs: redirect retired parity pages

* fix: bound auto-discovered resources to roots

* fix: repair post-rebase agent test drift

* fix: preserve bundled provider allowlist migration

* fix: preserve manifest-owned provider aliases

* fix: declare photon image dependency

* fix: keep provider headers out of proxy body

* fix: preserve shipped env aliases

* fix: refresh control ui i18n generated state

* fix: quote read fallback paths

* fix: preview edits through configured backend

* test: satisfy core test typecheck

* fix: preserve ZAI usage auth fallback

* test: repair codex diagnostic test

* fix: repair agent runtime rebase drift

* test: finish embedded runner import rename

* fix: repair agent runtime rebase integrations

* test: align compaction oauth fallback expectations

* fix: allow sdk-auth session models

* fix: update doctor tool schema import

* fix: preserve bedrock plugin region

* fix: stream harmony-like prose immediately

* ci: include session runtime in codeql shards

* fix: repair latest rebase integrations

* fix: honor explicit codex websocket transport

* fix: keep openai-compatible credentials provider-scoped

* fix: refresh sdk api baseline after rebase

* fix: route cli runtime aliases through openclaw harness

* test: rename stale harness mock expectation

* test: rename embedded agent overflow calls

* test: clean embedded auth test wording

* test: use openclaw stream types in deepinfra cache test

* fix: refresh sdk api baseline on latest main

* fix: honor bundled discovery compat allowlists

* fix: refresh sdk api baseline after latest rebase

* fix: remove stale rebase imports

* test: rename stale model catalog mock

* test: mock renamed doctor runtime modules

* fix: map canonical kimi env auth

* fix: use internal model registry in bench script

* fix: migrate deepinfra provider catalog entry

* fix: enforce builtin tool suppression

* fix: route compaction auth and proxy payloads safely

* refactor: prune unused llm registry leftovers

* test: update codex hooks session import

* test: fix model picker ci coverage

* test: align model picker auth mock types
2026-05-27 19:24:04 +01:00

1007 lines
32 KiB
TypeScript

import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { PluginMemoryEmbeddingProviderRegistration } from "./registry-types.js";
import {
createCompatibilityNotice,
createCustomHook,
createPluginLoadResult,
createPluginRecord,
createTypedHook,
DEPRECATED_MEMORY_EMBEDDING_PROVIDER_API_MESSAGE,
HOOK_ONLY_MESSAGE,
LEGACY_BEFORE_AGENT_START_MESSAGE,
} from "./status.test-helpers.js";
const loadConfigMock = vi.fn();
const loadOpenClawPluginsMock = vi.fn();
const loadPluginMetadataRegistrySnapshotMock = vi.fn();
const loadPluginManifestRegistryForPluginRegistryMock = vi.fn();
const loadPluginRegistrySnapshotWithMetadataMock = vi.fn();
const loadPluginManifestRegistryForInstalledIndexMock = vi.fn();
const loadPluginMetadataSnapshotMock = vi.fn((rawParams: unknown = {}) => {
const params = rawParams as { index?: unknown };
const manifestRegistry = loadPluginManifestRegistryForInstalledIndexMock(params) ?? {
plugins: [],
diagnostics: [],
};
return {
index: params.index ?? createInstalledPluginIndexSnapshot([]),
manifestRegistry,
plugins: manifestRegistry.plugins,
byPluginId: new Map(
manifestRegistry.plugins.map((plugin: { id: string }) => [plugin.id, plugin]),
),
};
});
const applyPluginAutoEnableMock = vi.fn();
const resolveBundledProviderCompatPluginIdsMock = vi.fn();
const withBundledPluginEnablementCompatMock = vi.fn();
const listImportedBundledPluginFacadeIdsMock = vi.fn();
const listImportedRuntimePluginIdsMock = vi.fn();
let buildPluginSnapshotReport: typeof import("./status.js").buildPluginSnapshotReport;
let buildPluginRegistrySnapshotReport: typeof import("./status.js").buildPluginRegistrySnapshotReport;
let buildPluginDiagnosticsReport: typeof import("./status.js").buildPluginDiagnosticsReport;
let buildPluginInspectReport: typeof import("./status.js").buildPluginInspectReport;
let buildAllPluginInspectReports: typeof import("./status.js").buildAllPluginInspectReports;
let buildPluginCompatibilityNotices: typeof import("./status.js").buildPluginCompatibilityNotices;
let buildPluginCompatibilityWarnings: typeof import("./status.js").buildPluginCompatibilityWarnings;
let formatPluginCompatibilityNotice: typeof import("./status.js").formatPluginCompatibilityNotice;
let summarizePluginCompatibility: typeof import("./status.js").summarizePluginCompatibility;
vi.mock("../config/config.js", () => ({
getRuntimeConfig: () => loadConfigMock(),
loadConfig: () => loadConfigMock(),
}));
vi.mock("../config/plugin-auto-enable.js", () => ({
applyPluginAutoEnable: (...args: unknown[]) => applyPluginAutoEnableMock(...args),
}));
vi.mock("./loader.js", () => ({
loadOpenClawPlugins: (...args: unknown[]) => loadOpenClawPluginsMock(...args),
}));
vi.mock("./runtime/metadata-registry-loader.js", () => ({
loadPluginMetadataRegistrySnapshot: (...args: unknown[]) =>
loadPluginMetadataRegistrySnapshotMock(...args),
}));
vi.mock("./plugin-registry.js", () => ({
loadPluginManifestRegistryForPluginRegistry: (...args: unknown[]) =>
loadPluginManifestRegistryForPluginRegistryMock(...args),
loadPluginRegistrySnapshotWithMetadata: (...args: unknown[]) =>
loadPluginRegistrySnapshotWithMetadataMock(...args),
}));
vi.mock("./manifest-registry-installed.js", () => ({
loadPluginManifestRegistryForInstalledIndex: (...args: unknown[]) =>
loadPluginManifestRegistryForInstalledIndexMock(...args),
resolveInstalledManifestRegistryIndexFingerprint: () => "test-installed-index",
}));
vi.mock("./plugin-metadata-snapshot.js", () => ({
loadPluginMetadataSnapshot: (...args: unknown[]) => loadPluginMetadataSnapshotMock(...args),
resolvePluginMetadataSnapshot: (params?: { pluginMetadataSnapshot?: unknown }) =>
params?.pluginMetadataSnapshot ?? loadPluginMetadataSnapshotMock(params),
}));
vi.mock("./providers.js", () => ({
resolveBundledProviderCompatPluginIds: (...args: unknown[]) =>
resolveBundledProviderCompatPluginIdsMock(...args),
}));
vi.mock("./bundled-compat.js", () => ({
withBundledPluginEnablementCompat: (...args: unknown[]) =>
withBundledPluginEnablementCompatMock(...args),
}));
vi.mock("../plugin-sdk/facade-runtime.js", () => ({
listImportedBundledPluginFacadeIds: (...args: unknown[]) =>
listImportedBundledPluginFacadeIdsMock(...args),
}));
vi.mock("./runtime.js", () => ({
getActivePluginChannelRegistry: () => null,
listImportedRuntimePluginIds: (...args: unknown[]) => listImportedRuntimePluginIdsMock(...args),
}));
vi.mock("../agents/agent-scope.js", () => ({
resolveAgentWorkspaceDir: () => undefined,
resolveDefaultAgentId: () => "default",
}));
vi.mock("../agents/workspace.js", () => ({
resolveDefaultAgentWorkspaceDir: () => "/default-workspace",
}));
function setPluginLoadResult(overrides: Partial<ReturnType<typeof createPluginLoadResult>>) {
const result = createPluginLoadResult({
plugins: [],
...overrides,
});
loadOpenClawPluginsMock.mockReturnValue(result);
loadPluginMetadataRegistrySnapshotMock.mockReturnValue(result);
}
function setSinglePluginLoadResult(
plugin: ReturnType<typeof createPluginRecord>,
overrides: Omit<Partial<ReturnType<typeof createPluginLoadResult>>, "plugins"> = {},
) {
setPluginLoadResult({
plugins: [plugin],
...overrides,
});
}
function createInstalledPluginIndexSnapshot(
plugins: Array<Record<string, unknown>>,
): Record<string, unknown> {
return {
version: 1,
warning: "test",
hostContractVersion: "test",
compatRegistryVersion: "test",
migrationVersion: 1,
policyHash: "test",
generatedAtMs: 0,
installRecords: {},
plugins,
diagnostics: [],
};
}
function expectInspectReport(
pluginId: string,
options: Omit<Parameters<typeof buildPluginInspectReport>[0], "id"> = {},
): NonNullable<ReturnType<typeof buildPluginInspectReport>> {
const inspect = buildPluginInspectReport({ id: pluginId, ...options });
if (inspect === null) {
throw new Error(`expected inspect report for ${pluginId}`);
}
return inspect;
}
function mockInput(mock: { mock: { calls: unknown[][] } }, index = 0): Record<string, unknown> {
const input = mock.mock.calls[index]?.[0];
if (!input || typeof input !== "object") {
throw new Error(`expected mock input ${index}`);
}
return input as Record<string, unknown>;
}
function expectMockCalledWithFields(
mock: { mock: { calls: unknown[][] } },
fields: Record<string, unknown>,
) {
const input = mockInput(mock, mock.mock.calls.length - 1);
for (const [key, expected] of Object.entries(fields)) {
expect(input[key]).toEqual(expected);
}
}
function expectPluginLoaderCall(params: {
config?: unknown;
activationSourceConfig?: unknown;
autoEnabledReasons?: Record<string, string[]>;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
logger?: unknown;
loadModules?: boolean;
}) {
expectMockCalledWithFields(loadOpenClawPluginsMock, {
...(params.config !== undefined ? { config: params.config } : {}),
...(params.activationSourceConfig !== undefined
? { activationSourceConfig: params.activationSourceConfig }
: {}),
...(params.autoEnabledReasons !== undefined
? { autoEnabledReasons: params.autoEnabledReasons }
: {}),
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
...(params.env ? { env: params.env } : {}),
...(params.logger !== undefined ? { logger: params.logger } : {}),
...(params.loadModules !== undefined ? { loadModules: params.loadModules } : {}),
});
}
function expectMetadataSnapshotLoaderCall(params: {
config?: unknown;
activationSourceConfig?: unknown;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
logger?: unknown;
loadModules?: boolean;
}) {
expectMockCalledWithFields(loadPluginMetadataRegistrySnapshotMock, {
...(params.config !== undefined ? { config: params.config } : {}),
...(params.activationSourceConfig !== undefined
? { activationSourceConfig: params.activationSourceConfig }
: {}),
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
...(params.env ? { env: params.env } : {}),
...(params.logger !== undefined ? { logger: params.logger } : {}),
...(params.loadModules !== undefined ? { loadModules: params.loadModules } : {}),
});
}
function expectAutoEnabledStatusLoad(params: { rawConfig: unknown }) {
expectMockCalledWithFields(applyPluginAutoEnableMock, {
config: params.rawConfig,
env: process.env,
});
}
function createCompatChainFixture() {
const config = { plugins: { allow: ["telegram"] } };
const pluginIds = ["anthropic", "openai"];
const enabledConfig = {
plugins: {
allow: ["telegram"],
entries: {
anthropic: { enabled: true },
openai: { enabled: true },
},
},
};
return { config, pluginIds, enabledConfig };
}
function expectBundledCompatChainApplied(params: {
config: unknown;
pluginIds: string[];
enabledConfig: unknown;
loadModules: boolean;
}) {
expect(withBundledPluginEnablementCompatMock).toHaveBeenCalledWith({
config: params.config,
pluginIds: params.pluginIds,
});
if (params.loadModules) {
expectPluginLoaderCall({ config: params.enabledConfig, loadModules: true });
return;
}
expectMetadataSnapshotLoaderCall({ config: params.enabledConfig, loadModules: false });
}
function createAutoEnabledStatusConfig(
entries: Record<string, unknown>,
rawConfigOverrides?: Record<string, unknown>,
) {
const rawConfig = {
plugins: {},
...rawConfigOverrides,
};
const autoEnabledConfig = {
...rawConfig,
plugins: {
entries,
},
};
return { rawConfig, autoEnabledConfig };
}
function expectAutoEnabledDemoCompatibilityNoticesPreserveRawConfig() {
const { rawConfig, autoEnabledConfig } = createAutoEnabledStatusConfig(
{
demo: { enabled: true },
},
{ channels: { demo: { enabled: true } } },
);
const autoEnabledReasons = {
demo: ["demo configured"],
};
applyPluginAutoEnableMock.mockReturnValue({
config: autoEnabledConfig,
changes: [],
autoEnabledReasons,
});
setSinglePluginLoadResult(
createPluginRecord({
id: "demo",
name: "Demo",
description: "Auto-enabled plugin",
origin: "bundled",
hookCount: 1,
}),
{
typedHooks: [createTypedHook({ pluginId: "demo", hookName: "before_agent_start" })],
},
);
expect(buildPluginCompatibilityNotices({ config: rawConfig })).toEqual([
createCompatibilityNotice({ pluginId: "demo", code: "legacy-before-agent-start" }),
createCompatibilityNotice({ pluginId: "demo", code: "hook-only" }),
]);
expectAutoEnabledStatusLoad({
rawConfig,
});
expectPluginLoaderCall({
config: autoEnabledConfig,
activationSourceConfig: rawConfig,
autoEnabledReasons,
loadModules: true,
});
}
function expectNoCompatibilityWarnings() {
expect(buildPluginCompatibilityNotices()).toStrictEqual([]);
expect(buildPluginCompatibilityWarnings()).toStrictEqual([]);
}
function expectCompatibilityOutput(params: { notices?: unknown[]; warnings?: string[] }) {
if (params.notices) {
expect(buildPluginCompatibilityNotices()).toEqual(params.notices);
}
if (params.warnings) {
expect(buildPluginCompatibilityWarnings()).toEqual(params.warnings);
}
}
function expectCapabilityKinds(
inspect: NonNullable<ReturnType<typeof buildPluginInspectReport>>,
kinds: readonly string[],
) {
expect(inspect.capabilities.map((entry) => entry.kind)).toEqual(kinds);
}
function expectInspectShape(
inspect: NonNullable<ReturnType<typeof buildPluginInspectReport>>,
params: {
shape: string;
capabilityMode: string;
capabilityKinds: readonly string[];
},
) {
expect(inspect.shape).toBe(params.shape);
expect(inspect.capabilityMode).toBe(params.capabilityMode);
expectCapabilityKinds(inspect, params.capabilityKinds);
}
function expectInspectPolicy(
inspect: NonNullable<ReturnType<typeof buildPluginInspectReport>>,
expected: Record<string, unknown>,
) {
expect(inspect.policy).toEqual(expected);
}
function expectBundleInspectState(
inspect: NonNullable<ReturnType<typeof buildPluginInspectReport>>,
params: {
bundleCapabilities: readonly string[];
shape: string;
},
) {
expect(inspect.bundleCapabilities).toEqual(params.bundleCapabilities);
expect(inspect.mcpServers).toStrictEqual([]);
expect(inspect.shape).toBe(params.shape);
}
describe("plugin status reports", () => {
beforeAll(async () => {
({
buildAllPluginInspectReports,
buildPluginCompatibilityNotices,
buildPluginDiagnosticsReport,
buildPluginCompatibilityWarnings,
buildPluginInspectReport,
buildPluginRegistrySnapshotReport,
buildPluginSnapshotReport,
formatPluginCompatibilityNotice,
summarizePluginCompatibility,
} = await import("./status.js"));
});
beforeEach(() => {
loadConfigMock.mockReset();
loadOpenClawPluginsMock.mockReset();
loadPluginMetadataRegistrySnapshotMock.mockReset();
loadPluginManifestRegistryForPluginRegistryMock.mockReset();
loadPluginRegistrySnapshotWithMetadataMock.mockReset();
loadPluginManifestRegistryForInstalledIndexMock.mockReset();
loadPluginMetadataSnapshotMock.mockClear();
applyPluginAutoEnableMock.mockReset();
resolveBundledProviderCompatPluginIdsMock.mockReset();
withBundledPluginEnablementCompatMock.mockReset();
listImportedBundledPluginFacadeIdsMock.mockReset();
listImportedRuntimePluginIdsMock.mockReset();
loadConfigMock.mockReturnValue({});
loadPluginRegistrySnapshotWithMetadataMock.mockReturnValue({
snapshot: createInstalledPluginIndexSnapshot([]),
source: "derived",
diagnostics: [],
});
loadPluginManifestRegistryForPluginRegistryMock.mockReturnValue({
plugins: [],
diagnostics: [],
});
loadPluginManifestRegistryForInstalledIndexMock.mockReturnValue({
plugins: [],
diagnostics: [],
});
applyPluginAutoEnableMock.mockImplementation((params: { config: unknown }) => ({
config: params.config,
changes: [],
autoEnabledReasons: {},
}));
resolveBundledProviderCompatPluginIdsMock.mockReturnValue([]);
withBundledPluginEnablementCompatMock.mockImplementation(
(params: { config: unknown }) => params.config,
);
listImportedBundledPluginFacadeIdsMock.mockReturnValue([]);
listImportedRuntimePluginIdsMock.mockReturnValue([]);
setPluginLoadResult({ plugins: [] });
});
it("forwards an explicit env to plugin loading", () => {
const env = { HOME: "/tmp/openclaw-home" } as NodeJS.ProcessEnv;
buildPluginSnapshotReport({
config: {},
workspaceDir: "/workspace",
env,
});
expectMetadataSnapshotLoaderCall({
config: {},
workspaceDir: "/workspace",
env,
loadModules: false,
});
});
it("forwards an explicit logger to plugin loading", () => {
const logger = {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
buildPluginSnapshotReport({
config: {},
logger,
workspaceDir: "/workspace",
});
expectMetadataSnapshotLoaderCall({
config: {},
logger,
workspaceDir: "/workspace",
loadModules: false,
});
});
it("carries installed-index compatibility metadata into registry snapshot reports", () => {
loadPluginRegistrySnapshotWithMetadataMock.mockReturnValue({
snapshot: createInstalledPluginIndexSnapshot([
{
pluginId: "provider-env-plugin",
manifestPath: "/tmp/provider-env-plugin/openclaw.plugin.json",
manifestHash: "manifest-hash",
rootDir: "/tmp/provider-env-plugin",
origin: "workspace",
enabled: true,
startup: {
sidecar: false,
memory: false,
deferConfiguredChannelFullLoadUntilAfterListen: false,
agentHarnesses: [],
},
compat: ["provider-auth-env-vars"],
},
]),
source: "derived",
diagnostics: [],
});
loadPluginManifestRegistryForInstalledIndexMock.mockReturnValue({
plugins: [{ id: "provider-env-plugin", name: "Provider Env Plugin" }],
diagnostics: [],
});
const report = buildPluginRegistrySnapshotReport({ config: {} });
expect(report.plugins[0]?.id).toBe("provider-env-plugin");
expect(report.plugins[0]?.compat).toEqual(["provider-auth-env-vars"]);
});
it("uses a metadata snapshot load for snapshot reports", () => {
buildPluginSnapshotReport({ config: {}, workspaceDir: "/workspace" });
expect(mockInput(loadPluginMetadataRegistrySnapshotMock).loadModules).toBe(false);
expect(loadOpenClawPluginsMock).not.toHaveBeenCalled();
});
it("loads plugin status from the auto-enabled config snapshot", () => {
const { rawConfig, autoEnabledConfig } = createAutoEnabledStatusConfig(
{
demo: { enabled: true },
},
{ channels: { demo: { enabled: true } } },
);
applyPluginAutoEnableMock.mockReturnValue({
config: autoEnabledConfig,
changes: [],
autoEnabledReasons: {
demo: ["demo configured"],
},
});
buildPluginSnapshotReport({ config: rawConfig });
expectAutoEnabledStatusLoad({
rawConfig,
});
expectMetadataSnapshotLoaderCall({
config: autoEnabledConfig,
activationSourceConfig: rawConfig,
loadModules: false,
});
});
it("uses the auto-enabled config snapshot for inspect policy summaries", () => {
const { rawConfig, autoEnabledConfig } = createAutoEnabledStatusConfig(
{
demo: {
enabled: true,
subagent: {
allowModelOverride: true,
allowedModels: ["openai/gpt-5.5"],
hasAllowedModelsConfig: true,
},
},
},
{ channels: { demo: { enabled: true } } },
);
applyPluginAutoEnableMock.mockReturnValue({
config: autoEnabledConfig,
changes: [],
autoEnabledReasons: {
demo: ["demo configured"],
},
});
setSinglePluginLoadResult(
createPluginRecord({
id: "demo",
name: "Demo",
description: "Auto-enabled plugin",
origin: "bundled",
providerIds: ["demo"],
}),
);
const inspect = expectInspectReport("demo", { config: rawConfig });
expectInspectPolicy(inspect, {
allowPromptInjection: undefined,
allowConversationAccess: undefined,
hookTimeoutMs: undefined,
hookTimeouts: undefined,
allowModelOverride: true,
allowedModels: ["openai/gpt-5.5"],
hasAllowedModelsConfig: true,
});
expectPluginLoaderCall({ loadModules: true });
});
it("preserves raw config activation context when compatibility notices build their own report", () => {
expectAutoEnabledDemoCompatibilityNoticesPreserveRawConfig();
});
it("applies the full bundled provider compat chain before loading plugins", () => {
const { config, pluginIds, enabledConfig } = createCompatChainFixture();
loadConfigMock.mockReturnValue(config);
resolveBundledProviderCompatPluginIdsMock.mockReturnValue(pluginIds);
withBundledPluginEnablementCompatMock.mockReturnValue(enabledConfig);
buildPluginSnapshotReport({ config });
expectBundledCompatChainApplied({
config,
pluginIds,
enabledConfig,
loadModules: false,
});
});
it("preserves raw config activation context for compatibility-derived reports", () => {
expectAutoEnabledDemoCompatibilityNoticesPreserveRawConfig();
});
it("normalizes bundled plugin versions to the core base release", () => {
setSinglePluginLoadResult(
createPluginRecord({
id: "whatsapp",
name: "WhatsApp",
description: "Bundled channel plugin",
version: "2026.3.22",
origin: "bundled",
channelIds: ["whatsapp"],
}),
);
const report = buildPluginDiagnosticsReport({
config: {},
env: {
OPENCLAW_VERSION: "2026.3.23-1",
} as NodeJS.ProcessEnv,
});
expect(report.plugins[0]?.version).toBe("2026.3.23");
});
it("marks plugins as imported when runtime or facade state has loaded them", () => {
setPluginLoadResult({
plugins: [
createPluginRecord({ id: "runtime-loaded" }),
createPluginRecord({ id: "facade-loaded" }),
createPluginRecord({ id: "bundle-loaded", format: "bundle" }),
createPluginRecord({ id: "cold-plugin" }),
],
});
listImportedRuntimePluginIdsMock.mockReturnValue(["runtime-loaded", "bundle-loaded"]);
listImportedBundledPluginFacadeIdsMock.mockReturnValue(["facade-loaded"]);
const report = buildPluginSnapshotReport({ config: {} });
const pluginsById = new Map(report.plugins.map((plugin) => [plugin.id, plugin]));
expect(pluginsById.get("runtime-loaded")?.imported).toBe(true);
expect(pluginsById.get("facade-loaded")?.imported).toBe(true);
expect(pluginsById.get("bundle-loaded")?.imported).toBe(false);
expect(pluginsById.get("cold-plugin")?.imported).toBe(false);
});
it("marks snapshot-loaded plugin modules as imported during full report loads", () => {
setPluginLoadResult({
plugins: [
createPluginRecord({ id: "runtime-loaded" }),
createPluginRecord({ id: "bundle-loaded", format: "bundle" }),
],
});
const report = buildPluginDiagnosticsReport({ config: {} });
const pluginsById = new Map(report.plugins.map((plugin) => [plugin.id, plugin]));
expect(pluginsById.get("runtime-loaded")?.imported).toBe(true);
expect(pluginsById.get("bundle-loaded")?.imported).toBe(false);
});
it("marks errored plugin modules as imported when full diagnostics already evaluated them", () => {
setPluginLoadResult({
plugins: [createPluginRecord({ id: "broken-plugin", status: "error" })],
});
listImportedRuntimePluginIdsMock.mockReturnValue(["broken-plugin"]);
const report = buildPluginDiagnosticsReport({ config: {} });
const plugin = report.plugins.find((entry) => entry.id === "broken-plugin");
expect(plugin?.status).toBe("error");
expect(plugin?.imported).toBe(true);
});
it("builds an inspect report with capability shape and policy", () => {
loadConfigMock.mockReturnValue({
plugins: {
entries: {
google: {
hooks: { allowPromptInjection: false, allowConversationAccess: true },
subagent: {
allowModelOverride: true,
allowedModels: ["openai/gpt-5.5"],
},
},
},
},
});
setPluginLoadResult({
plugins: [
createPluginRecord({
id: "google",
name: "Google",
description: "Google provider plugin",
origin: "bundled",
providerIds: ["google"],
mediaUnderstandingProviderIds: ["google"],
imageGenerationProviderIds: ["google"],
webSearchProviderIds: ["google"],
}),
],
diagnostics: [{ level: "warn", pluginId: "google", message: "watch this surface" }],
typedHooks: [createTypedHook({ pluginId: "google", hookName: "before_agent_start" })],
});
const inspect = expectInspectReport("google");
expectInspectShape(inspect, {
shape: "hybrid-capability",
capabilityMode: "hybrid",
capabilityKinds: ["text-inference", "media-understanding", "image-generation", "web-search"],
});
expect(inspect.usesLegacyBeforeAgentStart).toBe(true);
expect(inspect.compatibility).toEqual([
createCompatibilityNotice({ pluginId: "google", code: "legacy-before-agent-start" }),
]);
expectInspectPolicy(inspect, {
allowPromptInjection: false,
allowConversationAccess: true,
hookTimeoutMs: undefined,
hookTimeouts: undefined,
allowModelOverride: true,
allowedModels: ["openai/gpt-5.5"],
hasAllowedModelsConfig: true,
});
expect(inspect.diagnostics).toEqual([
{ level: "warn", pluginId: "google", message: "watch this surface" },
]);
});
it("builds inspect reports for every loaded plugin", () => {
setPluginLoadResult({
plugins: [
createPluginRecord({
id: "lca",
name: "LCA",
description: "Legacy hook plugin",
hookCount: 1,
}),
createPluginRecord({
id: "microsoft",
name: "Microsoft",
description: "Hybrid capability plugin",
origin: "bundled",
providerIds: ["microsoft"],
webSearchProviderIds: ["microsoft"],
}),
],
hooks: [createCustomHook({ pluginId: "lca", events: ["message"] })],
typedHooks: [createTypedHook({ pluginId: "lca", hookName: "before_agent_start" })],
});
const inspect = buildAllPluginInspectReports();
expect(inspect.map((entry) => entry.plugin.id)).toEqual(["lca", "microsoft"]);
expect(inspect.map((entry) => entry.shape)).toEqual(["hook-only", "hybrid-capability"]);
expect(inspect[0]?.usesLegacyBeforeAgentStart).toBe(true);
expectCapabilityKinds(inspect[1], ["text-inference", "web-search"]);
});
it("treats a CLI-command-only plugin as a plain capability", () => {
setSinglePluginLoadResult(
createPluginRecord({
id: "anthropic",
name: "Anthropic",
cliBackendIds: ["claude-cli"],
}),
);
const inspect = expectInspectReport("anthropic");
expectInspectShape(inspect, {
shape: "plain-capability",
capabilityMode: "plain",
capabilityKinds: ["cli-backend"],
});
expect(inspect.capabilities).toEqual([{ kind: "cli-backend", ids: ["claude-cli"] }]);
});
it("treats a context-engine plugin as a plain capability", () => {
setPluginLoadResult({
plugins: [
createPluginRecord({
id: "moon",
name: "Moon",
kind: "context-engine",
contextEngineIds: ["moon-engine"],
hookCount: 1,
}),
],
hooks: [createCustomHook({ pluginId: "moon", events: ["message"] })],
});
const inspect = expectInspectReport("moon");
expectInspectShape(inspect, {
shape: "plain-capability",
capabilityMode: "plain",
capabilityKinds: ["context-engine"],
});
expect(inspect.capabilities).toEqual([{ kind: "context-engine", ids: ["moon-engine"] }]);
expect(inspect.compatibility).toStrictEqual([]);
expectNoCompatibilityWarnings();
});
it("builds compatibility warnings for legacy compatibility paths", () => {
setPluginLoadResult({
plugins: [
createPluginRecord({
id: "lca",
name: "LCA",
description: "Legacy hook plugin",
hookCount: 1,
}),
],
typedHooks: [createTypedHook({ pluginId: "lca", hookName: "before_agent_start" })],
});
expectCompatibilityOutput({
warnings: [`lca ${LEGACY_BEFORE_AGENT_START_MESSAGE}`, `lca ${HOOK_ONLY_MESSAGE}`],
});
});
it("warns external plugins off deprecated memory embedding provider registration", () => {
setSinglePluginLoadResult(
createPluginRecord({
id: "legacy-memory-provider",
name: "Legacy Memory Provider",
memoryEmbeddingProviderIds: ["legacy-memory-provider"],
contracts: { memoryEmbeddingProviders: ["legacy-memory-provider"] },
}),
);
expectCompatibilityOutput({
notices: [
createCompatibilityNotice({
pluginId: "legacy-memory-provider",
code: "deprecated-memory-embedding-provider-api",
}),
],
warnings: [`legacy-memory-provider ${DEPRECATED_MEMORY_EMBEDDING_PROVIDER_API_MESSAGE}`],
});
});
it("warns when external plugins register memory embedding providers at runtime only", () => {
const runtimeProviderRegistration: PluginMemoryEmbeddingProviderRegistration = {
pluginId: "runtime-only-legacy-memory-provider",
pluginName: "Runtime Only Legacy Memory Provider",
provider: {
id: "runtime-only-legacy-memory-provider",
create: async () => ({ provider: null }),
},
source: "/tmp/runtime-only-legacy-memory-provider/index.ts",
};
setPluginLoadResult({
plugins: [
createPluginRecord({
id: "runtime-only-legacy-memory-provider",
name: "Runtime Only Legacy Memory Provider",
}),
],
memoryEmbeddingProviders: [runtimeProviderRegistration],
});
expectCompatibilityOutput({
notices: [
createCompatibilityNotice({
pluginId: "runtime-only-legacy-memory-provider",
code: "deprecated-memory-embedding-provider-api",
}),
],
warnings: [
`runtime-only-legacy-memory-provider ${DEPRECATED_MEMORY_EMBEDDING_PROVIDER_API_MESSAGE}`,
],
});
});
it("does not surface bundled memory embedding migration debt as user warnings", () => {
setSinglePluginLoadResult(
createPluginRecord({
id: "bundled-memory-provider",
name: "Bundled Memory Provider",
origin: "bundled",
memoryEmbeddingProviderIds: ["bundled-memory-provider"],
contracts: { memoryEmbeddingProviders: ["bundled-memory-provider"] },
}),
);
expectNoCompatibilityWarnings();
});
it("builds structured compatibility notices with deterministic ordering", () => {
setPluginLoadResult({
plugins: [
createPluginRecord({
id: "hook-only",
name: "Hook Only",
hookCount: 1,
}),
createPluginRecord({
id: "legacy-only",
name: "Legacy Only",
providerIds: ["legacy-only"],
hookCount: 1,
}),
],
hooks: [createCustomHook({ pluginId: "hook-only", events: ["message"] })],
typedHooks: [createTypedHook({ pluginId: "legacy-only", hookName: "before_agent_start" })],
});
expectCompatibilityOutput({
notices: [
createCompatibilityNotice({ pluginId: "hook-only", code: "hook-only" }),
createCompatibilityNotice({ pluginId: "legacy-only", code: "legacy-before-agent-start" }),
],
});
});
it("does not warn for explicit startup-lazy metadata", () => {
setSinglePluginLoadResult(
createPluginRecord({
id: "modern-startup-lazy",
name: "Modern Startup Lazy",
compat: [],
}),
);
expectNoCompatibilityWarnings();
});
it("returns no compatibility warnings for modern capability plugins", () => {
setSinglePluginLoadResult(
createPluginRecord({
id: "modern",
name: "Modern",
providerIds: ["modern"],
}),
);
expectNoCompatibilityWarnings();
});
it.each([
{
name: "populates bundleCapabilities from plugin record",
plugin: createPluginRecord({
id: "claude-bundle",
name: "Claude Bundle",
description: "A bundle plugin with skills and commands",
source: "/tmp/claude-bundle/.claude-plugin/plugin.json",
format: "bundle",
bundleFormat: "claude",
bundleCapabilities: ["skills", "commands", "agents", "settings"],
rootDir: "/tmp/claude-bundle",
}),
expectedId: "claude-bundle",
expectedBundleCapabilities: ["skills", "commands", "agents", "settings"],
expectedShape: "non-capability",
},
{
name: "returns empty bundleCapabilities and mcpServers for non-bundle plugins",
plugin: createPluginRecord({
id: "plain-plugin",
name: "Plain Plugin",
description: "A regular plugin",
providerIds: ["plain"],
}),
expectedId: "plain-plugin",
expectedBundleCapabilities: [],
expectedShape: "plain-capability",
},
])("$name", ({ plugin, expectedId, expectedBundleCapabilities, expectedShape }) => {
setSinglePluginLoadResult(plugin);
const inspect = expectInspectReport(expectedId);
expectBundleInspectState(inspect, {
bundleCapabilities: expectedBundleCapabilities,
shape: expectedShape,
});
});
it("formats and summarizes compatibility notices", () => {
const notice = createCompatibilityNotice({
pluginId: "legacy-plugin",
code: "legacy-before-agent-start",
});
expect(formatPluginCompatibilityNotice(notice)).toBe(
`legacy-plugin ${LEGACY_BEFORE_AGENT_START_MESSAGE}`,
);
expect(
summarizePluginCompatibility([
notice,
createCompatibilityNotice({ pluginId: "legacy-plugin", code: "hook-only" }),
]),
).toEqual({
noticeCount: 2,
pluginCount: 1,
});
});
});