

























@@ -26,13 +26,55 @@ vi.mock("./plugin-registry.js", () => ({
26262727import { resolvePluginConfigContractsById } from "./config-contracts.js";
282829+type PluginManifestRecord = PluginManifestRegistry["plugins"][number];
30+2931function createRegistry(plugins: PluginManifestRegistry["plugins"]): PluginManifestRegistry {
3032return {
3133 plugins,
3234diagnostics: [],
3335};
3436}
353738+function createPluginRecord(
39+overrides: Pick<PluginManifestRecord, "id" | "origin"> & Partial<PluginManifestRecord>,
40+): PluginManifestRecord {
41+return {
42+rootDir: `/tmp/${overrides.id}`,
43+manifestPath: `/tmp/${overrides.id}/openclaw.plugin.json`,
44+channelConfigs: undefined,
45+providerAuthEnvVars: undefined,
46+configUiHints: undefined,
47+configSchema: undefined,
48+configContracts: undefined,
49+contracts: undefined,
50+name: undefined,
51+description: undefined,
52+version: undefined,
53+enabledByDefault: undefined,
54+autoEnableWhenConfiguredProviders: undefined,
55+legacyPluginIds: undefined,
56+format: undefined,
57+bundleFormat: undefined,
58+bundleCapabilities: undefined,
59+kind: undefined,
60+channels: [],
61+providers: [],
62+modelSupport: undefined,
63+cliBackends: [],
64+channelEnvVars: undefined,
65+providerAuthAliases: undefined,
66+providerAuthChoices: undefined,
67+skills: [],
68+settingsFiles: undefined,
69+hooks: [],
70+source: `/tmp/${overrides.id}/openclaw.plugin.json`,
71+setupSource: undefined,
72+startupDeferConfiguredChannelFullLoadUntilAfterListen: undefined,
73+channelCatalogMeta: undefined,
74+ ...overrides,
75+};
76+}
77+3678describe("resolvePluginConfigContractsById", () => {
3779beforeEach(() => {
3880mocks.findBundledPluginMetadataById.mockReset();
@@ -45,42 +87,10 @@ describe("resolvePluginConfigContractsById", () => {
4587it("does not fall back to bundled metadata when registry already resolved a plugin without config contracts", () => {
4688mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue(
4789createRegistry([
48-{
90+createPluginRecord({
4991id: "brave",
5092origin: "bundled",
51-rootDir: "/tmp/brave",
52-manifestPath: "/tmp/brave/openclaw.plugin.json",
53-channelConfigs: undefined,
54-providerAuthEnvVars: undefined,
55-configUiHints: undefined,
56-configSchema: undefined,
57-configContracts: undefined,
58-contracts: undefined,
59-name: undefined,
60-description: undefined,
61-version: undefined,
62-enabledByDefault: undefined,
63-autoEnableWhenConfiguredProviders: undefined,
64-legacyPluginIds: undefined,
65-format: undefined,
66-bundleFormat: undefined,
67-bundleCapabilities: undefined,
68-kind: undefined,
69-channels: [],
70-providers: [],
71-modelSupport: undefined,
72-cliBackends: [],
73-channelEnvVars: undefined,
74-providerAuthAliases: undefined,
75-providerAuthChoices: undefined,
76-skills: [],
77-settingsFiles: undefined,
78-hooks: [],
79-source: "/tmp/brave/openclaw.plugin.json",
80-setupSource: undefined,
81-startupDeferConfiguredChannelFullLoadUntilAfterListen: undefined,
82-channelCatalogMeta: undefined,
83-},
93+}),
8494]),
8595);
8696@@ -92,6 +102,92 @@ describe("resolvePluginConfigContractsById", () => {
92102expect(mocks.findBundledPluginMetadataById).not.toHaveBeenCalled();
93103});
94104105+it("can hydrate missing contracts from bundled metadata for resolved bundled plugins", () => {
106+mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue(
107+createRegistry([
108+createPluginRecord({
109+id: "voice-call",
110+origin: "bundled",
111+configContracts: {
112+compatibilityMigrationPaths: ["plugins.entries.voice-call.config"],
113+},
114+}),
115+]),
116+);
117+mocks.findBundledPluginMetadataById.mockReturnValue({
118+manifest: {
119+configContracts: {
120+secretInputs: {
121+paths: [{ path: "twilio.authToken", expected: "string" }],
122+},
123+},
124+},
125+});
126+127+expect(
128+resolvePluginConfigContractsById({
129+pluginIds: ["voice-call"],
130+fallbackToBundledMetadataForResolvedBundled: true,
131+}),
132+).toEqual(
133+new Map([
134+[
135+"voice-call",
136+{
137+origin: "bundled",
138+configContracts: {
139+compatibilityMigrationPaths: ["plugins.entries.voice-call.config"],
140+secretInputs: {
141+paths: [{ path: "twilio.authToken", expected: "string" }],
142+},
143+},
144+},
145+],
146+]),
147+);
148+});
149+150+it("can hydrate missing contracts for plugin ids known to be bundled by runtime discovery", () => {
151+mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue(
152+createRegistry([
153+createPluginRecord({
154+id: "voice-call",
155+origin: "config",
156+}),
157+]),
158+);
159+mocks.findBundledPluginMetadataById.mockReturnValue({
160+manifest: {
161+configContracts: {
162+secretInputs: {
163+paths: [{ path: "tts.providers.*.apiKey", expected: "string" }],
164+},
165+},
166+},
167+});
168+169+expect(
170+resolvePluginConfigContractsById({
171+pluginIds: ["voice-call"],
172+fallbackBundledPluginIds: ["voice-call"],
173+}),
174+).toEqual(
175+new Map([
176+[
177+"voice-call",
178+{
179+origin: "bundled",
180+configContracts: {
181+secretInputs: {
182+paths: [{ path: "tts.providers.*.apiKey", expected: "string" }],
183+},
184+},
185+},
186+],
187+]),
188+);
189+});
190+95191it("can skip bundled metadata fallback for registry-scoped callers", () => {
96192expect(
97193resolvePluginConfigContractsById({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。