



























@@ -13,6 +13,7 @@ let findModelInCatalog: typeof import("./model-catalog.js").findModelInCatalog;
1313let loadManifestModelCatalog: typeof import("./model-catalog.js").loadManifestModelCatalog;
1414let loadModelCatalog: typeof import("./model-catalog.js").loadModelCatalog;
1515let modelSupportsInput: typeof import("./model-catalog.js").modelSupportsInput;
16+let resetModelCatalogCache: typeof import("./model-catalog.js").resetModelCatalogCache;
1617let resetModelCatalogCacheForTest: typeof import("./model-catalog.js").resetModelCatalogCacheForTest;
1718let augmentCatalogMock: ReturnType<typeof vi.fn>;
1819let prepareOpenClawModelsJsonSourceMock: ReturnType<typeof vi.fn>;
@@ -337,6 +338,7 @@ describe("loadModelCatalog", () => {
337338 loadManifestModelCatalog,
338339 loadModelCatalog,
339340 modelSupportsInput,
341+ resetModelCatalogCache,
340342 resetModelCatalogCacheForTest,
341343} = await import("./model-catalog.js"));
342344const providerRuntime = await import("../plugins/provider-runtime.runtime.js");
@@ -512,6 +514,57 @@ describe("loadModelCatalog", () => {
512514});
513515});
514516517+it("exposes only a fully loaded process catalog snapshot", async () => {
518+mockAgentDiscoveryModels([
519+{ id: "runtime-reasoner", name: "Runtime Reasoner", provider: "ollama", reasoning: true },
520+]);
521+await expect(loadModelCatalog({ cacheOnly: true })).resolves.toEqual([]);
522+523+const result = await loadModelCatalog({ config: {} as OpenClawConfig });
524+525+await expect(loadModelCatalog({ cacheOnly: true })).resolves.toBe(result);
526+resetModelCatalogCache();
527+await expect(loadModelCatalog({ cacheOnly: true })).resolves.toEqual([]);
528+resetModelCatalogCacheForTest();
529+await expect(loadModelCatalog({ cacheOnly: true })).resolves.toEqual([]);
530+});
531+532+it("does not publish a catalog load from an invalidated generation", async () => {
533+let releaseStaleFingerprint:
534+| ((value: { agentDir: string; fingerprint: string; workspaceDir: string }) => void)
535+| undefined;
536+const staleFingerprint = new Promise<{
537+agentDir: string;
538+fingerprint: string;
539+workspaceDir: string;
540+}>((resolve) => {
541+releaseStaleFingerprint = resolve;
542+});
543+buildModelsJsonSourceFingerprintMock.mockReturnValueOnce(staleFingerprint).mockResolvedValue({
544+agentDir: "/tmp/openclaw",
545+fingerprint: "fresh-fingerprint",
546+workspaceDir: "/tmp/openclaw-workspace",
547+});
548+const freshCatalog = [{ id: "fresh", name: "Fresh", provider: "ollama", reasoning: true }];
549+const staleCatalog = [{ id: "stale", name: "Stale", provider: "ollama", reasoning: false }];
550+readCachedAgentModelCatalogMock
551+.mockReturnValueOnce(freshCatalog)
552+.mockReturnValueOnce(staleCatalog);
553+554+const staleLoad = loadModelCatalog({ config: {} as OpenClawConfig });
555+resetModelCatalogCache();
556+await expect(loadModelCatalog({ config: {} as OpenClawConfig })).resolves.toBe(freshCatalog);
557+await expect(loadModelCatalog({ cacheOnly: true })).resolves.toBe(freshCatalog);
558+559+releaseStaleFingerprint?.({
560+agentDir: "/tmp/openclaw",
561+fingerprint: "stale-fingerprint",
562+workspaceDir: "/tmp/openclaw-workspace",
563+});
564+await expect(staleLoad).resolves.toBe(staleCatalog);
565+await expect(loadModelCatalog({ cacheOnly: true })).resolves.toBe(freshCatalog);
566+});
567+515568it("preserves runtime model params in the internal catalog", async () => {
516569mockAgentDiscoveryModels([
517570{
@@ -727,6 +780,7 @@ describe("loadModelCatalog", () => {
727780728781const result = await loadModelCatalog({ config: {} as OpenClawConfig });
729782expect(result).toEqual([{ id: "gpt-4.1", name: "GPT-4.1", provider: "openai" }]);
783+await expect(loadModelCatalog({ cacheOnly: true })).resolves.toEqual([]);
730784} finally {
731785setLoggerOverride(null);
732786resetLogger();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。