import { resolveOpenClawAgentDir } from "../agents/agent-paths.js"; import { listAgentIds, resolveAgentDir } from "../agents/agent-scope.js"; import type { AuthProfileStore } from "../agents/auth-profiles.js"; import { clearRuntimeAuthProfileStoreSnapshots, loadAuthProfileStoreForSecretsRuntime, replaceRuntimeAuthProfileStoreSnapshots, } from "../agents/auth-profiles.js"; import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshotRefreshHandler, setRuntimeConfigSnapshot, type OpenClawConfig, } from "../config/config.js"; import { resolveUserPath } from "../utils.js"; import { collectCommandSecretAssignmentsFromSnapshot, type CommandSecretAssignment, } from "./command-config.js"; import { resolveSecretRefValues } from "./resolve.js"; import { collectAuthStoreAssignments } from "./runtime-auth-collectors.js"; import { collectConfigAssignments } from "./runtime-config-collectors.js"; import { applyResolvedAssignments, createResolverContext, type SecretResolverWarning, } from "./runtime-shared.js"; import { resolveRuntimeWebTools, type RuntimeWebToolsMetadata } from "./runtime-web-tools.js"; export type { SecretResolverWarning } from "./runtime-shared.js"; export type PreparedSecretsRuntimeSnapshot = { sourceConfig: OpenClawConfig; config: OpenClawConfig; authStores: Array<{ agentDir: string; store: AuthProfileStore }>; warnings: SecretResolverWarning[]; webTools: RuntimeWebToolsMetadata; }; type SecretsRuntimeRefreshContext = { env: Record; explicitAgentDirs: string[] | null; loadAuthStore: (agentDir?: string) => AuthProfileStore; }; let activeSnapshot: PreparedSecretsRuntimeSnapshot | null = null; let activeRefreshContext: SecretsRuntimeRefreshContext | null = null; const preparedSnapshotRefreshContext = new WeakMap< PreparedSecretsRuntimeSnapshot, SecretsRuntimeRefreshContext >(); function cloneSnapshot(snapshot: PreparedSecretsRuntimeSnapshot): PreparedSecretsRuntimeSnapshot { return { sourceConfig: structuredClone(snapshot.sourceConfig), config: structuredClone(snapshot.config), authStores: snapshot.authStores.map((entry) => ({ agentDir: entry.agentDir, store: structuredClone(entry.store), })), warnings: snapshot.warnings.map((warning) => ({ ...warning })), webTools: structuredClone(snapshot.webTools), }; } function cloneRefreshContext(context: SecretsRuntimeRefreshContext): SecretsRuntimeRefreshContext { return { env: { ...context.env }, explicitAgentDirs: context.explicitAgentDirs ? [...context.explicitAgentDirs] : null, loadAuthStore: context.loadAuthStore, }; } function clearActiveSecretsRuntimeState(): void { activeSnapshot = null; activeRefreshContext = null; setRuntimeConfigSnapshotRefreshHandler(null); clearRuntimeConfigSnapshot(); clearRuntimeAuthProfileStoreSnapshots(); } function collectCandidateAgentDirs(config: OpenClawConfig): string[] { const dirs = new Set(); dirs.add(resolveUserPath(resolveOpenClawAgentDir())); for (const agentId of listAgentIds(config)) { dirs.add(resolveUserPath(resolveAgentDir(config, agentId))); } return [...dirs]; } function resolveRefreshAgentDirs( config: OpenClawConfig, context: SecretsRuntimeRefreshContext, ): string[] { const configDerived = collectCandidateAgentDirs(config); if (!context.explicitAgentDirs || context.explicitAgentDirs.length === 0) { return configDerived; } return [...new Set([...context.explicitAgentDirs, ...configDerived])]; } export async function prepareSecretsRuntimeSnapshot(params: { config: OpenClawConfig; env?: NodeJS.ProcessEnv; agentDirs?: string[]; loadAuthStore?: (agentDir?: string) => AuthProfileStore; }): Promise { const sourceConfig = structuredClone(params.config); const resolvedConfig = structuredClone(params.config); const context = createResolverContext({ sourceConfig, env: params.env ?? process.env, }); collectConfigAssignments({ config: resolvedConfig, context, }); const loadAuthStore = params.loadAuthStore ?? loadAuthProfileStoreForSecretsRuntime; const candidateDirs = params.agentDirs?.length ? [...new Set(params.agentDirs.map((entry) => resolveUserPath(entry)))] : collectCandidateAgentDirs(resolvedConfig); const authStores: Array<{ agentDir: string; store: AuthProfileStore }> = []; for (const agentDir of candidateDirs) { const store = structuredClone(loadAuthStore(agentDir)); collectAuthStoreAssignments({ store, context, agentDir, }); authStores.push({ agentDir, store }); } if (context.assignments.length > 0) { const refs = context.assignments.map((assignment) => assignment.ref); const resolved = await resolveSecretRefValues(refs, { config: sourceConfig, env: context.env, cache: context.cache, }); applyResolvedAssignments({ assignments: context.assignments, resolved, }); } const snapshot = { sourceConfig, config: resolvedConfig, authStores, warnings: context.warnings, webTools: await resolveRuntimeWebTools({ sourceConfig, resolvedConfig, context, }), }; preparedSnapshotRefreshContext.set(snapshot, { env: { ...(params.env ?? process.env) } as Record, explicitAgentDirs: params.agentDirs?.length ? [...candidateDirs] : null, loadAuthStore, }); return snapshot; } export function activateSecretsRuntimeSnapshot(snapshot: PreparedSecretsRuntimeSnapshot): void { const next = cloneSnapshot(snapshot); const refreshContext = preparedSnapshotRefreshContext.get(snapshot) ?? activeRefreshContext ?? ({ env: { ...process.env } as Record, explicitAgentDirs: null, loadAuthStore: loadAuthProfileStoreForSecretsRuntime, } satisfies SecretsRuntimeRefreshContext); setRuntimeConfigSnapshot(next.config, next.sourceConfig); replaceRuntimeAuthProfileStoreSnapshots(next.authStores); activeSnapshot = next; activeRefreshContext = cloneRefreshContext(refreshContext); setRuntimeConfigSnapshotRefreshHandler({ refresh: async ({ sourceConfig }) => { if (!activeSnapshot || !activeRefreshContext) { return false; } const refreshed = await prepareSecretsRuntimeSnapshot({ config: sourceConfig, env: activeRefreshContext.env, agentDirs: resolveRefreshAgentDirs(sourceConfig, activeRefreshContext), loadAuthStore: activeRefreshContext.loadAuthStore, }); activateSecretsRuntimeSnapshot(refreshed); return true; }, }); } export function getActiveSecretsRuntimeSnapshot(): PreparedSecretsRuntimeSnapshot | null { if (!activeSnapshot) { return null; } const snapshot = cloneSnapshot(activeSnapshot); if (activeRefreshContext) { preparedSnapshotRefreshContext.set(snapshot, cloneRefreshContext(activeRefreshContext)); } return snapshot; } export function getActiveRuntimeWebToolsMetadata(): RuntimeWebToolsMetadata | null { if (!activeSnapshot) { return null; } return structuredClone(activeSnapshot.webTools); } export function resolveCommandSecretsFromActiveRuntimeSnapshot(params: { commandName: string; targetIds: ReadonlySet; }): { assignments: CommandSecretAssignment[]; diagnostics: string[]; inactiveRefPaths: string[] } { if (!activeSnapshot) { throw new Error("Secrets runtime snapshot is not active."); } if (params.targetIds.size === 0) { return { assignments: [], diagnostics: [], inactiveRefPaths: [] }; } const inactiveRefPaths = [ ...new Set( activeSnapshot.warnings .filter((warning) => warning.code === "SECRETS_REF_IGNORED_INACTIVE_SURFACE") .map((warning) => warning.path), ), ]; const resolved = collectCommandSecretAssignmentsFromSnapshot({ sourceConfig: activeSnapshot.sourceConfig, resolvedConfig: activeSnapshot.config, commandName: params.commandName, targetIds: params.targetIds, inactiveRefPaths: new Set(inactiveRefPaths), }); return { assignments: resolved.assignments, diagnostics: resolved.diagnostics, inactiveRefPaths, }; } export function clearSecretsRuntimeSnapshot(): void { clearActiveSecretsRuntimeState(); }