Skip to content

Commit ac00ba1

Browse files
committed
perf(commands): lazy-load agent secret resolution
1 parent f2e0829 commit ac00ba1

3 files changed

Lines changed: 96 additions & 11 deletions

File tree

src/agents/agent-command.ts

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
supportsXHighThinking,
1111
type VerboseLevel,
1212
} from "../auto-reply/thinking.js";
13-
import { resolveCommandConfigWithSecrets } from "../cli/command-config-resolution.js";
1413
import { formatCliCommand } from "../cli/command-format.js";
1514
import { getAgentRuntimeCommandSecretTargetIds } from "../cli/command-secret-targets.js";
1615
import { type CliDeps, createDefaultDeps } from "../cli/deps.js";
@@ -19,6 +18,7 @@ import { setRuntimeConfigSnapshot } from "../config/runtime-snapshot.js";
1918
import { resolveSessionTranscriptFile } from "../config/sessions/transcript.js";
2019
import type { SessionEntry } from "../config/sessions/types.js";
2120
import type { OpenClawConfig } from "../config/types.openclaw.js";
21+
import { isSecretRef } from "../config/types.secrets.js";
2222
import {
2323
clearAgentRunContext,
2424
emitAgentEvent,
@@ -148,18 +148,81 @@ async function resolveAgentRuntimeConfig(
148148
}
149149
return loadedRaw;
150150
})();
151-
const { resolvedConfig: cfg } = await resolveCommandConfigWithSecrets({
151+
const includeChannelTargets = params?.runtimeTargetsChannelSecrets === true;
152+
const cfg = hasAgentRuntimeSecretRefs({
152153
config: loadedRaw,
153-
commandName: "agent",
154-
targetIds: getAgentRuntimeCommandSecretTargetIds({
155-
includeChannelTargets: params?.runtimeTargetsChannelSecrets === true,
156-
}),
157-
runtime,
158-
});
154+
includeChannelTargets,
155+
})
156+
? (
157+
await (
158+
await import("../cli/command-config-resolution.runtime.js")
159+
).resolveCommandConfigWithSecrets({
160+
config: loadedRaw,
161+
commandName: "agent",
162+
targetIds: getAgentRuntimeCommandSecretTargetIds({
163+
includeChannelTargets,
164+
}),
165+
runtime,
166+
})
167+
).resolvedConfig
168+
: loadedRaw;
159169
setRuntimeConfigSnapshot(cfg, sourceConfig);
160170
return { loadedRaw, sourceConfig, cfg };
161171
}
162172

