mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-03 13:30:22 +00:00
fix: defer command secret target registry loading
This commit is contained in:
24
src/cli/command-secret-targets.import.test.ts
Normal file
24
src/cli/command-secret-targets.import.test.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("command secret targets module import", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
it("does not touch the registry during module import", async () => {
|
||||
const listSecretTargetRegistryEntries = vi.fn(() => {
|
||||
throw new Error("registry touched too early");
|
||||
});
|
||||
|
||||
vi.doMock("../secrets/target-registry.js", () => ({
|
||||
discoverConfigSecretTargetsByIds: vi.fn(() => []),
|
||||
listSecretTargetRegistryEntries,
|
||||
}));
|
||||
|
||||
const mod = await import("./command-secret-targets.js");
|
||||
|
||||
expect(listSecretTargetRegistryEntries).not.toHaveBeenCalled();
|
||||
expect(() => mod.getChannelsCommandSecretTargetIds()).toThrow("registry touched too early");
|
||||
expect(listSecretTargetRegistryEntries).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,56 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
const REGISTRY_IDS = [
|
||||
"agents.defaults.memorySearch.remote.apiKey",
|
||||
"agents.list[].memorySearch.remote.apiKey",
|
||||
"channels.discord.token",
|
||||
"channels.discord.accounts.ops.token",
|
||||
"channels.discord.accounts.chat.token",
|
||||
"channels.telegram.botToken",
|
||||
"gateway.auth.token",
|
||||
"gateway.auth.password",
|
||||
"gateway.remote.token",
|
||||
"gateway.remote.password",
|
||||
"models.providers.openai.apiKey",
|
||||
"messages.tts.providers.openai.apiKey",
|
||||
"plugins.entries.firecrawl.config.webFetch.apiKey",
|
||||
"skills.entries.demo.apiKey",
|
||||
"tools.web.search.apiKey",
|
||||
] as const;
|
||||
|
||||
vi.mock("../secrets/target-registry.js", () => ({
|
||||
listSecretTargetRegistryEntries: vi.fn(() =>
|
||||
REGISTRY_IDS.map((id) => ({
|
||||
id,
|
||||
})),
|
||||
),
|
||||
discoverConfigSecretTargetsByIds: vi.fn((config: unknown, targetIds?: Iterable<string>) => {
|
||||
const allowed = targetIds ? new Set(targetIds) : null;
|
||||
const out: Array<{ path: string; pathSegments: string[] }> = [];
|
||||
const record = (path: string) => {
|
||||
if (allowed && !allowed.has(path)) {
|
||||
return;
|
||||
}
|
||||
out.push({ path, pathSegments: path.split(".") });
|
||||
};
|
||||
|
||||
const channels = (config as { channels?: Record<string, unknown> } | undefined)?.channels;
|
||||
const discord = channels?.discord as
|
||||
| { token?: unknown; accounts?: Record<string, { token?: unknown }> }
|
||||
| undefined;
|
||||
|
||||
if (discord?.token !== undefined) {
|
||||
record("channels.discord.token");
|
||||
}
|
||||
for (const [accountId, account] of Object.entries(discord?.accounts ?? {})) {
|
||||
if (account?.token !== undefined) {
|
||||
record(`channels.discord.accounts.${accountId}.token`);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}),
|
||||
}));
|
||||
|
||||
import {
|
||||
getAgentRuntimeCommandSecretTargetIds,
|
||||
getScopedChannelsCommandSecretTargets,
|
||||
|
||||
@@ -19,30 +19,48 @@ function idsByPredicate(predicate: (id: string) => boolean): string[] {
|
||||
.toSorted();
|
||||
}
|
||||
|
||||
const WEB_PLUGIN_SECRET_TARGETS = idsByPredicate((id) =>
|
||||
/^plugins\.entries\.[^.]+\.config\.(webSearch|webFetch)\.apiKey$/.test(id),
|
||||
);
|
||||
type CommandSecretTargets = {
|
||||
qrRemote: string[];
|
||||
channels: string[];
|
||||
models: string[];
|
||||
agentRuntime: string[];
|
||||
status: string[];
|
||||
securityAudit: string[];
|
||||
};
|
||||
|
||||
const COMMAND_SECRET_TARGETS = {
|
||||
qrRemote: ["gateway.remote.token", "gateway.remote.password"],
|
||||
channels: idsByPrefix(["channels."]),
|
||||
models: idsByPrefix(["models.providers."]),
|
||||
agentRuntime: idsByPrefix([
|
||||
"channels.",
|
||||
"models.providers.",
|
||||
"agents.defaults.memorySearch.remote.",
|
||||
"agents.list[].memorySearch.remote.",
|
||||
"skills.entries.",
|
||||
"messages.tts.",
|
||||
"tools.web.search",
|
||||
]).concat(WEB_PLUGIN_SECRET_TARGETS),
|
||||
status: idsByPrefix([
|
||||
"channels.",
|
||||
"agents.defaults.memorySearch.remote.",
|
||||
"agents.list[].memorySearch.remote.",
|
||||
]),
|
||||
securityAudit: idsByPrefix(["channels.", "gateway.auth.", "gateway.remote."]),
|
||||
} as const;
|
||||
let cachedCommandSecretTargets: CommandSecretTargets | undefined;
|
||||
|
||||
function buildCommandSecretTargets(): CommandSecretTargets {
|
||||
const webPluginSecretTargets = idsByPredicate((id) =>
|
||||
/^plugins\.entries\.[^.]+\.config\.(webSearch|webFetch)\.apiKey$/.test(id),
|
||||
);
|
||||
|
||||
return {
|
||||
qrRemote: ["gateway.remote.token", "gateway.remote.password"],
|
||||
channels: idsByPrefix(["channels."]),
|
||||
models: idsByPrefix(["models.providers."]),
|
||||
agentRuntime: idsByPrefix([
|
||||
"channels.",
|
||||
"models.providers.",
|
||||
"agents.defaults.memorySearch.remote.",
|
||||
"agents.list[].memorySearch.remote.",
|
||||
"skills.entries.",
|
||||
"messages.tts.",
|
||||
"tools.web.search",
|
||||
]).concat(webPluginSecretTargets),
|
||||
status: idsByPrefix([
|
||||
"channels.",
|
||||
"agents.defaults.memorySearch.remote.",
|
||||
"agents.list[].memorySearch.remote.",
|
||||
]),
|
||||
securityAudit: idsByPrefix(["channels.", "gateway.auth.", "gateway.remote."]),
|
||||
};
|
||||
}
|
||||
|
||||
function getCommandSecretTargets(): CommandSecretTargets {
|
||||
cachedCommandSecretTargets ??= buildCommandSecretTargets();
|
||||
return cachedCommandSecretTargets;
|
||||
}
|
||||
|
||||
function toTargetIdSet(values: readonly string[]): Set<string> {
|
||||
return new Set(values);
|
||||
@@ -54,11 +72,12 @@ function normalizeScopedChannelId(value?: string | null): string | undefined {
|
||||
}
|
||||
|
||||
function selectChannelTargetIds(channel?: string): Set<string> {
|
||||
const commandSecretTargets = getCommandSecretTargets();
|
||||
if (!channel) {
|
||||
return toTargetIdSet(COMMAND_SECRET_TARGETS.channels);
|
||||
return toTargetIdSet(commandSecretTargets.channels);
|
||||
}
|
||||
return toTargetIdSet(
|
||||
COMMAND_SECRET_TARGETS.channels.filter((id) => id.startsWith(`channels.${channel}.`)),
|
||||
commandSecretTargets.channels.filter((id) => id.startsWith(`channels.${channel}.`)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -108,25 +127,25 @@ export function getScopedChannelsCommandSecretTargets(params: {
|
||||
}
|
||||
|
||||
export function getQrRemoteCommandSecretTargetIds(): Set<string> {
|
||||
return toTargetIdSet(COMMAND_SECRET_TARGETS.qrRemote);
|
||||
return toTargetIdSet(getCommandSecretTargets().qrRemote);
|
||||
}
|
||||
|
||||
export function getChannelsCommandSecretTargetIds(): Set<string> {
|
||||
return toTargetIdSet(COMMAND_SECRET_TARGETS.channels);
|
||||
return toTargetIdSet(getCommandSecretTargets().channels);
|
||||
}
|
||||
|
||||
export function getModelsCommandSecretTargetIds(): Set<string> {
|
||||
return toTargetIdSet(COMMAND_SECRET_TARGETS.models);
|
||||
return toTargetIdSet(getCommandSecretTargets().models);
|
||||
}
|
||||
|
||||
export function getAgentRuntimeCommandSecretTargetIds(): Set<string> {
|
||||
return toTargetIdSet(COMMAND_SECRET_TARGETS.agentRuntime);
|
||||
return toTargetIdSet(getCommandSecretTargets().agentRuntime);
|
||||
}
|
||||
|
||||
export function getStatusCommandSecretTargetIds(): Set<string> {
|
||||
return toTargetIdSet(COMMAND_SECRET_TARGETS.status);
|
||||
return toTargetIdSet(getCommandSecretTargets().status);
|
||||
}
|
||||
|
||||
export function getSecurityAuditCommandSecretTargetIds(): Set<string> {
|
||||
return toTargetIdSet(COMMAND_SECRET_TARGETS.securityAudit);
|
||||
return toTargetIdSet(getCommandSecretTargets().securityAudit);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user