Files
openclaw/src/agents/agent-runtime-config.ts
Peter Steinberger a2a68d154b fix(agent): preserve explicit recipient sessions (#101507)
* fix(agent): honor recipient session routing

Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Co-authored-by: pingfanfan <pingfan.work@gmail.com>

* fix(agent): preserve canonical recipient routes

* fix(agent): require exact recipient routes

Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>

Co-authored-by: pingfanfan <pingfan.work@gmail.com>

* fix(agent): harden recipient route resolution

* fix(imessage): require canonical recipient handles

* fix(agent): refine provider recipient exactness

* fix(agent): bound direct alias session routing

* fix(signal): preserve direct alias route type

* fix(agent): honor binding-scoped recipient sessions

* fix(routing): honor configured main session aliases

* fix(clickclack): align account-owned session routes

* fix(twitch): preserve canonical recipient sessions

* fix(routing): isolate stable outbound identities

* chore: defer recipient session changelog

* fix(sms): use dedicated channel SDK facade

---------

Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Co-authored-by: pingfanfan <pingfan.work@gmail.com>
2026-07-07 10:07:13 +01:00

164 lines
5.3 KiB
TypeScript

/** Resolves agent runtime config, including SecretRef materialization for agent command use. */
import {
getAgentRuntimeCommandSecretTargetIds,
getScopedChannelsCommandSecretTargets,
} from "../cli/command-secret-targets.js";
import { getRuntimeConfig, readConfigFileSnapshotForWrite } from "../config/io.js";
import { setRuntimeConfigSnapshot } from "../config/runtime-snapshot.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { isSecretRef } from "../config/types.secrets.js";
import type { RuntimeEnv } from "../runtime.js";
import { discoverConfigSecretTargetsByIds } from "../secrets/target-registry.js";
/** Loads runtime/source config and resolves command SecretRefs when the agent path needs them. */
export async function resolveAgentRuntimeConfig(
runtime: RuntimeEnv,
params?: {
runtimeTargetsChannelSecrets?: boolean;
runtimeChannelSecretScope?: { channel: string; accountId?: string };
},
): Promise<{
loadedRaw: OpenClawConfig;
sourceConfig: OpenClawConfig;
cfg: OpenClawConfig;
}> {
const loadedRaw = getRuntimeConfig();
const includeChannelTargets = params?.runtimeTargetsChannelSecrets === true;
const channelSecretScope = params?.runtimeChannelSecretScope;
const hasRuntimeSecretRefs = hasAgentRuntimeSecretRefs({
config: loadedRaw,
includeChannelTargets,
channel: channelSecretScope?.channel,
});
const sourceConfig = await (async () => {
try {
const { snapshot } = await readConfigFileSnapshotForWrite();
if (snapshot.valid) {
return snapshot.resolved;
}
} catch {
// Fall back to runtime-loaded config when source snapshot is unavailable.
}
return loadedRaw;
})();
const cfg = hasRuntimeSecretRefs
? await (async () => {
const runtimeSecretTargets = resolveAgentRuntimeSecretTargets({
config: loadedRaw,
includeChannelTargets,
channelSecretScope,
});
return (
await (
await import("../cli/command-config-resolution.runtime.js")
).resolveCommandConfigWithSecrets({
config: loadedRaw,
commandName: "agent",
targetIds: runtimeSecretTargets.targetIds,
...(runtimeSecretTargets.allowedPaths
? { allowedPaths: runtimeSecretTargets.allowedPaths }
: {}),
runtime,
})
).resolvedConfig;
})()
: loadedRaw;
setRuntimeConfigSnapshot(cfg, sourceConfig);
return { loadedRaw, sourceConfig, cfg };
}
function hasNestedSecretRef(value: unknown): boolean {
if (isSecretRef(value)) {
return true;
}
if (Array.isArray(value)) {
return value.some((entry) => hasNestedSecretRef(entry));
}
if (!value || typeof value !== "object") {
return false;
}
return Object.values(value).some((entry) => hasNestedSecretRef(entry));
}
function hasAgentRuntimeSecretRefs(params: {
config: OpenClawConfig;
includeChannelTargets: boolean;
channel?: string;
}): boolean {
const { config } = params;
if (hasNestedSecretRef(config.models?.providers)) {
return true;
}
if (hasNestedSecretRef(config.agents?.defaults?.memorySearch?.remote?.apiKey)) {
return true;
}
if (
Array.isArray(config.agents?.list) &&
config.agents.list.some((agent) => hasNestedSecretRef(agent?.memorySearch?.remote?.apiKey))
) {
return true;
}
if (hasNestedSecretRef(config.messages?.tts?.providers)) {
return true;
}
if (hasNestedSecretRef(config.skills?.entries)) {
return true;
}
if (hasNestedSecretRef(config.tools?.web?.search)) {
return true;
}
if (
config.plugins?.entries &&
Object.values(config.plugins.entries).some((entry) =>
hasNestedSecretRef({
webSearch: entry?.config?.webSearch,
webFetch: entry?.config?.webFetch,
}),
)
) {
return true;
}
if (params.includeChannelTargets) {
return hasNestedSecretRef(config.channels);
}
if (!params.channel) {
return false;
}
return hasNestedSecretRef(
(config.channels as Record<string, unknown> | undefined)?.[params.channel],
);
}
function resolveAgentRuntimeSecretTargets(params: {
config: OpenClawConfig;
includeChannelTargets: boolean;
channelSecretScope?: { channel: string; accountId?: string };
}): { targetIds: Set<string>; allowedPaths?: Set<string> } {
const baseTargetIds = getAgentRuntimeCommandSecretTargetIds({
includeChannelTargets: params.includeChannelTargets,
});
if (params.includeChannelTargets || !params.channelSecretScope) {
return { targetIds: baseTargetIds };
}
const channelTargets = getScopedChannelsCommandSecretTargets({
config: params.config,
channel: params.channelSecretScope.channel,
accountId: params.channelSecretScope.accountId,
defaultAccountWhenMissing: true,
});
const targetIds = new Set(baseTargetIds);
for (const targetId of channelTargets.targetIds) {
targetIds.add(targetId);
}
if (!channelTargets.allowedPaths) {
return { targetIds };
}
// Account scoping must not exclude the agent's model/tool secrets from the same resolution.
const allowedPaths = new Set(channelTargets.allowedPaths);
for (const target of discoverConfigSecretTargetsByIds(params.config, baseTargetIds)) {
allowedPaths.add(target.path);
}
return { targetIds, allowedPaths };
}