Skip to content

Commit 6de357a

Browse files
SunnyShu0925claudesallyom
authored
fix(agents): enable bundled static catalog fallback for cron Attempt 2 (#96070)
* [AI] fix(agents): enable bundled static catalog fallback for cron Attempt 2 (#95500) Plugin-provided models (e.g. opencode-go/deepseek-v4-flash) are registered in the bundled static catalog but are not discoverable via agent model discovery alone. The embedded runner's Attempt 2 (after ensureOpenClawModelsJson) now passes allowBundledStaticCatalogFallback to resolveModelAsync so these models are resolved through the static catalog fallback path. Co-Authored-By: iCodeMate <[email protected]> * fix(agents): trim production comment to durable contract per ClawSweeper P3 * fix(agents): enforce plugin policy for static catalog fallback Signed-off-by: sallyom <[email protected]> --------- Signed-off-by: sallyom <[email protected]> Co-authored-by: iCodeMate <[email protected]> Co-authored-by: sallyom <[email protected]>
1 parent 1052652 commit 6de357a

3 files changed

Lines changed: 84 additions & 5 deletions

File tree

src/agents/embedded-agent-runner/model.static-catalog.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const manifestMocks = vi.hoisted(() => ({
88
}));
99
const providerMocks = vi.hoisted(() => ({
1010
normalizePluginDiscoveryResult: vi.fn(),
11+
resolveActivatableProviderOwnerPluginIds: vi.fn(),
1112
resolveBundledProviderCompatPluginIds: vi.fn(),
1213
resolveOwningPluginIdsForProviderRef: vi.fn(),
1314
resolveRuntimePluginDiscoveryProviders: vi.fn(),
@@ -30,6 +31,7 @@ vi.mock("../../plugins/manifest-registry.js", async (importOriginal) => ({
3031

3132
vi.mock("../../plugins/providers.js", async (importOriginal) => ({
3233
...(await importOriginal<typeof import("../../plugins/providers.js")>()),
34+
resolveActivatableProviderOwnerPluginIds: providerMocks.resolveActivatableProviderOwnerPluginIds,
3335
resolveBundledProviderCompatPluginIds: providerMocks.resolveBundledProviderCompatPluginIds,
3436
resolveOwningPluginIdsForProviderRef: providerMocks.resolveOwningPluginIdsForProviderRef,
3537
}));
@@ -117,12 +119,16 @@ beforeEach(() => {
117119
manifestMocks.loadPluginManifest.mockReset();
118120
manifestMocks.loadPluginManifestRegistry.mockReset();
119121
providerMocks.normalizePluginDiscoveryResult.mockReset();
122+
providerMocks.resolveActivatableProviderOwnerPluginIds.mockReset();
120123
providerMocks.resolveBundledProviderCompatPluginIds.mockReset();
121124
providerMocks.resolveOwningPluginIdsForProviderRef.mockReset();
122125
providerMocks.resolveRuntimePluginDiscoveryProviders.mockReset();
123126
providerMocks.runProviderStaticCatalog.mockReset();
124127
setManifestPlugins([]);
125128
manifestMocks.loadPluginManifestRegistry.mockReturnValue({ plugins: [] });
129+
providerMocks.resolveActivatableProviderOwnerPluginIds.mockImplementation(
130+
({ pluginIds }: { pluginIds: string[] }) => pluginIds,
131+
);
126132
providerMocks.resolveBundledProviderCompatPluginIds.mockReturnValue([]);
127133
providerMocks.resolveOwningPluginIdsForProviderRef.mockReturnValue(undefined);
128134
providerMocks.resolveRuntimePluginDiscoveryProviders.mockResolvedValue([]);
@@ -191,6 +197,25 @@ describe("resolveBundledStaticCatalogModel", () => {
191197
}
192198
});
193199

200+
it("does not resolve bundled manifest rows blocked by plugin config", () => {
201+
setManifestPlugins([createMistralManifestPlugin()]);
202+
203+
for (const cfg of [
204+
{ plugins: { enabled: false } },
205+
{ plugins: { entries: { mistral: { enabled: false } } } },
206+
{ plugins: { deny: ["mistral"] } },
207+
{ plugins: { allow: ["google"] } },
208+
]) {
209+
expect(
210+
resolveBundledStaticCatalogModel({
211+
provider: "mistral",
212+
modelId: "mistral-medium-3-5",
213+
cfg,
214+
}),
215+
).toBeUndefined();
216+
}
217+
});
218+
194219
it("can include bundled runtime-discovery manifest catalog rows for configured fallbacks", () => {
195220
setManifestPlugins([createMistralManifestPlugin({ discovery: "runtime" })]);
196221

@@ -424,6 +449,23 @@ describe("resolveBundledProviderStaticCatalogModel", () => {
424449
});
425450
});
426451

452+
it("does not load bundled provider static catalogs when owner policy blocks the plugin", async () => {
453+
providerMocks.resolveOwningPluginIdsForProviderRef.mockReturnValue(["google"]);
454+
providerMocks.resolveActivatableProviderOwnerPluginIds.mockReturnValue([]);
455+
providerMocks.resolveBundledProviderCompatPluginIds.mockReturnValue(["google"]);
456+
457+
await expect(
458+
resolveBundledProviderStaticCatalogModel({
459+
provider: "google",
460+
modelId: "gemini-3.1-pro-preview",
461+
cfg: { plugins: { entries: { google: { enabled: false } } } },
462+
}),
463+
).resolves.toBeUndefined();
464+
465+
expect(providerMocks.resolveRuntimePluginDiscoveryProviders).not.toHaveBeenCalled();
466+
expect(providerMocks.runProviderStaticCatalog).not.toHaveBeenCalled();
467+
});
468+
427469
it("runs each prepared provider static catalog once", async () => {
428470
const provider = {
429471
id: "google",

src/agents/embedded-agent-runner/model.static-catalog.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
66
import type { ModelProviderConfig } from "../../config/types.models.js";
77
import type { OpenClawConfig } from "../../config/types.openclaw.js";
88
import { planManifestModelCatalogRows } from "../../model-catalog/manifest-planner.js";
9+
import { normalizePluginsConfig } from "../../plugins/config-state.js";
910
import { listOpenClawPluginManifestMetadata } from "../../plugins/manifest-metadata-scan.js";
11+
import { passesManifestOwnerBasePolicy } from "../../plugins/manifest-owner-policy.js";
1012
import { loadPluginManifestRegistry } from "../../plugins/manifest-registry.js";
1113
import type { PluginManifestRecord } from "../../plugins/manifest-registry.js";
1214
import { loadPluginManifest } from "../../plugins/manifest.js";
@@ -17,6 +19,7 @@ import {
1719
} from "../../plugins/provider-discovery.js";
1820
import type { ProviderRuntimeModel } from "../../plugins/provider-runtime-model.types.js";
1921
import {
22+
resolveActivatableProviderOwnerPluginIds,
2023
resolveBundledProviderCompatPluginIds,
2124
resolveOwningPluginIdsForProviderRef,
2225
} from "../../plugins/providers.js";
@@ -136,15 +139,27 @@ type StaticCatalogPlugin = Parameters<
136139
typeof planManifestModelCatalogRows
137140
>[0]["registry"]["plugins"][number];
138141

139-
function listBundledStaticCatalogPlugins(env: NodeJS.ProcessEnv): StaticCatalogPlugin[] {
140-
return listOpenClawPluginManifestMetadata(env).flatMap((record): StaticCatalogPlugin[] => {
142+
function listBundledStaticCatalogPlugins(params: {
143+
cfg?: OpenClawConfig;
144+
env: NodeJS.ProcessEnv;
145+
}): StaticCatalogPlugin[] {
146+
const normalizedConfig = normalizePluginsConfig(params.cfg?.plugins);
147+
return listOpenClawPluginManifestMetadata(params.env).flatMap((record): StaticCatalogPlugin[] => {
141148
if (record.origin !== "bundled") {
142149
return [];
143150
}
144151
const loaded = loadPluginManifest(record.pluginDir);
145152
if (!loaded.ok || !loaded.manifest.modelCatalog) {
146153
return [];
147154
}
155+
if (
156+
!passesManifestOwnerBasePolicy({
157+
plugin: { id: loaded.manifest.id },
158+
normalizedConfig,
159+
})
160+
) {
161+
return [];
162+
}
148163
return [
149164
{
150165
id: loaded.manifest.id,
@@ -206,13 +221,17 @@ export function canonicalizeManifestModelCatalogProviderAlias(params: {
206221
/** Returns whether a bundled static catalog asks runtime discovery to augment its rows. */
207222
export function bundledStaticCatalogProviderUsesRuntimeAugment(params: {
208223
provider: string;
224+
cfg?: OpenClawConfig;
209225
env?: NodeJS.ProcessEnv;
210226
}): boolean {
211227
const provider = normalizeProviderId(params.provider);
212228
if (!provider) {
213229
return false;
214230
}
215-
return listBundledStaticCatalogPlugins(params.env ?? process.env).some((plugin) => {
231+
return listBundledStaticCatalogPlugins({
232+
cfg: params.cfg,
233+
env: params.env ?? process.env,
234+
}).some((plugin) => {
216235
const catalog = plugin.modelCatalog;
217236
if (catalog?.runtimeAugment !== true) {
218237
return false;
@@ -254,10 +273,14 @@ type BundledProviderStaticCatalogResolverParams = {
254273
* Manifest discovery runs once; provider-specific plans are cached on demand.
255274
*/
256275
export function createBundledStaticCatalogModelResolver(params?: {
276+
cfg?: OpenClawConfig;
257277
env?: NodeJS.ProcessEnv;
258278
includeRuntimeDiscovery?: boolean;
259279
}): (lookup: BundledStaticCatalogLookup) => ProviderRuntimeModel | undefined {
260-
const bundledStaticPlugins = listBundledStaticCatalogPlugins(params?.env ?? process.env);
280+
const bundledStaticPlugins = listBundledStaticCatalogPlugins({
281+
cfg: params?.cfg,
282+
env: params?.env ?? process.env,
283+
});
261284
const plans = new Map<string, ReturnType<typeof planManifestModelCatalogRows>>();
262285
return (lookup) => {
263286
const provider = normalizeProviderId(lookup.provider);
@@ -304,6 +327,7 @@ export function resolveBundledStaticCatalogModel(
304327
},
305328
): ProviderRuntimeModel | undefined {
306329
return createBundledStaticCatalogModelResolver({
330+
cfg: params.cfg,
307331
...(params.env ? { env: params.env } : {}),
308332
...(params.includeRuntimeDiscovery !== undefined
309333
? { includeRuntimeDiscovery: params.includeRuntimeDiscovery }
@@ -326,14 +350,23 @@ function resolveBundledProviderStaticCatalogPluginIds(params: {
326350
if (!pluginIds || pluginIds.length === 0) {
327351
return [];
328352
}
353+
const activatablePluginIds = resolveActivatableProviderOwnerPluginIds({
354+
pluginIds,
355+
config: params.cfg,
356+
workspaceDir: params.workspaceDir,
357+
env: params.env,
358+
});
359+
if (activatablePluginIds.length === 0) {
360+
return [];
361+
}
329362
const bundledPluginIds = new Set(
330363
resolveBundledProviderCompatPluginIds({
331364
config: params.cfg,
332365
workspaceDir: params.workspaceDir,
333366
env: params.env,
334367
}),
335368
);
336-
return pluginIds.filter((pluginId) => bundledPluginIds.has(pluginId)).toSorted();
369+
return activatablePluginIds.filter((pluginId) => bundledPluginIds.has(pluginId)).toSorted();
337370
}
338371

339372
async function loadBundledProviderStaticCatalogModels(params: {

src/agents/embedded-agent-runner/run.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,6 +1117,10 @@ async function runEmbeddedAgentInternal(
11171117
{
11181118
workspaceDir: resolvedWorkspace,
11191119
authProfileId: params.authProfileId,
1120+
// Enable bundled static catalog fallback so plugin-provided
1121+
// models that are not discoverable via agent model discovery
1122+
// can still be resolved from the static catalog.
1123+
allowBundledStaticCatalogFallback: true,
11201124
},
11211125
);
11221126
firstModelResolution ??= candidateResolution;

0 commit comments

Comments
 (0)