import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { ensureAuthProfileStore, type AuthProfileStore } from "../agents/auth-profiles.js"; import { loadConfig, type OpenClawConfig } from "../config/config.js"; import { activateSecretsRuntimeSnapshot, clearSecretsRuntimeSnapshot, prepareSecretsRuntimeSnapshot, } from "./runtime.js"; function asConfig(value: unknown): OpenClawConfig { return value as OpenClawConfig; } const OPENAI_ENV_KEY_REF = { source: "env", provider: "default", id: "OPENAI_API_KEY" } as const; function createOpenAiFileModelsConfig(): NonNullable { return { providers: { openai: { baseUrl: "https://api.openai.com/v1", apiKey: { source: "file", provider: "default", id: "/providers/openai/apiKey" }, models: [], }, }, }; } function loadAuthStoreWithProfiles(profiles: AuthProfileStore["profiles"]): AuthProfileStore { return { version: 1, profiles, }; } describe("secrets runtime snapshot", () => { afterEach(() => { clearSecretsRuntimeSnapshot(); }); it("resolves env refs for config and auth profiles", async () => { const config = asConfig({ agents: { defaults: { memorySearch: { remote: { apiKey: { source: "env", provider: "default", id: "MEMORY_REMOTE_API_KEY" }, }, }, }, }, models: { providers: { openai: { baseUrl: "https://api.openai.com/v1", apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" }, models: [], }, }, }, skills: { entries: { "review-pr": { enabled: true, apiKey: { source: "env", provider: "default", id: "REVIEW_SKILL_API_KEY" }, }, }, }, talk: { apiKey: { source: "env", provider: "default", id: "TALK_API_KEY" }, providers: { elevenlabs: { apiKey: { source: "env", provider: "default", id: "TALK_PROVIDER_API_KEY" }, }, }, }, gateway: { mode: "remote", remote: { url: "wss://gateway.example", token: { source: "env", provider: "default", id: "REMOTE_GATEWAY_TOKEN" }, password: { source: "env", provider: "default", id: "REMOTE_GATEWAY_PASSWORD" }, }, }, channels: { telegram: { botToken: { source: "env", provider: "default", id: "TELEGRAM_BOT_TOKEN_REF" }, webhookUrl: "https://example.test/telegram-webhook", webhookSecret: { source: "env", provider: "default", id: "TELEGRAM_WEBHOOK_SECRET_REF" }, accounts: { work: { botToken: { source: "env", provider: "default", id: "TELEGRAM_WORK_BOT_TOKEN_REF", }, }, }, }, slack: { mode: "http", signingSecret: { source: "env", provider: "default", id: "SLACK_SIGNING_SECRET_REF" }, accounts: { work: { botToken: { source: "env", provider: "default", id: "SLACK_WORK_BOT_TOKEN_REF" }, appToken: { source: "env", provider: "default", id: "SLACK_WORK_APP_TOKEN_REF" }, }, }, }, }, tools: { web: { search: { apiKey: { source: "env", provider: "default", id: "WEB_SEARCH_API_KEY" }, }, }, }, }); const snapshot = await prepareSecretsRuntimeSnapshot({ config, env: { OPENAI_API_KEY: "sk-env-openai", GITHUB_TOKEN: "ghp-env-token", REVIEW_SKILL_API_KEY: "sk-skill-ref", MEMORY_REMOTE_API_KEY: "mem-ref-key", TALK_API_KEY: "talk-ref-key", TALK_PROVIDER_API_KEY: "talk-provider-ref-key", REMOTE_GATEWAY_TOKEN: "remote-token-ref", REMOTE_GATEWAY_PASSWORD: "remote-password-ref", TELEGRAM_BOT_TOKEN_REF: "telegram-bot-ref", TELEGRAM_WEBHOOK_SECRET_REF: "telegram-webhook-ref", TELEGRAM_WORK_BOT_TOKEN_REF: "telegram-work-ref", SLACK_SIGNING_SECRET_REF: "slack-signing-ref", SLACK_WORK_BOT_TOKEN_REF: "slack-work-bot-ref", SLACK_WORK_APP_TOKEN_REF: "slack-work-app-ref", WEB_SEARCH_API_KEY: "web-search-ref", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => loadAuthStoreWithProfiles({ "openai:default": { type: "api_key", provider: "openai", key: "old-openai", keyRef: OPENAI_ENV_KEY_REF, }, "github-copilot:default": { type: "token", provider: "github-copilot", token: "old-gh", tokenRef: { source: "env", provider: "default", id: "GITHUB_TOKEN" }, }, "openai:inline": { type: "api_key", provider: "openai", key: "${OPENAI_API_KEY}", }, }), }); expect(snapshot.config.models?.providers?.openai?.apiKey).toBe("sk-env-openai"); expect(snapshot.config.skills?.entries?.["review-pr"]?.apiKey).toBe("sk-skill-ref"); expect(snapshot.config.agents?.defaults?.memorySearch?.remote?.apiKey).toBe("mem-ref-key"); expect(snapshot.config.talk?.apiKey).toBe("talk-ref-key"); expect(snapshot.config.talk?.providers?.elevenlabs?.apiKey).toBe("talk-provider-ref-key"); expect(snapshot.config.gateway?.remote?.token).toBe("remote-token-ref"); expect(snapshot.config.gateway?.remote?.password).toBe("remote-password-ref"); expect(snapshot.config.channels?.telegram?.botToken).toEqual({ source: "env", provider: "default", id: "TELEGRAM_BOT_TOKEN_REF", }); expect(snapshot.config.channels?.telegram?.webhookSecret).toBe("telegram-webhook-ref"); expect(snapshot.config.channels?.telegram?.accounts?.work?.botToken).toBe("telegram-work-ref"); expect(snapshot.config.channels?.slack?.signingSecret).toBe("slack-signing-ref"); expect(snapshot.config.channels?.slack?.accounts?.work?.botToken).toBe("slack-work-bot-ref"); expect(snapshot.config.channels?.slack?.accounts?.work?.appToken).toEqual({ source: "env", provider: "default", id: "SLACK_WORK_APP_TOKEN_REF", }); expect(snapshot.config.tools?.web?.search?.apiKey).toBe("web-search-ref"); expect(snapshot.warnings).toHaveLength(4); expect(snapshot.warnings.map((warning) => warning.path)).toContain( "channels.slack.accounts.work.appToken", ); expect(snapshot.authStores[0]?.store.profiles["openai:default"]).toMatchObject({ type: "api_key", key: "sk-env-openai", }); expect(snapshot.authStores[0]?.store.profiles["github-copilot:default"]).toMatchObject({ type: "token", token: "ghp-env-token", }); expect(snapshot.authStores[0]?.store.profiles["openai:inline"]).toMatchObject({ type: "api_key", key: "sk-env-openai", }); // After normalization, inline SecretRef string should be promoted to keyRef expect( (snapshot.authStores[0].store.profiles["openai:inline"] as Record).keyRef, ).toEqual({ source: "env", provider: "default", id: "OPENAI_API_KEY" }); }); it("normalizes inline SecretRef object on token to tokenRef", async () => { const config: OpenClawConfig = { models: {}, secrets: {} }; const snapshot = await prepareSecretsRuntimeSnapshot({ config, env: { MY_TOKEN: "resolved-token-value" }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => loadAuthStoreWithProfiles({ "custom:inline-token": { type: "token", provider: "custom", token: { source: "env", provider: "default", id: "MY_TOKEN" } as unknown as string, }, }), }); const profile = snapshot.authStores[0]?.store.profiles["custom:inline-token"] as Record< string, unknown >; // tokenRef should be set from the inline SecretRef expect(profile.tokenRef).toEqual({ source: "env", provider: "default", id: "MY_TOKEN" }); // token should be resolved to the actual value after activation activateSecretsRuntimeSnapshot(snapshot); expect(profile.token).toBe("resolved-token-value"); }); it("normalizes inline SecretRef object on key to keyRef", async () => { const config: OpenClawConfig = { models: {}, secrets: {} }; const snapshot = await prepareSecretsRuntimeSnapshot({ config, env: { MY_KEY: "resolved-key-value" }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => loadAuthStoreWithProfiles({ "custom:inline-key": { type: "api_key", provider: "custom", key: { source: "env", provider: "default", id: "MY_KEY" } as unknown as string, }, }), }); const profile = snapshot.authStores[0]?.store.profiles["custom:inline-key"] as Record< string, unknown >; // keyRef should be set from the inline SecretRef expect(profile.keyRef).toEqual({ source: "env", provider: "default", id: "MY_KEY" }); // key should be resolved to the actual value after activation activateSecretsRuntimeSnapshot(snapshot); expect(profile.key).toBe("resolved-key-value"); }); it("keeps explicit keyRef when inline key SecretRef is also present", async () => { const config: OpenClawConfig = { models: {}, secrets: {} }; const snapshot = await prepareSecretsRuntimeSnapshot({ config, env: { PRIMARY_KEY: "primary-key-value", SHADOW_KEY: "shadow-key-value", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => loadAuthStoreWithProfiles({ "custom:explicit-keyref": { type: "api_key", provider: "custom", keyRef: { source: "env", provider: "default", id: "PRIMARY_KEY" }, key: { source: "env", provider: "default", id: "SHADOW_KEY" } as unknown as string, }, }), }); const profile = snapshot.authStores[0]?.store.profiles["custom:explicit-keyref"] as Record< string, unknown >; expect(profile.keyRef).toEqual({ source: "env", provider: "default", id: "PRIMARY_KEY" }); activateSecretsRuntimeSnapshot(snapshot); expect(profile.key).toBe("primary-key-value"); }); it("treats non-selected web search provider refs as inactive", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ tools: { web: { search: { enabled: true, provider: "brave", apiKey: { source: "env", provider: "default", id: "WEB_SEARCH_API_KEY" }, grok: { apiKey: { source: "env", provider: "default", id: "MISSING_GROK_API_KEY" }, }, }, }, }, }), env: { WEB_SEARCH_API_KEY: "web-search-ref", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.tools?.web?.search?.apiKey).toBe("web-search-ref"); expect(snapshot.config.tools?.web?.search?.grok?.apiKey).toEqual({ source: "env", provider: "default", id: "MISSING_GROK_API_KEY", }); expect(snapshot.warnings).toEqual( expect.arrayContaining([ expect.objectContaining({ code: "SECRETS_REF_IGNORED_INACTIVE_SURFACE", path: "tools.web.search.grok.apiKey", }), ]), ); }); it("resolves provider-specific refs in web search auto mode", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ tools: { web: { search: { enabled: true, apiKey: { source: "env", provider: "default", id: "WEB_SEARCH_API_KEY" }, gemini: { apiKey: { source: "env", provider: "default", id: "WEB_SEARCH_GEMINI_API_KEY" }, }, }, }, }, }), env: { WEB_SEARCH_API_KEY: "web-search-ref", WEB_SEARCH_GEMINI_API_KEY: "web-search-gemini-ref", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.tools?.web?.search?.apiKey).toBe("web-search-ref"); expect(snapshot.config.tools?.web?.search?.gemini?.apiKey).toBe("web-search-gemini-ref"); expect(snapshot.warnings.map((warning) => warning.path)).not.toContain( "tools.web.search.gemini.apiKey", ); }); it("resolves selected web search provider ref even when provider config is disabled", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ tools: { web: { search: { enabled: true, provider: "gemini", gemini: { enabled: false, apiKey: { source: "env", provider: "default", id: "WEB_SEARCH_GEMINI_API_KEY" }, }, }, }, }, }), env: { WEB_SEARCH_GEMINI_API_KEY: "web-search-gemini-ref", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.tools?.web?.search?.gemini?.apiKey).toBe("web-search-gemini-ref"); expect(snapshot.warnings.map((warning) => warning.path)).not.toContain( "tools.web.search.gemini.apiKey", ); }); it("resolves file refs via configured file provider", async () => { if (process.platform === "win32") { return; } const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-secrets-file-provider-")); const secretsPath = path.join(root, "secrets.json"); try { await fs.writeFile( secretsPath, JSON.stringify( { providers: { openai: { apiKey: "sk-from-file-provider", }, }, }, null, 2, ), "utf8", ); await fs.chmod(secretsPath, 0o600); const config = asConfig({ secrets: { providers: { default: { source: "file", path: secretsPath, mode: "json", }, }, defaults: { file: "default", }, }, models: { providers: { openai: { baseUrl: "https://api.openai.com/v1", apiKey: { source: "file", provider: "default", id: "/providers/openai/apiKey" }, models: [], }, }, }, }); const snapshot = await prepareSecretsRuntimeSnapshot({ config, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.models?.providers?.openai?.apiKey).toBe("sk-from-file-provider"); } finally { await fs.rm(root, { recursive: true, force: true }); } }); it("fails when file provider payload is not a JSON object", async () => { if (process.platform === "win32") { return; } const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-secrets-file-provider-bad-")); const secretsPath = path.join(root, "secrets.json"); try { await fs.writeFile(secretsPath, JSON.stringify(["not-an-object"]), "utf8"); await fs.chmod(secretsPath, 0o600); await expect( prepareSecretsRuntimeSnapshot({ config: asConfig({ secrets: { providers: { default: { source: "file", path: secretsPath, mode: "json", }, }, }, models: { ...createOpenAiFileModelsConfig(), }, }), agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }), ).rejects.toThrow("payload is not a JSON object"); } finally { await fs.rm(root, { recursive: true, force: true }); } }); it("activates runtime snapshots for loadConfig and ensureAuthProfileStore", async () => { const prepared = await prepareSecretsRuntimeSnapshot({ config: asConfig({ models: { providers: { openai: { baseUrl: "https://api.openai.com/v1", apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" }, models: [], }, }, }, }), env: { OPENAI_API_KEY: "sk-runtime" }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => loadAuthStoreWithProfiles({ "openai:default": { type: "api_key", provider: "openai", keyRef: OPENAI_ENV_KEY_REF, }, }), }); activateSecretsRuntimeSnapshot(prepared); expect(loadConfig().models?.providers?.openai?.apiKey).toBe("sk-runtime"); const store = ensureAuthProfileStore("/tmp/openclaw-agent-main"); expect(store.profiles["openai:default"]).toMatchObject({ type: "api_key", key: "sk-runtime", }); }); it("skips inactive-surface refs and emits diagnostics", async () => { const config = asConfig({ agents: { defaults: { memorySearch: { enabled: false, remote: { apiKey: { source: "env", provider: "default", id: "DISABLED_MEMORY_API_KEY" }, }, }, }, }, gateway: { auth: { mode: "token", password: { source: "env", provider: "default", id: "DISABLED_GATEWAY_PASSWORD" }, }, }, channels: { telegram: { botToken: { source: "env", provider: "default", id: "DISABLED_TELEGRAM_BASE_TOKEN" }, accounts: { disabled: { enabled: false, botToken: { source: "env", provider: "default", id: "DISABLED_TELEGRAM_ACCOUNT_TOKEN", }, }, }, }, }, tools: { web: { search: { enabled: false, apiKey: { source: "env", provider: "default", id: "DISABLED_WEB_SEARCH_API_KEY" }, gemini: { apiKey: { source: "env", provider: "default", id: "DISABLED_WEB_SEARCH_GEMINI_API_KEY", }, }, }, }, }, }); const snapshot = await prepareSecretsRuntimeSnapshot({ config, env: {}, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.telegram?.botToken).toEqual({ source: "env", provider: "default", id: "DISABLED_TELEGRAM_BASE_TOKEN", }); expect( snapshot.warnings.filter( (warning) => warning.code === "SECRETS_REF_IGNORED_INACTIVE_SURFACE", ), ).toHaveLength(6); expect(snapshot.warnings.map((warning) => warning.path)).toEqual( expect.arrayContaining([ "agents.defaults.memorySearch.remote.apiKey", "gateway.auth.password", "channels.telegram.botToken", "channels.telegram.accounts.disabled.botToken", "tools.web.search.apiKey", "tools.web.search.gemini.apiKey", ]), ); }); it("treats gateway.remote refs as inactive when local auth credentials are configured", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ gateway: { mode: "local", auth: { mode: "password", token: "local-token", password: "local-password", }, remote: { enabled: true, token: { source: "env", provider: "default", id: "MISSING_REMOTE_TOKEN" }, password: { source: "env", provider: "default", id: "MISSING_REMOTE_PASSWORD" }, }, }, }), env: {}, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.gateway?.remote?.token).toEqual({ source: "env", provider: "default", id: "MISSING_REMOTE_TOKEN", }); expect(snapshot.config.gateway?.remote?.password).toEqual({ source: "env", provider: "default", id: "MISSING_REMOTE_PASSWORD", }); expect(snapshot.warnings.map((warning) => warning.path)).toEqual( expect.arrayContaining(["gateway.remote.token", "gateway.remote.password"]), ); }); it("treats gateway.auth.password ref as active when mode is unset and no token is configured", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ gateway: { auth: { password: { source: "env", provider: "default", id: "GATEWAY_PASSWORD_REF" }, }, }, }), env: { GATEWAY_PASSWORD_REF: "resolved-gateway-password", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.gateway?.auth?.password).toBe("resolved-gateway-password"); expect(snapshot.warnings.map((warning) => warning.path)).not.toContain("gateway.auth.password"); }); it("treats gateway.auth.token ref as active when token mode is explicit", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ gateway: { auth: { mode: "token", token: { source: "env", provider: "default", id: "GATEWAY_TOKEN_REF" }, }, }, }), env: { GATEWAY_TOKEN_REF: "resolved-gateway-token", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.gateway?.auth?.token).toBe("resolved-gateway-token"); expect(snapshot.warnings.map((warning) => warning.path)).not.toContain("gateway.auth.token"); }); it("treats gateway.auth.token ref as inactive when password mode is explicit", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ gateway: { auth: { mode: "password", token: { source: "env", provider: "default", id: "GATEWAY_TOKEN_REF" }, password: "password-123", }, }, }), env: { GATEWAY_TOKEN_REF: "resolved-gateway-token", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.gateway?.auth?.token).toEqual({ source: "env", provider: "default", id: "GATEWAY_TOKEN_REF", }); expect(snapshot.warnings.map((warning) => warning.path)).toContain("gateway.auth.token"); }); it("fails when gateway.auth.token ref is active and unresolved", async () => { await expect( prepareSecretsRuntimeSnapshot({ config: asConfig({ gateway: { auth: { mode: "token", token: { source: "env", provider: "default", id: "MISSING_GATEWAY_TOKEN_REF" }, }, }, }), env: {}, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }), ).rejects.toThrow(/MISSING_GATEWAY_TOKEN_REF/i); }); it("treats gateway.auth.password ref as inactive when auth mode is trusted-proxy", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ gateway: { auth: { mode: "trusted-proxy", password: { source: "env", provider: "default", id: "GATEWAY_PASSWORD_REF" }, }, }, }), env: { GATEWAY_PASSWORD_REF: "resolved-gateway-password", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.gateway?.auth?.password).toEqual({ source: "env", provider: "default", id: "GATEWAY_PASSWORD_REF", }); expect(snapshot.warnings.map((warning) => warning.path)).toContain("gateway.auth.password"); }); it("treats gateway.auth.password ref as inactive when remote token is configured", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ gateway: { mode: "local", auth: { password: { source: "env", provider: "default", id: "GATEWAY_PASSWORD_REF" }, }, remote: { token: { source: "env", provider: "default", id: "REMOTE_GATEWAY_TOKEN" }, }, }, }), env: { REMOTE_GATEWAY_TOKEN: "remote-token", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.gateway?.auth?.password).toEqual({ source: "env", provider: "default", id: "GATEWAY_PASSWORD_REF", }); expect(snapshot.warnings.map((warning) => warning.path)).toContain("gateway.auth.password"); }); it.each(["none", "trusted-proxy"] as const)( "treats gateway.remote refs as inactive in local mode when auth mode is %s", async (mode) => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ gateway: { mode: "local", auth: { mode, }, remote: { token: { source: "env", provider: "default", id: "MISSING_REMOTE_TOKEN" }, password: { source: "env", provider: "default", id: "MISSING_REMOTE_PASSWORD" }, }, }, }), env: {}, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.gateway?.remote?.token).toEqual({ source: "env", provider: "default", id: "MISSING_REMOTE_TOKEN", }); expect(snapshot.config.gateway?.remote?.password).toEqual({ source: "env", provider: "default", id: "MISSING_REMOTE_PASSWORD", }); expect(snapshot.warnings.map((warning) => warning.path)).toEqual( expect.arrayContaining(["gateway.remote.token", "gateway.remote.password"]), ); }, ); it("treats gateway.remote.token ref as active in local mode when no local credentials are configured", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ gateway: { mode: "local", auth: {}, remote: { enabled: true, token: { source: "env", provider: "default", id: "REMOTE_TOKEN" }, password: { source: "env", provider: "default", id: "REMOTE_PASSWORD" }, }, }, }), env: { REMOTE_TOKEN: "resolved-remote-token", REMOTE_PASSWORD: "resolved-remote-password", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.gateway?.remote?.token).toBe("resolved-remote-token"); expect(snapshot.warnings.map((warning) => warning.path)).not.toContain("gateway.remote.token"); expect(snapshot.warnings.map((warning) => warning.path)).toContain("gateway.remote.password"); }); it("treats gateway.remote.password ref as active in local mode when password can win", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ gateway: { mode: "local", auth: {}, remote: { enabled: true, password: { source: "env", provider: "default", id: "REMOTE_PASSWORD" }, }, }, }), env: { REMOTE_PASSWORD: "resolved-remote-password", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.gateway?.remote?.password).toBe("resolved-remote-password"); expect(snapshot.warnings.map((warning) => warning.path)).not.toContain( "gateway.remote.password", ); }); it("treats top-level Zalo botToken refs as active even when tokenFile is configured", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { zalo: { botToken: { source: "env", provider: "default", id: "ZALO_BOT_TOKEN" }, tokenFile: "/tmp/missing-zalo-token-file", }, }, }), env: { ZALO_BOT_TOKEN: "resolved-zalo-token", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.zalo?.botToken).toBe("resolved-zalo-token"); expect(snapshot.warnings.map((warning) => warning.path)).not.toContain( "channels.zalo.botToken", ); }); it("treats account-level Zalo botToken refs as active even when tokenFile is configured", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { zalo: { accounts: { work: { botToken: { source: "env", provider: "default", id: "ZALO_WORK_BOT_TOKEN" }, tokenFile: "/tmp/missing-zalo-work-token-file", }, }, }, }, }), env: { ZALO_WORK_BOT_TOKEN: "resolved-zalo-work-token", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.zalo?.accounts?.work?.botToken).toBe( "resolved-zalo-work-token", ); expect(snapshot.warnings.map((warning) => warning.path)).not.toContain( "channels.zalo.accounts.work.botToken", ); }); it("treats top-level Zalo botToken refs as active for non-default accounts without overrides", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { zalo: { botToken: { source: "env", provider: "default", id: "ZALO_TOP_LEVEL_TOKEN" }, accounts: { work: { enabled: true, }, }, }, }, }), env: { ZALO_TOP_LEVEL_TOKEN: "resolved-zalo-top-level-token", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.zalo?.botToken).toBe("resolved-zalo-top-level-token"); expect(snapshot.warnings.map((warning) => warning.path)).not.toContain( "channels.zalo.botToken", ); }); it("treats channels.zalo.accounts.default.botToken refs as active", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { zalo: { accounts: { default: { enabled: true, botToken: { source: "env", provider: "default", id: "ZALO_DEFAULT_TOKEN" }, }, }, }, }, }), env: { ZALO_DEFAULT_TOKEN: "resolved-zalo-default-token", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.zalo?.accounts?.default?.botToken).toBe( "resolved-zalo-default-token", ); expect(snapshot.warnings.map((warning) => warning.path)).not.toContain( "channels.zalo.accounts.default.botToken", ); }); it("treats top-level Nextcloud Talk botSecret and apiPassword refs as active when file paths are configured", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { "nextcloud-talk": { botSecret: { source: "env", provider: "default", id: "NEXTCLOUD_BOT_SECRET" }, botSecretFile: "/tmp/missing-nextcloud-bot-secret-file", apiUser: "bot-user", apiPassword: { source: "env", provider: "default", id: "NEXTCLOUD_API_PASSWORD" }, apiPasswordFile: "/tmp/missing-nextcloud-api-password-file", }, }, }), env: { NEXTCLOUD_BOT_SECRET: "resolved-nextcloud-bot-secret", NEXTCLOUD_API_PASSWORD: "resolved-nextcloud-api-password", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.["nextcloud-talk"]?.botSecret).toBe( "resolved-nextcloud-bot-secret", ); expect(snapshot.config.channels?.["nextcloud-talk"]?.apiPassword).toBe( "resolved-nextcloud-api-password", ); expect(snapshot.warnings.map((warning) => warning.path)).not.toContain( "channels.nextcloud-talk.botSecret", ); expect(snapshot.warnings.map((warning) => warning.path)).not.toContain( "channels.nextcloud-talk.apiPassword", ); }); it("treats account-level Nextcloud Talk botSecret and apiPassword refs as active when file paths are configured", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { "nextcloud-talk": { accounts: { work: { botSecret: { source: "env", provider: "default", id: "NEXTCLOUD_WORK_BOT_SECRET" }, botSecretFile: "/tmp/missing-nextcloud-work-bot-secret-file", apiPassword: { source: "env", provider: "default", id: "NEXTCLOUD_WORK_API_PASSWORD", }, apiPasswordFile: "/tmp/missing-nextcloud-work-api-password-file", }, }, }, }, }), env: { NEXTCLOUD_WORK_BOT_SECRET: "resolved-nextcloud-work-bot-secret", NEXTCLOUD_WORK_API_PASSWORD: "resolved-nextcloud-work-api-password", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.["nextcloud-talk"]?.accounts?.work?.botSecret).toBe( "resolved-nextcloud-work-bot-secret", ); expect(snapshot.config.channels?.["nextcloud-talk"]?.accounts?.work?.apiPassword).toBe( "resolved-nextcloud-work-api-password", ); expect(snapshot.warnings.map((warning) => warning.path)).not.toContain( "channels.nextcloud-talk.accounts.work.botSecret", ); expect(snapshot.warnings.map((warning) => warning.path)).not.toContain( "channels.nextcloud-talk.accounts.work.apiPassword", ); }); it("treats gateway.remote refs as active when tailscale serve is enabled", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ gateway: { mode: "local", tailscale: { mode: "serve" }, remote: { enabled: true, token: { source: "env", provider: "default", id: "REMOTE_GATEWAY_TOKEN" }, password: { source: "env", provider: "default", id: "REMOTE_GATEWAY_PASSWORD" }, }, }, }), env: { REMOTE_GATEWAY_TOKEN: "tailscale-remote-token", REMOTE_GATEWAY_PASSWORD: "tailscale-remote-password", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.gateway?.remote?.token).toBe("tailscale-remote-token"); expect(snapshot.config.gateway?.remote?.password).toBe("tailscale-remote-password"); expect(snapshot.warnings.map((warning) => warning.path)).not.toContain("gateway.remote.token"); expect(snapshot.warnings.map((warning) => warning.path)).not.toContain( "gateway.remote.password", ); }); it("treats defaults memorySearch ref as inactive when all enabled agents disable memorySearch", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ agents: { defaults: { memorySearch: { remote: { apiKey: { source: "env", provider: "default", id: "DEFAULT_MEMORY_REMOTE_API_KEY", }, }, }, }, list: [ { enabled: true, memorySearch: { enabled: false, }, }, ], }, }), env: {}, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.agents?.defaults?.memorySearch?.remote?.apiKey).toEqual({ source: "env", provider: "default", id: "DEFAULT_MEMORY_REMOTE_API_KEY", }); expect(snapshot.warnings.map((warning) => warning.path)).toContain( "agents.defaults.memorySearch.remote.apiKey", ); }); it("fails when enabled channel surfaces contain unresolved refs", async () => { await expect( prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { telegram: { botToken: { source: "env", provider: "default", id: "MISSING_ENABLED_TELEGRAM_TOKEN", }, accounts: { work: { enabled: true, }, }, }, }, }), env: {}, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }), ).rejects.toThrow('Environment variable "MISSING_ENABLED_TELEGRAM_TOKEN" is missing or empty.'); }); it("fails when default Telegram account can inherit an unresolved top-level token ref", async () => { await expect( prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { telegram: { botToken: { source: "env", provider: "default", id: "MISSING_ENABLED_TELEGRAM_TOKEN", }, accounts: { default: { enabled: true, }, }, }, }, }), env: {}, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }), ).rejects.toThrow('Environment variable "MISSING_ENABLED_TELEGRAM_TOKEN" is missing or empty.'); }); it("treats top-level Telegram token as inactive when all enabled accounts override it", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { telegram: { botToken: { source: "env", provider: "default", id: "UNUSED_TELEGRAM_BASE_TOKEN", }, accounts: { work: { enabled: true, botToken: { source: "env", provider: "default", id: "TELEGRAM_WORK_TOKEN", }, }, disabled: { enabled: false, }, }, }, }, }), env: { TELEGRAM_WORK_TOKEN: "telegram-work-token", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.telegram?.accounts?.work?.botToken).toBe( "telegram-work-token", ); expect(snapshot.config.channels?.telegram?.botToken).toEqual({ source: "env", provider: "default", id: "UNUSED_TELEGRAM_BASE_TOKEN", }); expect(snapshot.warnings.map((warning) => warning.path)).toContain( "channels.telegram.botToken", ); }); it("treats Telegram account overrides as enabled when account.enabled is omitted", async () => { await expect( prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { telegram: { enabled: true, accounts: { inheritedEnabled: { botToken: { source: "env", provider: "default", id: "MISSING_INHERITED_TELEGRAM_ACCOUNT_TOKEN", }, }, }, }, }, }), env: {}, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }), ).rejects.toThrow( 'Environment variable "MISSING_INHERITED_TELEGRAM_ACCOUNT_TOKEN" is missing or empty.', ); }); it("treats Telegram webhookSecret refs as inactive when webhook mode is not configured", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { telegram: { webhookSecret: { source: "env", provider: "default", id: "MISSING_TELEGRAM_WEBHOOK_SECRET", }, accounts: { work: { enabled: true, }, }, }, }, }), env: {}, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.telegram?.webhookSecret).toEqual({ source: "env", provider: "default", id: "MISSING_TELEGRAM_WEBHOOK_SECRET", }); expect(snapshot.warnings.map((warning) => warning.path)).toContain( "channels.telegram.webhookSecret", ); }); it("treats Telegram top-level botToken refs as inactive when tokenFile is configured", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { telegram: { tokenFile: "/tmp/telegram-bot-token", botToken: { source: "env", provider: "default", id: "MISSING_TELEGRAM_BOT_TOKEN", }, }, }, }), env: {}, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.telegram?.botToken).toEqual({ source: "env", provider: "default", id: "MISSING_TELEGRAM_BOT_TOKEN", }); expect(snapshot.warnings.map((warning) => warning.path)).toContain( "channels.telegram.botToken", ); }); it("treats Telegram account botToken refs as inactive when account tokenFile is configured", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { telegram: { accounts: { work: { enabled: true, tokenFile: "/tmp/telegram-work-bot-token", botToken: { source: "env", provider: "default", id: "MISSING_TELEGRAM_WORK_BOT_TOKEN", }, }, }, }, }, }), env: {}, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.telegram?.accounts?.work?.botToken).toEqual({ source: "env", provider: "default", id: "MISSING_TELEGRAM_WORK_BOT_TOKEN", }); expect(snapshot.warnings.map((warning) => warning.path)).toContain( "channels.telegram.accounts.work.botToken", ); }); it("treats top-level Telegram botToken refs as active when account botToken is blank", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { telegram: { botToken: { source: "env", provider: "default", id: "TELEGRAM_BASE_TOKEN", }, accounts: { work: { enabled: true, botToken: "", }, }, }, }, }), env: { TELEGRAM_BASE_TOKEN: "telegram-base-token", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.telegram?.botToken).toBe("telegram-base-token"); expect(snapshot.config.channels?.telegram?.accounts?.work?.botToken).toBe(""); expect(snapshot.warnings.map((warning) => warning.path)).not.toContain( "channels.telegram.botToken", ); }); it("treats IRC account nickserv password refs as inactive when nickserv is disabled", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { irc: { accounts: { work: { enabled: true, nickserv: { enabled: false, password: { source: "env", provider: "default", id: "MISSING_IRC_WORK_NICKSERV_PASSWORD", }, }, }, }, }, }, }), env: {}, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.irc?.accounts?.work?.nickserv?.password).toEqual({ source: "env", provider: "default", id: "MISSING_IRC_WORK_NICKSERV_PASSWORD", }); expect(snapshot.warnings.map((warning) => warning.path)).toContain( "channels.irc.accounts.work.nickserv.password", ); }); it("treats top-level IRC nickserv password refs as inactive when nickserv is disabled", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { irc: { nickserv: { enabled: false, password: { source: "env", provider: "default", id: "MISSING_IRC_TOPLEVEL_NICKSERV_PASSWORD", }, }, }, }, }), env: {}, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.irc?.nickserv?.password).toEqual({ source: "env", provider: "default", id: "MISSING_IRC_TOPLEVEL_NICKSERV_PASSWORD", }); expect(snapshot.warnings.map((warning) => warning.path)).toContain( "channels.irc.nickserv.password", ); }); it("treats Slack signingSecret refs as inactive when mode is socket", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { slack: { mode: "socket", signingSecret: { source: "env", provider: "default", id: "MISSING_SLACK_SIGNING_SECRET", }, accounts: { work: { enabled: true, mode: "socket", }, }, }, }, }), env: {}, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.slack?.signingSecret).toEqual({ source: "env", provider: "default", id: "MISSING_SLACK_SIGNING_SECRET", }); expect(snapshot.warnings.map((warning) => warning.path)).toContain( "channels.slack.signingSecret", ); }); it("treats Slack appToken refs as inactive when mode is http", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { slack: { mode: "http", appToken: { source: "env", provider: "default", id: "MISSING_SLACK_APP_TOKEN", }, accounts: { work: { enabled: true, mode: "http", appToken: { source: "env", provider: "default", id: "MISSING_SLACK_WORK_APP_TOKEN", }, }, }, }, }, }), env: {}, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.slack?.appToken).toEqual({ source: "env", provider: "default", id: "MISSING_SLACK_APP_TOKEN", }); expect(snapshot.config.channels?.slack?.accounts?.work?.appToken).toEqual({ source: "env", provider: "default", id: "MISSING_SLACK_WORK_APP_TOKEN", }); expect(snapshot.warnings.map((warning) => warning.path)).toEqual( expect.arrayContaining(["channels.slack.appToken", "channels.slack.accounts.work.appToken"]), ); }); it("treats top-level Google Chat serviceAccount as inactive when enabled accounts use serviceAccountRef", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { googlechat: { serviceAccount: { source: "env", provider: "default", id: "MISSING_GOOGLECHAT_BASE_SERVICE_ACCOUNT", }, accounts: { work: { enabled: true, serviceAccountRef: { source: "env", provider: "default", id: "GOOGLECHAT_WORK_SERVICE_ACCOUNT", }, }, }, }, }, }), env: { GOOGLECHAT_WORK_SERVICE_ACCOUNT: "work-service-account-json", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.googlechat?.serviceAccount).toEqual({ source: "env", provider: "default", id: "MISSING_GOOGLECHAT_BASE_SERVICE_ACCOUNT", }); expect(snapshot.config.channels?.googlechat?.accounts?.work?.serviceAccount).toBe( "work-service-account-json", ); expect(snapshot.warnings.map((warning) => warning.path)).toContain( "channels.googlechat.serviceAccount", ); }); it("fails when non-default Discord account inherits an unresolved top-level token ref", async () => { await expect( prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { discord: { token: { source: "env", provider: "default", id: "MISSING_DISCORD_BASE_TOKEN", }, accounts: { work: { enabled: true, }, }, }, }, }), env: {}, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }), ).rejects.toThrow('Environment variable "MISSING_DISCORD_BASE_TOKEN" is missing or empty.'); }); it("treats top-level Discord token refs as inactive when account token is explicitly blank", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { discord: { token: { source: "env", provider: "default", id: "MISSING_DISCORD_DEFAULT_TOKEN", }, accounts: { default: { enabled: true, token: "", }, }, }, }, }), env: {}, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.discord?.token).toEqual({ source: "env", provider: "default", id: "MISSING_DISCORD_DEFAULT_TOKEN", }); expect(snapshot.warnings.map((warning) => warning.path)).toContain("channels.discord.token"); }); it("treats Discord PluralKit token refs as inactive when PluralKit is disabled", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { discord: { pluralkit: { enabled: false, token: { source: "env", provider: "default", id: "MISSING_DISCORD_PLURALKIT_TOKEN", }, }, }, }, }), env: {}, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.discord?.pluralkit?.token).toEqual({ source: "env", provider: "default", id: "MISSING_DISCORD_PLURALKIT_TOKEN", }); expect(snapshot.warnings.map((warning) => warning.path)).toContain( "channels.discord.pluralkit.token", ); }); it("treats Discord voice TTS refs as inactive when voice is disabled", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { discord: { voice: { enabled: false, tts: { openai: { apiKey: { source: "env", provider: "default", id: "MISSING_DISCORD_VOICE_TTS_OPENAI", }, }, }, }, accounts: { work: { enabled: true, voice: { enabled: false, tts: { openai: { apiKey: { source: "env", provider: "default", id: "MISSING_DISCORD_WORK_VOICE_TTS_OPENAI", }, }, }, }, }, }, }, }, }), env: {}, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.discord?.voice?.tts?.openai?.apiKey).toEqual({ source: "env", provider: "default", id: "MISSING_DISCORD_VOICE_TTS_OPENAI", }); expect(snapshot.config.channels?.discord?.accounts?.work?.voice?.tts?.openai?.apiKey).toEqual({ source: "env", provider: "default", id: "MISSING_DISCORD_WORK_VOICE_TTS_OPENAI", }); expect(snapshot.warnings.map((warning) => warning.path)).toEqual( expect.arrayContaining([ "channels.discord.voice.tts.openai.apiKey", "channels.discord.accounts.work.voice.tts.openai.apiKey", ]), ); }); it("handles Discord nested inheritance for enabled and disabled accounts", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { discord: { voice: { tts: { openai: { apiKey: { source: "env", provider: "default", id: "DISCORD_BASE_TTS_OPENAI" }, }, }, }, pluralkit: { token: { source: "env", provider: "default", id: "DISCORD_BASE_PK_TOKEN" }, }, accounts: { enabledInherited: { enabled: true, }, enabledOverride: { enabled: true, voice: { tts: { openai: { apiKey: { source: "env", provider: "default", id: "DISCORD_ENABLED_OVERRIDE_TTS_OPENAI", }, }, }, }, }, disabledOverride: { enabled: false, voice: { tts: { openai: { apiKey: { source: "env", provider: "default", id: "DISCORD_DISABLED_OVERRIDE_TTS_OPENAI", }, }, }, }, pluralkit: { token: { source: "env", provider: "default", id: "DISCORD_DISABLED_OVERRIDE_PK_TOKEN", }, }, }, }, }, }, }), env: { DISCORD_BASE_TTS_OPENAI: "base-tts-openai", DISCORD_BASE_PK_TOKEN: "base-pk-token", DISCORD_ENABLED_OVERRIDE_TTS_OPENAI: "enabled-override-tts-openai", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect(snapshot.config.channels?.discord?.voice?.tts?.openai?.apiKey).toBe("base-tts-openai"); expect(snapshot.config.channels?.discord?.pluralkit?.token).toBe("base-pk-token"); expect( snapshot.config.channels?.discord?.accounts?.enabledOverride?.voice?.tts?.openai?.apiKey, ).toBe("enabled-override-tts-openai"); expect( snapshot.config.channels?.discord?.accounts?.disabledOverride?.voice?.tts?.openai?.apiKey, ).toEqual({ source: "env", provider: "default", id: "DISCORD_DISABLED_OVERRIDE_TTS_OPENAI", }); expect(snapshot.config.channels?.discord?.accounts?.disabledOverride?.pluralkit?.token).toEqual( { source: "env", provider: "default", id: "DISCORD_DISABLED_OVERRIDE_PK_TOKEN", }, ); expect(snapshot.warnings.map((warning) => warning.path)).toEqual( expect.arrayContaining([ "channels.discord.accounts.disabledOverride.voice.tts.openai.apiKey", "channels.discord.accounts.disabledOverride.pluralkit.token", ]), ); }); it("skips top-level Discord voice refs when all enabled accounts override nested voice config", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { discord: { voice: { tts: { openai: { apiKey: { source: "env", provider: "default", id: "DISCORD_UNUSED_BASE_TTS_OPENAI", }, }, }, }, accounts: { enabledOverride: { enabled: true, voice: { tts: { openai: { apiKey: { source: "env", provider: "default", id: "DISCORD_ENABLED_ONLY_TTS_OPENAI", }, }, }, }, }, disabledInherited: { enabled: false, }, }, }, }, }), env: { DISCORD_ENABLED_ONLY_TTS_OPENAI: "enabled-only-tts-openai", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }); expect( snapshot.config.channels?.discord?.accounts?.enabledOverride?.voice?.tts?.openai?.apiKey, ).toBe("enabled-only-tts-openai"); expect(snapshot.config.channels?.discord?.voice?.tts?.openai?.apiKey).toEqual({ source: "env", provider: "default", id: "DISCORD_UNUSED_BASE_TTS_OPENAI", }); expect(snapshot.warnings.map((warning) => warning.path)).toContain( "channels.discord.voice.tts.openai.apiKey", ); }); it("fails when an enabled Discord account override has an unresolved nested ref", async () => { await expect( prepareSecretsRuntimeSnapshot({ config: asConfig({ channels: { discord: { voice: { tts: { openai: { apiKey: { source: "env", provider: "default", id: "DISCORD_BASE_TTS_OK" }, }, }, }, accounts: { enabledOverride: { enabled: true, voice: { tts: { openai: { apiKey: { source: "env", provider: "default", id: "DISCORD_ENABLED_OVERRIDE_TTS_MISSING", }, }, }, }, }, }, }, }, }), env: { DISCORD_BASE_TTS_OK: "base-tts-openai", }, agentDirs: ["/tmp/openclaw-agent-main"], loadAuthStore: () => ({ version: 1, profiles: {} }), }), ).rejects.toThrow( 'Environment variable "DISCORD_ENABLED_OVERRIDE_TTS_MISSING" is missing or empty.', ); }); it("does not write inherited auth stores during runtime secret activation", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-secrets-runtime-")); const stateDir = path.join(root, ".openclaw"); const mainAgentDir = path.join(stateDir, "agents", "main", "agent"); const workerStorePath = path.join(stateDir, "agents", "worker", "agent", "auth-profiles.json"); const prevStateDir = process.env.OPENCLAW_STATE_DIR; try { await fs.mkdir(mainAgentDir, { recursive: true }); await fs.writeFile( path.join(mainAgentDir, "auth-profiles.json"), JSON.stringify({ ...loadAuthStoreWithProfiles({ "openai:default": { type: "api_key", provider: "openai", keyRef: OPENAI_ENV_KEY_REF, }, }), }), "utf8", ); process.env.OPENCLAW_STATE_DIR = stateDir; await prepareSecretsRuntimeSnapshot({ config: { agents: { list: [{ id: "worker" }], }, }, env: { OPENAI_API_KEY: "sk-runtime-worker" }, }); await expect(fs.access(workerStorePath)).rejects.toMatchObject({ code: "ENOENT" }); } finally { if (prevStateDir === undefined) { delete process.env.OPENCLAW_STATE_DIR; } else { process.env.OPENCLAW_STATE_DIR = prevStateDir; } await fs.rm(root, { recursive: true, force: true }); } }); });