diff --git a/src/agents/model-catalog-state-cache.test.ts b/src/agents/model-catalog-state-cache.test.ts index fbcd64956e0..3303bb08138 100644 --- a/src/agents/model-catalog-state-cache.test.ts +++ b/src/agents/model-catalog-state-cache.test.ts @@ -89,6 +89,38 @@ describe("model catalog state cache", () => { ).toBeUndefined(); }); + it("prunes expired agent catalog rows on write", () => { + const expiredEntries = [{ provider: "openai", id: "gpt-5.5", name: "GPT-5.5" }]; + writeCachedAgentModelCatalog({ + agentDir: "/agent/main", + catalogKey: "expired-key", + entries: expiredEntries, + nowMs: 1_000, + }); + + writeCachedAgentModelCatalog({ + agentDir: "/agent/main", + catalogKey: "fresh-key", + entries: [{ provider: "openai", id: "gpt-5.6", name: "GPT-5.6" }], + nowMs: 31 * 60 * 1_000, + }); + + expect( + readCachedAgentModelCatalog({ + agentDir: "/agent/main", + catalogKey: "expired-key", + nowMs: 1_000, + }), + ).toBeUndefined(); + expect( + readCachedAgentModelCatalog({ + agentDir: "/agent/main", + catalogKey: "fresh-key", + nowMs: 31 * 60 * 1_000, + }), + ).toEqual([{ provider: "openai", id: "gpt-5.6", name: "GPT-5.6" }]); + }); + it("builds stable keys that change with relevant catalog inputs", () => { const base = buildAgentModelCatalogCacheKey({ agentDir: "/agent/main", diff --git a/src/agents/model-catalog-state-cache.ts b/src/agents/model-catalog-state-cache.ts index 520492124c4..1fd22be86b3 100644 --- a/src/agents/model-catalog-state-cache.ts +++ b/src/agents/model-catalog-state-cache.ts @@ -136,6 +136,12 @@ export function writeCachedAgentModelCatalog(params: WriteCachedAgentModelCatalo } satisfies CachedAgentModelCatalogPayload); runOpenClawStateWriteTransaction((database) => { const db = getNodeSqliteKysely(database.db); + executeSqliteQuerySync( + database.db, + db + .deleteFrom("agent_model_catalogs") + .where("updated_at", "<", updatedAt - AGENT_MODEL_CATALOG_CACHE_TTL_MS), + ); executeSqliteQuerySync( database.db, db diff --git a/src/agents/model-catalog.test.ts b/src/agents/model-catalog.test.ts index 185f85d9452..002c28863f4 100644 --- a/src/agents/model-catalog.test.ts +++ b/src/agents/model-catalog.test.ts @@ -733,6 +733,7 @@ describe("loadModelCatalog", () => { const entry = requireCatalogEntry(result, "openai", "gpt-test"); expect(entry.name).toBe("GPT Test"); + expect(readCachedAgentModelCatalogMock).not.toHaveBeenCalled(); expect(prepareOpenClawModelsJsonSourceMock).not.toHaveBeenCalled(); expect(importAgentDiscoveryModule).not.toHaveBeenCalled(); expect(loadPluginMetadataSnapshotMock).not.toHaveBeenCalled(); diff --git a/src/agents/model-catalog.ts b/src/agents/model-catalog.ts index 94734debe77..364b83eb3dd 100644 --- a/src/agents/model-catalog.ts +++ b/src/agents/model-catalog.ts @@ -389,23 +389,6 @@ async function loadReadOnlyPersistedModelCatalog(params?: { manifestPlugins ??= getMetadataSnapshot().plugins; return manifestPlugins; }; - const sourceFingerprint = await buildModelsJsonSourceFingerprint(cfg, agentDir, { - pluginMetadataSnapshot: params?.metadataSnapshot, - workspaceDir, - }); - const cached = readCachedAgentModelCatalog({ - agentDir, - catalogKey: buildLoadModelCatalogStateCacheKey({ - agentDir, - config: cfg, - metadataSnapshot: params?.metadataSnapshot, - sourceFingerprint: sourceFingerprint.fingerprint, - workspaceDir, - }), - }) as ModelCatalogEntry[] | undefined; - if (cached?.length) { - return cached; - } const providers = await loadReadOnlyPersistedProviderRows(agentDir, getMetadataSnapshot); for (const [providerRaw, providerConfig] of Object.entries(providers)) { if (!Array.isArray(providerConfig?.models)) {