173+
function hasNestedSecretRef(value: unknown): boolean {
174+
if (isSecretRef(value)) {
175+
return true;
176+
}
177+
if (Array.isArray(value)) {
178+
return value.some((entry) => hasNestedSecretRef(entry));
179+
}
180+
if (!value || typeof value !== "object") {
181+
return false;
182+
}
183+
return Object.values(value).some((entry) => hasNestedSecretRef(entry));
184+
}
185+
186+
function hasAgentRuntimeSecretRefs(params: {
187+
config: OpenClawConfig;
188+
includeChannelTargets: boolean;
189+
}): boolean {
190+
const { config } = params;
191+
if (hasNestedSecretRef(config.models?.providers)) {
192+
return true;
193+
}
194+
if (hasNestedSecretRef(config.agents?.defaults?.memorySearch?.remote?.apiKey)) {
195+
return true;
196+
}
197+
if (
198+
Array.isArray(config.agents?.list) &&
199+
config.agents.list.some((agent) => hasNestedSecretRef(agent?.memorySearch?.remote?.apiKey))
200+
) {
201+
return true;
202+
}
203+
if (hasNestedSecretRef(config.messages?.tts?.providers)) {
204+
return true;
205+
}
206+
if (hasNestedSecretRef(config.skills?.entries)) {
207+
return true;
208+
}
209+
if (hasNestedSecretRef(config.tools?.web?.search)) {
210+
return true;
211+
}
212+
if (
213+
config.plugins?.entries &&
214+
Object.values(config.plugins.entries).some((entry) =>
215+
hasNestedSecretRef({
216+
webSearch: entry?.config?.webSearch,
217+
webFetch: entry?.config?.webFetch,
218+
}),
219+
)
220+
) {
221+
return true;
222+
}
223+
return params.includeChannelTargets ? hasNestedSecretRef(config.channels) : false;
224+
}
225+
163226
function containsControlCharacters(value: string): boolean {
164227
for (const char of value) {
165228
const code = char.codePointAt(0);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { resolveCommandConfigWithSecrets } from "./command-config-resolution.js";

src/commands/agent.runtime-config.test.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import "./agent-command.test-mocks.js";
44
import "../cron/isolated-agent.mocks.js";
55
import { __testing as agentCommandTesting } from "../agents/agent-command.js";
66
import { resolveSession } from "../agents/command/session.js";
7-
import * as commandConfigResolutionModule from "../cli/command-config-resolution.js";
7+
import * as commandConfigResolutionRuntimeModule from "../cli/command-config-resolution.runtime.js";
88
import * as configIoModule from "../config/io.js";
99
import * as runtimeSnapshotModule from "../config/runtime-snapshot.js";
1010
import type { OpenClawConfig } from "../config/types.openclaw.js";
@@ -102,7 +102,7 @@ describe("agentCommand runtime config", () => {
102102
writeOptions: {},
103103
} as Awaited<ReturnType<typeof configIoModule.readConfigFileSnapshotForWrite>>);
104104
const resolveConfigWithSecretsSpy = vi
105-
.spyOn(commandConfigResolutionModule, "resolveCommandConfigWithSecrets")
105+
.spyOn(commandConfigResolutionRuntimeModule, "resolveCommandConfigWithSecrets")
106106
.mockResolvedValueOnce({
107107
resolvedConfig,
108108
effectiveConfig: resolvedConfig,
@@ -131,8 +131,13 @@ describe("agentCommand runtime config", () => {
131131
await withTempHome(async (home) => {
132132
const store = path.join(home, "sessions.json");
133133
const loadedConfig = mockConfig(home, store);
134+
loadedConfig.channels = {
135+
telegram: {
136+
botToken: { source: "env", provider: "default", id: "TELEGRAM_BOT_TOKEN" },
137+
},
138+
} as OpenClawConfig["channels"];
134139
const resolveConfigWithSecretsSpy = vi
135-
.spyOn(commandConfigResolutionModule, "resolveCommandConfigWithSecrets")
140+
.spyOn(commandConfigResolutionRuntimeModule, "resolveCommandConfigWithSecrets")
136141
.mockResolvedValueOnce({
137142
resolvedConfig: loadedConfig,
138143
effectiveConfig: loadedConfig,
@@ -148,6 +153,22 @@ describe("agentCommand runtime config", () => {
148153
});
149154
});
150155

156+
it("skips command secret resolution when no relevant SecretRef values exist", async () => {
157+
await withTempHome(async (home) => {
158+
const store = path.join(home, "sessions.json");
159+
const loadedConfig = mockConfig(home, store);
160+
const resolveConfigWithSecretsSpy = vi.spyOn(
161+
commandConfigResolutionRuntimeModule,
162+
"resolveCommandConfigWithSecrets",
163+
);
164+
165+
const prepared = await agentCommandTesting.resolveAgentRuntimeConfig(runtime);
166+
167+
expect(resolveConfigWithSecretsSpy).not.toHaveBeenCalled();
168+
expect(prepared.cfg).toBe(loadedConfig);
169+
});
170+
});
171+
151172
it("derives a fresh session from --to", async () => {
152173
await withTempHome(async (home) => {
153174
const store = path.join(home, "sessions.json");

0 commit comments

Comments
 (0)