fix(models): preserve source snapshots for SecretRef providers

* fix(models): preserve source snapshots for SecretRef providers

* docs: add models SecretRef changelog entry

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
NianJiu
2026-05-24 10:48:05 +08:00
committed by GitHub
parent bc6d430d00
commit 0abedd546a
4 changed files with 55 additions and 6 deletions

View File

@@ -67,6 +67,7 @@ Docs: https://docs.openclaw.ai
- Discord/voice: recover stale realtime playback state when Discord stream-close/player-idle events do not arrive, and keep generated runtime plugin aliases available after postbuild rewrites.
- Discord/voice: keep realtime playback running when meeting notes attaches to an existing voice session or a realtime consult starts, and route realtime user transcripts into meeting notes.
- Config/secrets: preflight active runtime SecretRefs before root and include config writes persist, and roll back unchanged file/env state when post-write refresh fails. Fixes #46531. (#84454) Thanks @samzong.
- CLI/models: preserve SecretRef-backed custom provider `apiKey` markers when `models status` regenerates `models.json`, avoiding resolved plaintext secrets on disk. Fixes #84632. (#84658) Thanks @NianJiuZst.
- WebChat: keep the run-complete indicator in progress until deferred history replay renders the assistant reply, so Done no longer appears before response text. (#85374) Thanks @neeravmakwana.
- Agents/tools: give timed-out or cancelled process trees a bounded SIGTERM cleanup window before SIGKILL while preserving tree-aware cancellation. Fixes #66399. (#85865) Thanks @IWhatsskill.
- Agents/compaction: skip agent-harness preflight for provider-owned CLI runtime sessions so over-threshold Claude CLI sessions continue through normal compaction instead of failing on a missing harness. Fixes #84857. (#84878) Thanks @zhangguiping-xydt.

View File

@@ -100,6 +100,40 @@ function createOpenAiApiKeyRuntimeConfig(): OpenClawConfig {
};
}
function createCustomProviderApiKeySourceConfig(): OpenClawConfig {
return {
models: {
providers: {
litellm: {
baseUrl: "https://litellm.example/v1",
apiKey: {
source: "env",
provider: "default",
id: "OPENCLAW_MODEL_LITELLM_API_KEY", // pragma: allowlist secret
},
api: "openai-completions" as const,
models: [],
},
},
},
};
}
function createCustomProviderApiKeyRuntimeConfig(): OpenClawConfig {
return {
models: {
providers: {
litellm: {
baseUrl: "https://litellm.example/v1",
apiKey: "sk-litellm-runtime-secret", // pragma: allowlist secret
api: "openai-completions" as const,
models: [],
},
},
},
};
}
function createOpenAiHeaderSourceConfig(): OpenClawConfig {
return {
models: {
@@ -280,6 +314,24 @@ describe("models-config runtime source snapshot", () => {
});
});
it("preserves source markers for custom-provider api keys after models status secret resolution", async () => {
const agentDir = await fixtureSuite.createCaseDir("agent");
await withTempEnv(MODELS_CONFIG_IMPLICIT_ENV_VARS, async () => {
unsetEnv(MODELS_CONFIG_IMPLICIT_ENV_VARS);
const sourceConfig = createCustomProviderApiKeySourceConfig();
const runtimeConfig = createCustomProviderApiKeyRuntimeConfig();
try {
setRuntimeConfigSnapshot(runtimeConfig, sourceConfig);
await ensureOpenClawModelsJson(runtimeConfig, agentDir);
await expectGeneratedProviderApiKey(agentDir, "litellm", "OPENCLAW_MODEL_LITELLM_API_KEY"); // pragma: allowlist secret
} finally {
clearRuntimeConfigSnapshot();
clearConfigCache();
}
});
});
it("invalidates cached readiness when projected config changes under the same runtime snapshot", async () => {
const agentDir = await fixtureSuite.createCaseDir("agent");
await withTempEnv(MODELS_CONFIG_IMPLICIT_ENV_VARS, async () => {

View File

@@ -99,6 +99,6 @@ describe("models load-config", () => {
const result = await loadModelsConfigWithSource({ commandName: "models list" });
expect(result.sourceConfig).toBe(runtimeConfig);
expect(mocks.setRuntimeConfigSnapshot).toHaveBeenCalledWith(resolvedConfig);
expect(mocks.setRuntimeConfigSnapshot).toHaveBeenCalledWith(resolvedConfig, runtimeConfig);
});
});

View File

@@ -27,11 +27,7 @@ export async function loadModelsConfigWithSource(params: {
targetIds: getModelsCommandSecretTargetIds(),
runtime: params.runtime,
});
if (pinnedSourceConfig) {
setRuntimeConfigSnapshot(resolvedConfig, sourceConfig);
} else {
setRuntimeConfigSnapshot(resolvedConfig);
}
setRuntimeConfigSnapshot(resolvedConfig, sourceConfig);
return {
sourceConfig,
resolvedConfig,