mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-04 19:57:39 +00:00
* 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
623 lines
20 KiB
TypeScript
623 lines
20 KiB
TypeScript
import { beforeEach, describe, expect, it } from "vitest";
|
|
import { createPluginRecord } from "../plugins/status.test-helpers.js";
|
|
import {
|
|
buildPluginDiagnosticsReport,
|
|
buildPluginInspectReport,
|
|
buildPluginRegistrySnapshotReport,
|
|
buildPluginSnapshotReport,
|
|
inspectPluginRegistry,
|
|
loadConfig,
|
|
readConfigFileSnapshot,
|
|
resetPluginsCliTestState,
|
|
refreshPluginRegistry,
|
|
runPluginsCommand,
|
|
runtimeErrors,
|
|
runtimeLogs,
|
|
setInstalledPluginIndexInstallRecords,
|
|
} from "./plugins-cli-test-helpers.js";
|
|
|
|
describe("plugins cli list", () => {
|
|
beforeEach(() => {
|
|
resetPluginsCliTestState();
|
|
});
|
|
|
|
it("includes imported state in JSON output", async () => {
|
|
buildPluginRegistrySnapshotReport.mockReturnValue({
|
|
workspaceDir: "/workspace",
|
|
registrySource: "persisted",
|
|
registryDiagnostics: [],
|
|
plugins: [
|
|
createPluginRecord({
|
|
id: "demo",
|
|
imported: true,
|
|
activated: true,
|
|
explicitlyEnabled: true,
|
|
}),
|
|
],
|
|
diagnostics: [],
|
|
});
|
|
|
|
await runPluginsCommand(["plugins", "list", "--json"]);
|
|
|
|
expect(buildPluginRegistrySnapshotReport).toHaveBeenCalledTimes(1);
|
|
const [reportOptions] = buildPluginRegistrySnapshotReport.mock.calls[0] as [
|
|
{
|
|
config?: unknown;
|
|
logger?: { info?: unknown; warn?: unknown; error?: unknown };
|
|
},
|
|
];
|
|
expect(reportOptions?.config).toEqual({});
|
|
expect(reportOptions?.logger?.info).toBeTypeOf("function");
|
|
expect(reportOptions?.logger?.warn).toBeTypeOf("function");
|
|
expect(reportOptions?.logger?.error).toBeTypeOf("function");
|
|
|
|
const output = JSON.parse(runtimeLogs[0] ?? "null") as {
|
|
workspaceDir?: string;
|
|
registry?: { source?: string; diagnostics?: unknown[] };
|
|
plugins?: Array<{
|
|
id?: string;
|
|
imported?: boolean;
|
|
activated?: boolean;
|
|
explicitlyEnabled?: boolean;
|
|
}>;
|
|
diagnostics?: unknown[];
|
|
};
|
|
expect(output.workspaceDir).toBe("/workspace");
|
|
expect(output.registry?.source).toBe("persisted");
|
|
expect(output.registry?.diagnostics).toEqual([]);
|
|
expect(output.plugins).toHaveLength(1);
|
|
expect(output.plugins?.[0]?.id).toBe("demo");
|
|
expect(output.plugins?.[0]?.imported).toBe(true);
|
|
expect(output.plugins?.[0]?.activated).toBe(true);
|
|
expect(output.plugins?.[0]?.explicitlyEnabled).toBe(true);
|
|
expect(output.diagnostics).toEqual([]);
|
|
});
|
|
|
|
it("keeps doctor on a module-loading snapshot", async () => {
|
|
buildPluginDiagnosticsReport.mockReturnValue({
|
|
plugins: [],
|
|
diagnostics: [],
|
|
});
|
|
|
|
await runPluginsCommand(["plugins", "doctor"]);
|
|
|
|
expect(buildPluginDiagnosticsReport).toHaveBeenCalledWith({ config: {}, effectiveOnly: true });
|
|
expect(runtimeLogs).toContain("No plugin issues detected.");
|
|
});
|
|
|
|
it("reports stale plugin config in doctor output without claiming full plugin health", async () => {
|
|
const sourceConfig = {
|
|
plugins: {
|
|
allow: ["lossless-claw"],
|
|
entries: {
|
|
"lossless-claw": { enabled: true },
|
|
},
|
|
slots: {
|
|
contextEngine: "lossless-claw",
|
|
},
|
|
},
|
|
};
|
|
loadConfig.mockReturnValue({});
|
|
readConfigFileSnapshot.mockResolvedValueOnce({
|
|
path: "/tmp/openclaw-config.json5",
|
|
exists: true,
|
|
raw: "{}",
|
|
parsed: sourceConfig,
|
|
resolved: sourceConfig,
|
|
sourceConfig,
|
|
runtimeConfig: {},
|
|
config: {},
|
|
valid: true,
|
|
hash: "mock",
|
|
issues: [],
|
|
warnings: [],
|
|
legacyIssues: [],
|
|
});
|
|
buildPluginDiagnosticsReport.mockReturnValue({
|
|
plugins: [],
|
|
diagnostics: [],
|
|
});
|
|
|
|
await runPluginsCommand(["plugins", "doctor"]);
|
|
|
|
const output = runtimeLogs.join("\n");
|
|
expect(output).toContain("Plugin configuration:");
|
|
expect(output).toContain('plugins.allow: stale plugin reference "lossless-claw" was found.');
|
|
expect(output).toContain(
|
|
'plugins.entries.lossless-claw: stale plugin reference "lossless-claw" was found.',
|
|
);
|
|
expect(output).toContain(
|
|
'plugins.slots.contextEngine: slot references missing plugin "lossless-claw".',
|
|
);
|
|
expect(output).toContain(
|
|
'Run "openclaw doctor --fix" to remove stale plugin ids and dangling channel references.',
|
|
);
|
|
expect(output).toContain(
|
|
"No plugin install-tree issues detected; configuration warnings remain.",
|
|
);
|
|
expect(output).not.toContain("No plugin issues detected.");
|
|
});
|
|
|
|
it("reports missing configured Codex runtime plugin in doctor output", async () => {
|
|
const sourceConfig = {
|
|
agents: {
|
|
defaults: {
|
|
models: {
|
|
"openai/gpt-5.5": {
|
|
agentRuntime: { id: "codex" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
loadConfig.mockReturnValue(sourceConfig);
|
|
readConfigFileSnapshot.mockResolvedValueOnce({
|
|
path: "/tmp/openclaw-config.json5",
|
|
exists: true,
|
|
raw: "{}",
|
|
parsed: sourceConfig,
|
|
resolved: sourceConfig,
|
|
sourceConfig,
|
|
runtimeConfig: sourceConfig,
|
|
config: sourceConfig,
|
|
valid: true,
|
|
hash: "mock",
|
|
issues: [],
|
|
warnings: [],
|
|
legacyIssues: [],
|
|
});
|
|
buildPluginDiagnosticsReport.mockReturnValue({
|
|
plugins: [],
|
|
diagnostics: [],
|
|
});
|
|
|
|
await runPluginsCommand(["plugins", "doctor"]);
|
|
|
|
const output = runtimeLogs.join("\n");
|
|
expect(output).toContain("Plugin configuration:");
|
|
expect(output).toContain('Configured runtime "codex" requires the Codex plugin');
|
|
expect(output).toContain("openclaw doctor --fix");
|
|
expect(output).toContain("openclaw plugins install @openclaw/codex");
|
|
expect(output).toContain(
|
|
"No plugin install-tree issues detected; configuration warnings remain.",
|
|
);
|
|
expect(output).not.toContain("No plugin issues detected.");
|
|
});
|
|
|
|
it("reports missing configured ACPX runtime plugin in doctor output", async () => {
|
|
const sourceConfig = {
|
|
acp: {
|
|
backend: "acpx",
|
|
},
|
|
};
|
|
loadConfig.mockReturnValue(sourceConfig);
|
|
buildPluginDiagnosticsReport.mockReturnValue({
|
|
plugins: [],
|
|
diagnostics: [],
|
|
});
|
|
|
|
await runPluginsCommand(["plugins", "doctor"]);
|
|
|
|
const output = runtimeLogs.join("\n");
|
|
expect(output).toContain("Plugin configuration:");
|
|
expect(output).toContain('Configured runtime "acpx" requires the ACPX Runtime plugin');
|
|
expect(output).toContain("openclaw doctor --fix");
|
|
expect(output).toContain("openclaw plugins install @openclaw/acpx");
|
|
expect(output).not.toContain("No plugin issues detected.");
|
|
});
|
|
|
|
it("reports blocked configured ACPX runtime with ACP-specific guidance", async () => {
|
|
const sourceConfig = {
|
|
acp: {
|
|
backend: "acpx",
|
|
},
|
|
plugins: {
|
|
entries: {
|
|
acpx: { enabled: false },
|
|
},
|
|
},
|
|
};
|
|
loadConfig.mockReturnValue(sourceConfig);
|
|
buildPluginDiagnosticsReport.mockReturnValue({
|
|
plugins: [],
|
|
diagnostics: [],
|
|
});
|
|
|
|
await runPluginsCommand(["plugins", "doctor"]);
|
|
|
|
const output = runtimeLogs.join("\n");
|
|
expect(output).toContain('Configured runtime "acpx" requires the ACPX Runtime plugin');
|
|
expect(output).toContain("Set plugins.entries.acpx.enabled=true");
|
|
expect(output).toContain("disable ACP/acpx in acp config");
|
|
expect(output).not.toContain('runtime policy to "openclaw"');
|
|
expect(output).not.toContain("openclaw plugins install @openclaw/acpx");
|
|
expect(output).not.toContain("No plugin issues detected.");
|
|
});
|
|
|
|
it("reports disabled configured ACPX runtime with ACP-specific guidance", async () => {
|
|
const sourceConfig = {
|
|
acp: {
|
|
backend: "acpx",
|
|
},
|
|
};
|
|
loadConfig.mockReturnValue(sourceConfig);
|
|
buildPluginDiagnosticsReport.mockReturnValue({
|
|
plugins: [createPluginRecord({ id: "acpx", enabled: false, status: "disabled" })],
|
|
diagnostics: [],
|
|
});
|
|
|
|
await runPluginsCommand(["plugins", "doctor"]);
|
|
|
|
const output = runtimeLogs.join("\n");
|
|
expect(output).toContain('Configured runtime "acpx" requires the ACPX Runtime plugin');
|
|
expect(output).toContain('Enable the "acpx" plugin');
|
|
expect(output).toContain("disable ACP/acpx in acp config");
|
|
expect(output).not.toContain('runtime policy to "openclaw"');
|
|
expect(output).not.toContain("openclaw plugins install @openclaw/acpx");
|
|
expect(output).not.toContain("No plugin issues detected.");
|
|
});
|
|
|
|
it("does not report implicit OpenAI Codex preference as configured runtime", async () => {
|
|
const sourceConfig = {
|
|
agents: {
|
|
defaults: {
|
|
model: "openai/gpt-5.5",
|
|
},
|
|
},
|
|
};
|
|
loadConfig.mockReturnValue(sourceConfig);
|
|
buildPluginDiagnosticsReport.mockReturnValue({
|
|
plugins: [],
|
|
diagnostics: [],
|
|
});
|
|
|
|
await runPluginsCommand(["plugins", "doctor"]);
|
|
|
|
const output = runtimeLogs.join("\n");
|
|
expect(output).not.toContain('Configured runtime "codex"');
|
|
expect(output).toContain("No plugin issues detected.");
|
|
});
|
|
|
|
it("does not report configured Codex runtime when the plugin is enabled", async () => {
|
|
const sourceConfig = {
|
|
agents: {
|
|
defaults: {
|
|
models: {
|
|
"openai/gpt-5.5": {
|
|
agentRuntime: { id: "codex" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
loadConfig.mockReturnValue(sourceConfig);
|
|
buildPluginDiagnosticsReport.mockReturnValue({
|
|
plugins: [createPluginRecord({ id: "codex" })],
|
|
diagnostics: [],
|
|
});
|
|
|
|
await runPluginsCommand(["plugins", "doctor"]);
|
|
|
|
expect(runtimeLogs).toContain("No plugin issues detected.");
|
|
});
|
|
|
|
it("reports configured Codex runtime when the plugin record is disabled", async () => {
|
|
const sourceConfig = {
|
|
agents: {
|
|
defaults: {
|
|
models: {
|
|
"openai/gpt-5.5": {
|
|
agentRuntime: { id: "codex" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
loadConfig.mockReturnValue(sourceConfig);
|
|
buildPluginDiagnosticsReport.mockReturnValue({
|
|
plugins: [createPluginRecord({ id: "codex", enabled: false, status: "disabled" })],
|
|
diagnostics: [],
|
|
});
|
|
|
|
await runPluginsCommand(["plugins", "doctor"]);
|
|
|
|
const output = runtimeLogs.join("\n");
|
|
expect(output).toContain('Configured runtime "codex" requires the Codex plugin');
|
|
expect(output).toContain('but "codex" is disabled');
|
|
expect(output).toContain('Enable the "codex" plugin');
|
|
expect(output).not.toContain("openclaw plugins install @openclaw/codex");
|
|
expect(output).not.toContain("No plugin issues detected.");
|
|
});
|
|
|
|
it("reports blocked configured Codex runtime without install advice", async () => {
|
|
const sourceConfig = {
|
|
plugins: {
|
|
deny: ["codex"],
|
|
},
|
|
agents: {
|
|
defaults: {
|
|
models: {
|
|
"openai/gpt-5.5": {
|
|
agentRuntime: { id: "codex" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
loadConfig.mockReturnValue(sourceConfig);
|
|
buildPluginDiagnosticsReport.mockReturnValue({
|
|
plugins: [],
|
|
diagnostics: [],
|
|
});
|
|
|
|
await runPluginsCommand(["plugins", "doctor"]);
|
|
|
|
const output = runtimeLogs.join("\n");
|
|
expect(output).toContain('Configured runtime "codex" requires the Codex plugin');
|
|
expect(output).toContain('but "codex" is blocked by plugin configuration');
|
|
expect(output).toContain('Remove "codex" from plugins.deny');
|
|
expect(output).not.toContain('Run "openclaw doctor --fix" to install');
|
|
expect(output).not.toContain("openclaw plugins install @openclaw/codex");
|
|
expect(output).not.toContain("No plugin issues detected.");
|
|
});
|
|
|
|
it("reports disabled configured Codex runtime entry without install advice", async () => {
|
|
const sourceConfig = {
|
|
plugins: {
|
|
entries: {
|
|
codex: { enabled: false },
|
|
},
|
|
},
|
|
agents: {
|
|
defaults: {
|
|
models: {
|
|
"openai/gpt-5.5": {
|
|
agentRuntime: { id: "codex" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
loadConfig.mockReturnValue(sourceConfig);
|
|
buildPluginDiagnosticsReport.mockReturnValue({
|
|
plugins: [],
|
|
diagnostics: [],
|
|
});
|
|
|
|
await runPluginsCommand(["plugins", "doctor"]);
|
|
|
|
const output = runtimeLogs.join("\n");
|
|
expect(output).toContain('Configured runtime "codex" requires the Codex plugin');
|
|
expect(output).toContain('but "codex" is blocked by plugin configuration');
|
|
expect(output).toContain("Set plugins.entries.codex.enabled=true");
|
|
expect(output).not.toContain('Run "openclaw doctor --fix" to install');
|
|
expect(output).not.toContain("openclaw plugins install @openclaw/codex");
|
|
expect(output).not.toContain("No plugin issues detected.");
|
|
});
|
|
|
|
it("reports config-selected plugin source shadowing in doctor output", async () => {
|
|
buildPluginDiagnosticsReport.mockReturnValue({
|
|
plugins: [
|
|
createPluginRecord({
|
|
id: "discord",
|
|
origin: "config",
|
|
source: "/tmp/openclaw-upstream/extensions/discord/index.ts",
|
|
status: "error",
|
|
error: "Cannot find module 'chalk'",
|
|
}),
|
|
],
|
|
diagnostics: [
|
|
{
|
|
level: "warn",
|
|
pluginId: "discord",
|
|
source: "/tmp/openclaw/npm/node_modules/@openclaw/discord/index.ts",
|
|
message:
|
|
"duplicate plugin id resolved by explicit config-selected plugin; global plugin will be overridden by config plugin (/tmp/openclaw-upstream/extensions/discord/index.ts)",
|
|
},
|
|
],
|
|
});
|
|
|
|
await runPluginsCommand(["plugins", "doctor"]);
|
|
|
|
const output = runtimeLogs.join("\n");
|
|
expect(output).toContain("Plugin source shadowing:");
|
|
expect(output).toContain(
|
|
"discord: duplicate plugin id resolved by explicit config-selected plugin",
|
|
);
|
|
expect(output).toContain("active: /tmp/openclaw-upstream/extensions/discord/index.ts");
|
|
expect(output).toContain("shadowed: /tmp/openclaw/npm/node_modules/@openclaw/discord/index.ts");
|
|
expect(output).toContain("openclaw plugins registry --refresh");
|
|
});
|
|
|
|
it("does not report healthy config-selected plugin source shadowing as doctor issue", async () => {
|
|
buildPluginDiagnosticsReport.mockReturnValue({
|
|
plugins: [
|
|
createPluginRecord({
|
|
id: "discord",
|
|
origin: "config",
|
|
source: "/tmp/openclaw-upstream/extensions/discord/index.ts",
|
|
status: "loaded",
|
|
}),
|
|
],
|
|
diagnostics: [
|
|
{
|
|
level: "warn",
|
|
pluginId: "discord",
|
|
source: "/tmp/openclaw/npm/node_modules/@openclaw/discord/index.ts",
|
|
message:
|
|
"duplicate plugin id resolved by explicit config-selected plugin; global plugin will be overridden by config plugin (/tmp/openclaw-upstream/extensions/discord/index.ts)",
|
|
},
|
|
],
|
|
});
|
|
|
|
await runPluginsCommand(["plugins", "doctor"]);
|
|
|
|
expect(runtimeLogs).toContain("No plugin issues detected.");
|
|
});
|
|
|
|
it("reports persisted plugin registry state without refreshing", async () => {
|
|
inspectPluginRegistry.mockResolvedValue({
|
|
state: "stale",
|
|
refreshReasons: ["stale-manifest"],
|
|
persisted: {
|
|
plugins: [{ pluginId: "demo", enabled: true }],
|
|
},
|
|
current: {
|
|
plugins: [
|
|
{ pluginId: "demo", enabled: true },
|
|
{ pluginId: "next", enabled: false },
|
|
],
|
|
},
|
|
});
|
|
|
|
await runPluginsCommand(["plugins", "registry"]);
|
|
|
|
expect(inspectPluginRegistry).toHaveBeenCalledWith({ config: {} });
|
|
expect(refreshPluginRegistry).not.toHaveBeenCalled();
|
|
expect(runtimeLogs.join("\n")).toContain("State:");
|
|
expect(runtimeLogs.join("\n")).toContain("stale");
|
|
expect(runtimeLogs.join("\n")).toContain("Refresh reasons:");
|
|
expect(runtimeLogs.join("\n")).toContain("openclaw plugins registry --refresh");
|
|
});
|
|
|
|
it("refreshes the persisted plugin registry on request", async () => {
|
|
refreshPluginRegistry.mockResolvedValue({
|
|
plugins: [
|
|
{ pluginId: "demo", enabled: true },
|
|
{ pluginId: "off", enabled: false },
|
|
],
|
|
});
|
|
|
|
await runPluginsCommand(["plugins", "registry", "--refresh"]);
|
|
|
|
expect(refreshPluginRegistry).toHaveBeenCalledWith({
|
|
config: {},
|
|
reason: "manual",
|
|
});
|
|
expect(inspectPluginRegistry).not.toHaveBeenCalled();
|
|
expect(runtimeLogs.join("\n")).toContain("Plugin registry refreshed: 1/2 enabled");
|
|
});
|
|
|
|
it("keeps inspect on the static snapshot by default", async () => {
|
|
setInstalledPluginIndexInstallRecords({
|
|
"openclaw-mem0": {
|
|
source: "clawhub",
|
|
spec: "clawhub:openclaw-mem0",
|
|
installPath: "/plugins/openclaw-mem0",
|
|
version: "2026.5.1",
|
|
clawhubPackage: "openclaw-mem0",
|
|
clawhubChannel: "official",
|
|
artifactKind: "npm-pack",
|
|
artifactFormat: "tgz",
|
|
npmIntegrity: "sha512-clawpack",
|
|
npmShasum: "1".repeat(40),
|
|
npmTarballName: "openclaw-mem0-2026.5.1.tgz",
|
|
clawpackSha256: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
clawpackSpecVersion: 1,
|
|
clawpackManifestSha256: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
clawpackSize: 4096,
|
|
},
|
|
});
|
|
buildPluginSnapshotReport.mockReturnValue({
|
|
plugins: [createPluginRecord({ id: "openclaw-mem0", name: "Mem0" })],
|
|
diagnostics: [],
|
|
});
|
|
buildPluginInspectReport.mockReturnValue({
|
|
workspaceDir: "/workspace",
|
|
plugin: createPluginRecord({ id: "openclaw-mem0", name: "Mem0" }),
|
|
shape: "hook-only",
|
|
capabilityMode: "plain",
|
|
capabilityCount: 1,
|
|
capabilities: [],
|
|
typedHooks: [{ name: "agent_end" }],
|
|
customHooks: [],
|
|
tools: [],
|
|
commands: [],
|
|
cliCommands: [],
|
|
services: [],
|
|
gatewayDiscoveryServices: [],
|
|
mcpServers: [],
|
|
lspServers: [],
|
|
httpRouteCount: 0,
|
|
bundleCapabilities: [],
|
|
diagnostics: [],
|
|
policy: {
|
|
allowConversationAccess: true,
|
|
allowedModels: [],
|
|
hasAllowedModelsConfig: false,
|
|
},
|
|
usesLegacyBeforeAgentStart: false,
|
|
compatibility: [],
|
|
});
|
|
|
|
await runPluginsCommand(["plugins", "inspect", "openclaw-mem0"]);
|
|
|
|
expect(buildPluginDiagnosticsReport).not.toHaveBeenCalled();
|
|
expect(runtimeLogs.join("\n")).toContain("Policy");
|
|
expect(runtimeLogs.join("\n")).toContain("allowConversationAccess: true");
|
|
expect(runtimeLogs.join("\n")).toContain("ClawHub package: openclaw-mem0");
|
|
expect(runtimeLogs.join("\n")).toContain("Artifact kind: npm-pack");
|
|
expect(runtimeLogs.join("\n")).toContain("Npm integrity: sha512-clawpack");
|
|
expect(runtimeLogs.join("\n")).toContain(
|
|
"ClawPack sha256: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
);
|
|
expect(runtimeLogs.join("\n")).toContain("ClawPack spec: 1");
|
|
expect(runtimeLogs.join("\n")).toContain("ClawPack size: 4096 bytes");
|
|
});
|
|
|
|
it("runtime-inspects without repairing deps", async () => {
|
|
buildPluginSnapshotReport.mockReturnValue({
|
|
plugins: [createPluginRecord({ id: "openclaw-mem0", name: "Mem0" })],
|
|
diagnostics: [],
|
|
});
|
|
buildPluginInspectReport.mockReturnValue({
|
|
workspaceDir: "/workspace",
|
|
plugin: createPluginRecord({ id: "openclaw-mem0", name: "Mem0" }),
|
|
shape: "hook-only",
|
|
capabilityMode: "plain",
|
|
capabilityCount: 1,
|
|
capabilities: [],
|
|
typedHooks: [],
|
|
customHooks: [],
|
|
tools: [],
|
|
commands: [],
|
|
cliCommands: [],
|
|
services: [],
|
|
gatewayDiscoveryServices: [],
|
|
mcpServers: [],
|
|
lspServers: [],
|
|
httpRouteCount: 0,
|
|
bundleCapabilities: [],
|
|
diagnostics: [],
|
|
policy: {
|
|
allowedModels: [],
|
|
hasAllowedModelsConfig: false,
|
|
},
|
|
usesLegacyBeforeAgentStart: false,
|
|
compatibility: [],
|
|
});
|
|
|
|
await runPluginsCommand(["plugins", "inspect", "openclaw-mem0", "--runtime"]);
|
|
|
|
expect(buildPluginDiagnosticsReport).toHaveBeenCalledWith({
|
|
config: {},
|
|
onlyPluginIds: ["openclaw-mem0"],
|
|
});
|
|
});
|
|
|
|
it("does not runtime-load plugins when inspect target is missing", async () => {
|
|
buildPluginSnapshotReport.mockReturnValue({
|
|
plugins: [],
|
|
diagnostics: [],
|
|
});
|
|
|
|
await expect(runPluginsCommand(["plugins", "inspect", "missing-plugin"])).rejects.toThrow(
|
|
"__exit__:1",
|
|
);
|
|
|
|
expect(buildPluginSnapshotReport).toHaveBeenCalledWith({ config: {} });
|
|
expect(buildPluginDiagnosticsReport).not.toHaveBeenCalled();
|
|
expect(runtimeErrors.at(-1)).toContain("Plugin not found: missing-plugin");
|
|
});
|
|
});
|