mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:20:43 +00:00
Support SecretRef for voice-call credentials and bundled plugin SecretInputs (#72607)
* fix: support voice-call secretrefs * test: classify plugin secretref targets * docs: credit voice-call secretref change * fix: keep plugin secret target discovery lightweight
This commit is contained in:
@@ -19,6 +19,7 @@ Docs: https://docs.openclaw.ai
|
||||
- CLI/startup: read generated startup metadata from the bundled `dist` layout before falling back to live help rendering, so root/browser help and channel-option bootstrap stay on the fast path. Thanks @vincentkoc.
|
||||
- CLI/help: treat positional `help` invocations like `openclaw channels help` as help paths for startup gating, avoiding model/auth warmup while preserving positional arguments such as `openclaw docs help`. Thanks @gumadeiras.
|
||||
- Web search: route plugin-scoped web_search SecretRefs through the active runtime config snapshot so provider execution receives resolved credentials across app/runtime paths, including `plugins.entries.brave.config.webSearch.apiKey`. Fixes #68690. Thanks @VACInc.
|
||||
- Voice Call: allow SecretRef-backed Twilio auth tokens and call-specific OpenAI/ElevenLabs TTS API keys through the plugin config surface. Fixes #68690. Thanks @joshavant.
|
||||
- Matrix/E2EE: stabilize recovery and broken-device QA flows while avoiding Matrix device-cleanup sync races that could leave shutdown-time crypto work running. Thanks @gumadeiras.
|
||||
- Cron: treat isolated run-level agent failures as job errors even when no reply payload is produced, synthesizing a safe error payload so model/provider failures increment error counters and trigger failure notifications instead of clearing as successful. Fixes #43604; carries forward #43631. Thanks @SPFAdvisors.
|
||||
- Cron: preserve exact `NO_REPLY` tool results from isolated jobs with empty final assistant turns as quiet successes instead of surfacing incomplete-turn errors. Fixes #68452; carries forward #68453. Thanks @anyech.
|
||||
|
||||
@@ -40,6 +40,7 @@ Scope intent:
|
||||
- `talk.providers.*.apiKey`
|
||||
- `messages.tts.providers.*.apiKey`
|
||||
- `tools.web.fetch.firecrawl.apiKey`
|
||||
- `plugins.entries.acpx.config.mcpServers.*.env.*`
|
||||
- `plugins.entries.brave.config.webSearch.apiKey`
|
||||
- `plugins.entries.exa.config.webSearch.apiKey`
|
||||
- `plugins.entries.google.config.webSearch.apiKey`
|
||||
@@ -49,6 +50,8 @@ Scope intent:
|
||||
- `plugins.entries.firecrawl.config.webSearch.apiKey`
|
||||
- `plugins.entries.minimax.config.webSearch.apiKey`
|
||||
- `plugins.entries.tavily.config.webSearch.apiKey`
|
||||
- `plugins.entries.voice-call.config.tts.providers.*.apiKey`
|
||||
- `plugins.entries.voice-call.config.twilio.authToken`
|
||||
- `tools.web.search.apiKey`
|
||||
- `gateway.auth.password`
|
||||
- `gateway.auth.token`
|
||||
|
||||
@@ -526,6 +526,13 @@
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "plugins.entries.acpx.config.mcpServers.*.env.*",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "plugins.entries.acpx.config.mcpServers.*.env.*",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "plugins.entries.brave.config.webSearch.apiKey",
|
||||
"configFile": "openclaw.json",
|
||||
@@ -582,6 +589,20 @@
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "plugins.entries.voice-call.config.tts.providers.*.apiKey",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "plugins.entries.voice-call.config.tts.providers.*.apiKey",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "plugins.entries.voice-call.config.twilio.authToken",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "plugins.entries.voice-call.config.twilio.authToken",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "plugins.entries.xai.config.webSearch.apiKey",
|
||||
"configFile": "openclaw.json",
|
||||
|
||||
@@ -203,7 +203,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"authToken": {
|
||||
"type": "string"
|
||||
"type": ["string", "object"]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -521,7 +521,7 @@
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"apiKey": {
|
||||
"type": "string"
|
||||
"type": ["string", "object"]
|
||||
},
|
||||
"baseUrl": {
|
||||
"type": "string"
|
||||
@@ -547,7 +547,7 @@
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"apiKey": {
|
||||
"type": "string"
|
||||
"type": ["string", "object"]
|
||||
},
|
||||
"baseUrl": {
|
||||
"type": "string"
|
||||
@@ -682,7 +682,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"apiKey": {
|
||||
"type": "string"
|
||||
"type": ["string", "object"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
@@ -718,6 +718,12 @@
|
||||
}
|
||||
},
|
||||
"configContracts": {
|
||||
"compatibilityMigrationPaths": ["plugins.entries.voice-call.config"]
|
||||
"compatibilityMigrationPaths": ["plugins.entries.voice-call.config"],
|
||||
"secretInputs": {
|
||||
"paths": [
|
||||
{ "path": "twilio.authToken", "expected": "string" },
|
||||
{ "path": "tts.providers.*.apiKey", "expected": "string" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,6 +477,38 @@ describe("config plugin validation", () => {
|
||||
expect(res.ok).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts voice-call SecretRef credentials declared by the plugin schema", async () => {
|
||||
const res = validateInSuite({
|
||||
agents: { list: [{ id: "pi" }] },
|
||||
plugins: {
|
||||
enabled: true,
|
||||
load: { paths: [voiceCallSchemaPluginDir] },
|
||||
entries: {
|
||||
"voice-call-schema-fixture": {
|
||||
config: {
|
||||
provider: "twilio",
|
||||
twilio: {
|
||||
accountSid: "twilio-account-sid-placeholder",
|
||||
authToken: { source: "env", provider: "default", id: "TWILIO_AUTH_TOKEN" },
|
||||
},
|
||||
tts: {
|
||||
providers: {
|
||||
openai: {
|
||||
apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
|
||||
},
|
||||
elevenlabs: {
|
||||
apiKey: { source: "env", provider: "default", id: "ELEVENLABS_API_KEY" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects out-of-range voice-call OpenAI TTS speed values", async () => {
|
||||
const res = validateInSuite({
|
||||
agents: { list: [{ id: "pi" }] },
|
||||
|
||||
@@ -26,6 +26,8 @@ vi.mock("./plugin-registry.js", () => ({
|
||||
|
||||
import { resolvePluginConfigContractsById } from "./config-contracts.js";
|
||||
|
||||
type PluginManifestRecord = PluginManifestRegistry["plugins"][number];
|
||||
|
||||
function createRegistry(plugins: PluginManifestRegistry["plugins"]): PluginManifestRegistry {
|
||||
return {
|
||||
plugins,
|
||||
@@ -33,6 +35,46 @@ function createRegistry(plugins: PluginManifestRegistry["plugins"]): PluginManif
|
||||
};
|
||||
}
|
||||
|
||||
function createPluginRecord(
|
||||
overrides: Pick<PluginManifestRecord, "id" | "origin"> & Partial<PluginManifestRecord>,
|
||||
): PluginManifestRecord {
|
||||
return {
|
||||
rootDir: `/tmp/${overrides.id}`,
|
||||
manifestPath: `/tmp/${overrides.id}/openclaw.plugin.json`,
|
||||
channelConfigs: undefined,
|
||||
providerAuthEnvVars: undefined,
|
||||
configUiHints: undefined,
|
||||
configSchema: undefined,
|
||||
configContracts: undefined,
|
||||
contracts: undefined,
|
||||
name: undefined,
|
||||
description: undefined,
|
||||
version: undefined,
|
||||
enabledByDefault: undefined,
|
||||
autoEnableWhenConfiguredProviders: undefined,
|
||||
legacyPluginIds: undefined,
|
||||
format: undefined,
|
||||
bundleFormat: undefined,
|
||||
bundleCapabilities: undefined,
|
||||
kind: undefined,
|
||||
channels: [],
|
||||
providers: [],
|
||||
modelSupport: undefined,
|
||||
cliBackends: [],
|
||||
channelEnvVars: undefined,
|
||||
providerAuthAliases: undefined,
|
||||
providerAuthChoices: undefined,
|
||||
skills: [],
|
||||
settingsFiles: undefined,
|
||||
hooks: [],
|
||||
source: `/tmp/${overrides.id}/openclaw.plugin.json`,
|
||||
setupSource: undefined,
|
||||
startupDeferConfiguredChannelFullLoadUntilAfterListen: undefined,
|
||||
channelCatalogMeta: undefined,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("resolvePluginConfigContractsById", () => {
|
||||
beforeEach(() => {
|
||||
mocks.findBundledPluginMetadataById.mockReset();
|
||||
@@ -45,42 +87,10 @@ describe("resolvePluginConfigContractsById", () => {
|
||||
it("does not fall back to bundled metadata when registry already resolved a plugin without config contracts", () => {
|
||||
mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue(
|
||||
createRegistry([
|
||||
{
|
||||
createPluginRecord({
|
||||
id: "brave",
|
||||
origin: "bundled",
|
||||
rootDir: "/tmp/brave",
|
||||
manifestPath: "/tmp/brave/openclaw.plugin.json",
|
||||
channelConfigs: undefined,
|
||||
providerAuthEnvVars: undefined,
|
||||
configUiHints: undefined,
|
||||
configSchema: undefined,
|
||||
configContracts: undefined,
|
||||
contracts: undefined,
|
||||
name: undefined,
|
||||
description: undefined,
|
||||
version: undefined,
|
||||
enabledByDefault: undefined,
|
||||
autoEnableWhenConfiguredProviders: undefined,
|
||||
legacyPluginIds: undefined,
|
||||
format: undefined,
|
||||
bundleFormat: undefined,
|
||||
bundleCapabilities: undefined,
|
||||
kind: undefined,
|
||||
channels: [],
|
||||
providers: [],
|
||||
modelSupport: undefined,
|
||||
cliBackends: [],
|
||||
channelEnvVars: undefined,
|
||||
providerAuthAliases: undefined,
|
||||
providerAuthChoices: undefined,
|
||||
skills: [],
|
||||
settingsFiles: undefined,
|
||||
hooks: [],
|
||||
source: "/tmp/brave/openclaw.plugin.json",
|
||||
setupSource: undefined,
|
||||
startupDeferConfiguredChannelFullLoadUntilAfterListen: undefined,
|
||||
channelCatalogMeta: undefined,
|
||||
},
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
@@ -92,6 +102,92 @@ describe("resolvePluginConfigContractsById", () => {
|
||||
expect(mocks.findBundledPluginMetadataById).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("can hydrate missing contracts from bundled metadata for resolved bundled plugins", () => {
|
||||
mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue(
|
||||
createRegistry([
|
||||
createPluginRecord({
|
||||
id: "voice-call",
|
||||
origin: "bundled",
|
||||
configContracts: {
|
||||
compatibilityMigrationPaths: ["plugins.entries.voice-call.config"],
|
||||
},
|
||||
}),
|
||||
]),
|
||||
);
|
||||
mocks.findBundledPluginMetadataById.mockReturnValue({
|
||||
manifest: {
|
||||
configContracts: {
|
||||
secretInputs: {
|
||||
paths: [{ path: "twilio.authToken", expected: "string" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
resolvePluginConfigContractsById({
|
||||
pluginIds: ["voice-call"],
|
||||
fallbackToBundledMetadataForResolvedBundled: true,
|
||||
}),
|
||||
).toEqual(
|
||||
new Map([
|
||||
[
|
||||
"voice-call",
|
||||
{
|
||||
origin: "bundled",
|
||||
configContracts: {
|
||||
compatibilityMigrationPaths: ["plugins.entries.voice-call.config"],
|
||||
secretInputs: {
|
||||
paths: [{ path: "twilio.authToken", expected: "string" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("can hydrate missing contracts for plugin ids known to be bundled by runtime discovery", () => {
|
||||
mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue(
|
||||
createRegistry([
|
||||
createPluginRecord({
|
||||
id: "voice-call",
|
||||
origin: "config",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
mocks.findBundledPluginMetadataById.mockReturnValue({
|
||||
manifest: {
|
||||
configContracts: {
|
||||
secretInputs: {
|
||||
paths: [{ path: "tts.providers.*.apiKey", expected: "string" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
resolvePluginConfigContractsById({
|
||||
pluginIds: ["voice-call"],
|
||||
fallbackBundledPluginIds: ["voice-call"],
|
||||
}),
|
||||
).toEqual(
|
||||
new Map([
|
||||
[
|
||||
"voice-call",
|
||||
{
|
||||
origin: "bundled",
|
||||
configContracts: {
|
||||
secretInputs: {
|
||||
paths: [{ path: "tts.providers.*.apiKey", expected: "string" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("can skip bundled metadata fallback for registry-scoped callers", () => {
|
||||
expect(
|
||||
resolvePluginConfigContractsById({
|
||||
|
||||
@@ -103,6 +103,8 @@ export function resolvePluginConfigContractsById(params: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
cache?: boolean;
|
||||
fallbackToBundledMetadata?: boolean;
|
||||
fallbackToBundledMetadataForResolvedBundled?: boolean;
|
||||
fallbackBundledPluginIds?: readonly string[];
|
||||
pluginIds: readonly string[];
|
||||
}): ReadonlyMap<string, PluginConfigContractMetadata> {
|
||||
const matches = new Map<string, PluginConfigContractMetadata>();
|
||||
@@ -112,8 +114,11 @@ export function resolvePluginConfigContractsById(params: {
|
||||
if (pluginIds.length === 0) {
|
||||
return matches;
|
||||
}
|
||||
const fallbackBundledPluginIds = new Set(
|
||||
(params.fallbackBundledPluginIds ?? []).map((pluginId) => pluginId.trim()).filter(Boolean),
|
||||
);
|
||||
|
||||
const resolvedPluginIds = new Set<string>();
|
||||
const resolvedPluginOrigins = new Map<string, PluginOrigin>();
|
||||
const registry = loadPluginManifestRegistryForPluginRegistry({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
@@ -125,7 +130,7 @@ export function resolvePluginConfigContractsById(params: {
|
||||
if (!pluginIds.includes(plugin.id)) {
|
||||
continue;
|
||||
}
|
||||
resolvedPluginIds.add(plugin.id);
|
||||
resolvedPluginOrigins.set(plugin.id, plugin.origin);
|
||||
if (!plugin.configContracts) {
|
||||
continue;
|
||||
}
|
||||
@@ -137,7 +142,35 @@ export function resolvePluginConfigContractsById(params: {
|
||||
|
||||
if (params.fallbackToBundledMetadata ?? true) {
|
||||
for (const pluginId of pluginIds) {
|
||||
if (matches.has(pluginId) || resolvedPluginIds.has(pluginId)) {
|
||||
const existing = matches.get(pluginId);
|
||||
const shouldHydrateBundledMatch =
|
||||
existing &&
|
||||
!existing.configContracts.secretInputs &&
|
||||
((params.fallbackToBundledMetadataForResolvedBundled && existing.origin === "bundled") ||
|
||||
fallbackBundledPluginIds.has(pluginId));
|
||||
if (shouldHydrateBundledMatch) {
|
||||
const bundled = findBundledPluginMetadataById(pluginId);
|
||||
if (bundled?.manifest.configContracts?.secretInputs) {
|
||||
matches.set(pluginId, {
|
||||
origin: fallbackBundledPluginIds.has(pluginId) ? "bundled" : existing.origin,
|
||||
configContracts: {
|
||||
...bundled.manifest.configContracts,
|
||||
...existing.configContracts,
|
||||
secretInputs: bundled.manifest.configContracts.secretInputs,
|
||||
},
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (matches.has(pluginId)) {
|
||||
continue;
|
||||
}
|
||||
const resolvedOrigin = resolvedPluginOrigins.get(pluginId);
|
||||
if (
|
||||
resolvedOrigin &&
|
||||
!(params.fallbackToBundledMetadataForResolvedBundled && resolvedOrigin === "bundled") &&
|
||||
!fallbackBundledPluginIds.has(pluginId)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const bundled = findBundledPluginMetadataById(pluginId);
|
||||
|
||||
@@ -125,6 +125,9 @@ describe("exec SecretRef id parity", () => {
|
||||
if (canonicalId.startsWith("tools.web.search.")) {
|
||||
return "tools.web.search";
|
||||
}
|
||||
if (canonicalId.startsWith("plugins.entries.")) {
|
||||
return "plugins.config";
|
||||
}
|
||||
return "unclassified";
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { findBundledPluginMetadataById } from "../plugins/bundled-plugin-metadata.js";
|
||||
import { resolvePluginConfigContractsById } from "../plugins/config-contracts.js";
|
||||
import { collectPluginConfigAssignments } from "./runtime-config-collectors-plugins.js";
|
||||
import { createResolverContext } from "./runtime-shared.js";
|
||||
|
||||
function envRef(id: string) {
|
||||
return { source: "env" as const, provider: "default", id };
|
||||
}
|
||||
|
||||
describe("collectPluginConfigAssignments bundled plugin manifests", () => {
|
||||
it("collects voice-call SecretRef assignments from bundled manifest contracts", () => {
|
||||
expect(
|
||||
findBundledPluginMetadataById("voice-call")?.manifest.configContracts?.secretInputs?.paths,
|
||||
).toEqual([
|
||||
{ path: "twilio.authToken", expected: "string" },
|
||||
{ path: "tts.providers.*.apiKey", expected: "string" },
|
||||
]);
|
||||
const config = {
|
||||
plugins: {
|
||||
entries: {
|
||||
"voice-call": {
|
||||
enabled: true,
|
||||
config: {
|
||||
twilio: {
|
||||
authToken: envRef("TWILIO_AUTH_TOKEN"),
|
||||
},
|
||||
tts: {
|
||||
providers: {
|
||||
openai: {
|
||||
apiKey: envRef("OPENAI_API_KEY"),
|
||||
},
|
||||
elevenlabs: {
|
||||
apiKey: envRef("ELEVENLABS_API_KEY"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
expect(
|
||||
resolvePluginConfigContractsById({
|
||||
config,
|
||||
workspaceDir: resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config)),
|
||||
env: {},
|
||||
cache: true,
|
||||
fallbackToBundledMetadata: true,
|
||||
fallbackToBundledMetadataForResolvedBundled: true,
|
||||
pluginIds: ["voice-call"],
|
||||
fallbackBundledPluginIds: ["voice-call"],
|
||||
}).get("voice-call")?.configContracts.secretInputs?.paths,
|
||||
).toEqual([
|
||||
{ path: "twilio.authToken", expected: "string" },
|
||||
{ path: "tts.providers.*.apiKey", expected: "string" },
|
||||
]);
|
||||
const context = createResolverContext({
|
||||
sourceConfig: config,
|
||||
env: {},
|
||||
});
|
||||
|
||||
collectPluginConfigAssignments({
|
||||
config,
|
||||
defaults: undefined,
|
||||
context,
|
||||
loadablePluginOrigins: new Map([["voice-call", "bundled"]]),
|
||||
});
|
||||
|
||||
expect({
|
||||
assignments: context.assignments.map((assignment) => assignment.path).toSorted(),
|
||||
warnings: context.warnings,
|
||||
}).toEqual({
|
||||
assignments: [
|
||||
"plugins.entries.voice-call.config.tts.providers.elevenlabs.apiKey",
|
||||
"plugins.entries.voice-call.config.tts.providers.openai.apiKey",
|
||||
"plugins.entries.voice-call.config.twilio.authToken",
|
||||
],
|
||||
warnings: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -40,6 +40,9 @@ export function collectPluginConfigAssignments(params: {
|
||||
params.config,
|
||||
resolveDefaultAgentId(params.config),
|
||||
);
|
||||
const bundledLoadablePluginIds = [...(params.loadablePluginOrigins?.entries() ?? [])]
|
||||
.filter(([, origin]) => origin === "bundled")
|
||||
.map(([pluginId]) => pluginId);
|
||||
const pluginSecretInputs = new Map(
|
||||
[
|
||||
...resolvePluginConfigContractsById({
|
||||
@@ -47,7 +50,9 @@ export function collectPluginConfigAssignments(params: {
|
||||
workspaceDir,
|
||||
env: params.context.env,
|
||||
cache: true,
|
||||
fallbackToBundledMetadata: false,
|
||||
fallbackToBundledMetadata: true,
|
||||
fallbackToBundledMetadataForResolvedBundled: true,
|
||||
fallbackBundledPluginIds: bundledLoadablePluginIds,
|
||||
pluginIds: Object.keys(entries),
|
||||
}).entries(),
|
||||
].flatMap(([pluginId, metadata]) => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { listBundledPluginMetadata } from "../plugins/bundled-plugin-metadata.js";
|
||||
import type { PluginManifestRecord } from "../plugins/manifest-registry.js";
|
||||
import { loadPluginManifestRegistryForPluginRegistry } from "../plugins/plugin-registry.js";
|
||||
import { loadBundledChannelSecretContractApi } from "./channel-contract-api.js";
|
||||
@@ -66,6 +67,30 @@ function listBundledWebProviderSecretTargetRegistryEntries(): SecretTargetRegist
|
||||
return entries.toSorted((left, right) => left.id.localeCompare(right.id));
|
||||
}
|
||||
|
||||
function listBundledPluginConfigSecretTargetRegistryEntries(): SecretTargetRegistryEntry[] {
|
||||
const entries: SecretTargetRegistryEntry[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const record of listBundledPluginMetadata({
|
||||
includeChannelConfigs: false,
|
||||
includeSyntheticChannelConfigs: false,
|
||||
})) {
|
||||
const secretInputs = record.manifest.configContracts?.secretInputs?.paths ?? [];
|
||||
for (const secretInput of secretInputs) {
|
||||
const entry = createPluginOpenClawConfigSecretTargetEntry(
|
||||
record.manifest.id,
|
||||
secretInput.path,
|
||||
);
|
||||
const key = `${entry.configFile}:${entry.pathPattern}`;
|
||||
if (seen.has(key)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(key);
|
||||
entries.push(entry);
|
||||
}
|
||||
}
|
||||
return entries.toSorted((left, right) => left.id.localeCompare(right.id));
|
||||
}
|
||||
|
||||
function listChannelSecretTargetRegistryEntries(): SecretTargetRegistryEntry[] {
|
||||
const entries: SecretTargetRegistryEntry[] = [];
|
||||
|
||||
@@ -436,6 +461,7 @@ export function getSecretTargetRegistry(): SecretTargetRegistryEntry[] {
|
||||
cachedSecretTargetRegistry = [
|
||||
...CORE_SECRET_TARGET_REGISTRY,
|
||||
...listBundledWebProviderSecretTargetRegistryEntries(),
|
||||
...listBundledPluginConfigSecretTargetRegistryEntries(),
|
||||
...listChannelSecretTargetRegistryEntries(),
|
||||
];
|
||||
return cachedSecretTargetRegistry;
|
||||
|
||||
@@ -72,4 +72,23 @@ describe("secret target registry", () => {
|
||||
expect(fetchTarget).not.toBeNull();
|
||||
expect(fetchTarget?.entry?.id).toBe("plugins.entries.firecrawl.config.webFetch.apiKey");
|
||||
});
|
||||
|
||||
it("derives bundled plugin SecretInput contract target paths from plugin manifests", () => {
|
||||
const coreTargetIds = new Set(getCoreSecretTargetRegistry().map((entry) => entry.id));
|
||||
expect(coreTargetIds.has("plugins.entries.voice-call.config.twilio.authToken")).toBe(false);
|
||||
|
||||
const target = resolveConfigSecretTargetByPath([
|
||||
"plugins",
|
||||
"entries",
|
||||
"voice-call",
|
||||
"config",
|
||||
"tts",
|
||||
"providers",
|
||||
"elevenlabs",
|
||||
"apiKey",
|
||||
]);
|
||||
|
||||
expect(target).not.toBeNull();
|
||||
expect(target?.entry?.id).toBe("plugins.entries.voice-call.config.tts.providers.*.apiKey");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user