fix: avoid default workspace metadata for agent models

This commit is contained in:
Shakker
2026-04-27 17:45:06 +01:00
parent 51c7f544f3
commit 498af508d0
2 changed files with 94 additions and 4 deletions

View File

@@ -151,21 +151,29 @@ export async function ensureOpenClawModelsJson(
agentDirOverride?: string,
options: {
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">;
workspaceDir?: string;
} = {},
): Promise<{ agentDir: string; wrote: boolean }> {
const resolved = resolveModelsConfigInput(config);
const cfg = resolved.config;
const workspaceDir = resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg));
const workspaceDir =
options.workspaceDir ??
(agentDirOverride?.trim()
? undefined
: resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg)));
const pluginMetadataSnapshot =
options.pluginMetadataSnapshot ??
getCurrentPluginMetadataSnapshot({ config: cfg, workspaceDir });
getCurrentPluginMetadataSnapshot({
config: cfg,
...(workspaceDir ? { workspaceDir } : {}),
});
const agentDir = agentDirOverride?.trim() ? agentDirOverride.trim() : resolveOpenClawAgentDir();
const targetPath = path.join(agentDir, "models.json");
const fingerprint = await buildModelsJsonFingerprint({
config: cfg,
sourceConfigForSecrets: resolved.sourceConfigForSecrets,
agentDir,
workspaceDir,
...(workspaceDir ? { workspaceDir } : {}),
...(pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {}),
});
const cached = MODELS_JSON_STATE.readyCache.get(targetPath);
@@ -187,7 +195,7 @@ export async function ensureOpenClawModelsJson(
sourceConfigForSecrets: resolved.sourceConfigForSecrets,
agentDir,
env,
workspaceDir,
...(workspaceDir ? { workspaceDir } : {}),
existingRaw: existingModelsFile.raw,
existingParsed: existingModelsFile.parsed,
...(pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {}),

View File

@@ -1,6 +1,8 @@
import fs from "node:fs/promises";
import path from "node:path";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { resolveInstalledPluginIndexPolicyHash } from "../plugins/installed-plugin-index-policy.js";
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
import {
CUSTOM_PROXY_MODELS_CONFIG,
installModelsConfigTestHooks,
@@ -13,15 +15,63 @@ const planOpenClawModelsJsonMock = vi.fn();
installModelsConfigTestHooks();
let ensureOpenClawModelsJson: typeof import("./models-config.js").ensureOpenClawModelsJson;
let clearCurrentPluginMetadataSnapshot: typeof import("../plugins/current-plugin-metadata-snapshot.js").clearCurrentPluginMetadataSnapshot;
let setCurrentPluginMetadataSnapshot: typeof import("../plugins/current-plugin-metadata-snapshot.js").setCurrentPluginMetadataSnapshot;
function createPluginMetadataSnapshot(workspaceDir: string): PluginMetadataSnapshot {
const policyHash = resolveInstalledPluginIndexPolicyHash({});
return {
policyHash,
workspaceDir,
index: {
version: 1,
hostContractVersion: "test",
compatRegistryVersion: "test",
migrationVersion: 1,
policyHash,
generatedAtMs: 1,
installRecords: {},
plugins: [],
diagnostics: [],
},
registryDiagnostics: [],
manifestRegistry: { plugins: [], diagnostics: [] },
plugins: [],
diagnostics: [],
byPluginId: new Map(),
normalizePluginId: (pluginId) => pluginId,
owners: {
channels: new Map(),
channelConfigs: new Map(),
providers: new Map(),
modelCatalogProviders: new Map(),
cliBackends: new Map(),
setupProviders: new Map(),
commandAliases: new Map(),
contracts: new Map(),
},
metrics: {
registrySnapshotMs: 0,
manifestRegistryMs: 0,
ownerMapsMs: 0,
totalMs: 0,
indexPluginCount: 0,
manifestPluginCount: 0,
},
};
}
beforeAll(async () => {
vi.doMock("./models-config.plan.js", () => ({
planOpenClawModelsJson: (...args: unknown[]) => planOpenClawModelsJsonMock(...args),
}));
({ ensureOpenClawModelsJson } = await import("./models-config.js"));
({ clearCurrentPluginMetadataSnapshot, setCurrentPluginMetadataSnapshot } =
await import("../plugins/current-plugin-metadata-snapshot.js"));
});
beforeEach(() => {
clearCurrentPluginMetadataSnapshot();
planOpenClawModelsJsonMock
.mockReset()
.mockImplementation(async (params: { cfg?: typeof CUSTOM_PROXY_MODELS_CONFIG }) => ({
@@ -31,6 +81,38 @@ beforeEach(() => {
});
describe("models-config write serialization", () => {
it("does not reuse default workspace plugin metadata for explicit agent dirs without workspace", async () => {
await withModelsTempHome(async (home) => {
const snapshot = createPluginMetadataSnapshot(path.join(home, "default-workspace"));
setCurrentPluginMetadataSnapshot(snapshot, { config: {} });
const agentDir = path.join(home, "agent-non-default");
await ensureOpenClawModelsJson({}, agentDir);
expect(planOpenClawModelsJsonMock).toHaveBeenCalledWith(
expect.not.objectContaining({ pluginMetadataSnapshot: snapshot }),
);
});
});
it("reuses current plugin metadata for explicit agent dirs with matching workspace", async () => {
await withModelsTempHome(async (home) => {
const workspaceDir = path.join(home, "agent-workspace");
const snapshot = createPluginMetadataSnapshot(workspaceDir);
setCurrentPluginMetadataSnapshot(snapshot, { config: {} });
const agentDir = path.join(home, "agent-non-default");
await ensureOpenClawModelsJson({}, agentDir, { workspaceDir });
expect(planOpenClawModelsJsonMock).toHaveBeenCalledWith(
expect.objectContaining({
workspaceDir,
pluginMetadataSnapshot: snapshot,
}),
);
});
});
it("serializes concurrent models.json writes to avoid overlap", async () => {
await withModelsTempHome(async () => {
const first = structuredClone(CUSTOM_PROXY_MODELS_CONFIG);