| 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<string, string | undefined>; |
| 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<string>(); |
| 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<PreparedSecretsRuntimeSnapshot> { |
| 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<string, string | undefined>, |
| 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<string, string | undefined>, |
| 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<string>; |
| }): { 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(); |
| } |
|
|