UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

193 lines (192 loc) 8.39 kB
import { o as isRecord } from "./record-coerce-DHZ4bFlT.js"; import { y as resolveStateDir } from "./paths-mvMm5bYV.js"; import { g as shortenHomePath } from "./utils-CCC-BEJH.js"; import "./agent-scope-MrLta7Pq.js"; import { a as resolveAgentDir, s as resolveDefaultAgentDir, t as listAgentEntries } from "./agent-scope-config-CgCYpZfK.js"; import { l as resolveAuthStorePath } from "./runtime-snapshots-CGKcj2Tz.js"; import { E as hasUsableOAuthCredential, O as isSafeToAdoptMainStoreOAuthIdentity, m as updateAuthProfileStoreWithLock, v as loadPersistedAuthProfileStore, w as areOAuthCredentialsEquivalent, x as isLegacyOAuthRef } from "./store-C8spD0DG.js"; import path from "node:path"; import fs from "node:fs/promises"; //#region src/commands/doctor/shared/stale-oauth-profile-shadows.ts async function loadRawAuthProfileStore(authPath) { try { const raw = JSON.parse(await fs.readFile(authPath, "utf8")); return isRecord(raw) ? raw : null; } catch { return null; } } function hasLegacyOAuthSidecarRef(raw, profileId) { if (!raw || !isRecord(raw.profiles)) return false; const profile = raw.profiles[profileId]; if (!isRecord(profile)) return false; return profile.type === "oauth" && profile.provider === "openai-codex" && isLegacyOAuthRef(profile.oauthRef); } async function collectStateAgentDirs(env) { const agentsRoot = path.join(resolveStateDir(env), "agents"); return (await fs.readdir(agentsRoot, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory() || entry.isSymbolicLink()).map((entry) => path.join(agentsRoot, entry.name, "agent")); } async function collectCandidateAgentDirs(cfg, env) { const dirs = /* @__PURE__ */ new Set(); for (const entry of listAgentEntries(cfg)) { const id = entry.id?.trim(); if (id) dirs.add(path.resolve(resolveAgentDir(cfg, id, env))); } for (const agentDir of await collectStateAgentDirs(env)) dirs.add(path.resolve(agentDir)); return [...dirs].toSorted((left, right) => left.localeCompare(right)); } function shouldRemoveLocalOAuthShadow(params) { const { local, main, now } = params; if (!main || main.type !== "oauth" || local.provider !== main.provider) return false; if (!isSafeToAdoptMainStoreOAuthIdentity(local, main)) return false; if (areOAuthCredentialsEquivalent(local, main)) return true; if (!hasUsableOAuthCredential(main, now)) return false; if (!hasUsableOAuthCredential(local, now)) return true; const localExpires = Number.isFinite(local.expires) ? local.expires : 0; return (Number.isFinite(main.expires) ? main.expires : 0) >= localExpires; } /** Find local OAuth profiles that safely inherit fresher main-agent credentials instead. */ async function scanStaleOAuthProfileShadows(params) { const env = params.env ?? process.env; const now = params.now ?? Date.now(); const mainAgentDir = resolveDefaultAgentDir({}, env); const mainAuthPath = path.resolve(resolveAuthStorePath(mainAgentDir)); const mainStore = loadPersistedAuthProfileStore(mainAgentDir); if (!mainStore) return []; const hits = []; for (const agentDir of await collectCandidateAgentDirs(params.cfg, env)) { const authPath = path.resolve(resolveAuthStorePath(agentDir)); if (authPath === mainAuthPath) continue; const rawLocalStore = await loadRawAuthProfileStore(authPath); const localStore = loadPersistedAuthProfileStore(agentDir); if (!localStore) continue; for (const [profileId, local] of Object.entries(localStore.profiles)) { if (local.type !== "oauth") continue; if (hasLegacyOAuthSidecarRef(rawLocalStore, profileId)) continue; const main = mainStore.profiles[profileId]; if (shouldRemoveLocalOAuthShadow({ local, main: main?.type === "oauth" ? main : void 0, now })) hits.push({ agentDir, authPath, profileId }); } } return hits; } function removeStaleProfilesFromStore(params) { const removedProfileIds = []; const profiles = { ...params.store.profiles }; const usageStats = params.store.usageStats ? { ...params.store.usageStats } : void 0; const order = params.store.order ? { ...params.store.order } : void 0; const lastGood = params.store.lastGood ? { ...params.store.lastGood } : void 0; for (const profileId of params.profileIds) { const local = profiles[profileId]; const main = params.mainStore.profiles[profileId]; if (local?.type !== "oauth" || !shouldRemoveLocalOAuthShadow({ local, main: main?.type === "oauth" ? main : void 0, now: params.now })) continue; delete profiles[profileId]; if (usageStats) delete usageStats[profileId]; if (lastGood) { for (const [provider, lastGoodProfileId] of Object.entries(lastGood)) if (lastGoodProfileId === profileId) delete lastGood[provider]; } if (order) for (const [provider, profileIds] of Object.entries(order)) { const nextProfileIds = profileIds.filter((entry) => entry !== profileId); if (nextProfileIds.length > 0) order[provider] = nextProfileIds; else delete order[provider]; } removedProfileIds.push(profileId); } return { store: { ...params.store, profiles, ...usageStats && Object.keys(usageStats).length > 0 ? { usageStats } : { usageStats: void 0 }, ...lastGood && Object.keys(lastGood).length > 0 ? { lastGood } : { lastGood: void 0 }, ...order && Object.keys(order).length > 0 ? { order } : { order: void 0 } }, removedProfileIds }; } function formatProfileList(profileIds) { return profileIds.length === 1 ? profileIds[0] : `${profileIds.length} profiles`; } async function repairStaleOAuthProfilesForAgent(params) { const rawStore = await loadRawAuthProfileStore(resolveAuthStorePath(params.agentDir)); const profileIds = new Set([...params.profileIds].filter((profileId) => !hasLegacyOAuthSidecarRef(rawStore, profileId))); if (profileIds.size === 0) return { status: "unchanged" }; if (!loadPersistedAuthProfileStore(params.agentDir)) return { status: "missing" }; let sawStore = false; let removedProfileIds = []; await updateAuthProfileStoreWithLock({ agentDir: params.agentDir, updater: (store) => { sawStore = true; const result = removeStaleProfilesFromStore({ store, mainStore: params.mainStore, profileIds, now: params.now }); if (result.removedProfileIds.length === 0) return false; removedProfileIds = result.removedProfileIds; Object.assign(store, result.store); return true; } }); if (!sawStore) return { status: "missing" }; return removedProfileIds.length > 0 ? { status: "changed", removedProfileIds } : { status: "unchanged" }; } /** Format warnings for stale per-agent OAuth profile shadows. */ function collectStaleOAuthProfileShadowWarnings(params) { return params.hits.map((hit) => `- ${shortenHomePath(hit.authPath)} has stale OAuth auth profile ${hit.profileId}; it shadows the fresher main-agent credential. Run "${params.doctorFixCommand}" to remove the local shadow and inherit main auth.`); } /** Remove stale per-agent OAuth profile shadows after rechecking each locked store. */ async function repairStaleOAuthProfileShadows(params) { const env = params.env ?? process.env; const now = params.now ?? Date.now(); const hits = await scanStaleOAuthProfileShadows({ ...params, env, now }); const changes = []; const warnings = []; const byAgentDir = /* @__PURE__ */ new Map(); for (const hit of hits) { const existing = byAgentDir.get(hit.agentDir) ?? []; existing.push(hit); byAgentDir.set(hit.agentDir, existing); } for (const [agentDir, agentHits] of byAgentDir) { const mainStore = loadPersistedAuthProfileStore(resolveDefaultAgentDir({}, env)); if (!mainStore) continue; const profileIds = new Set(agentHits.map((hit) => hit.profileId)); try { const repair = await repairStaleOAuthProfilesForAgent({ agentDir, mainStore, profileIds, now }); if (repair.status === "changed") changes.push(`Removed stale OAuth auth profile shadow ${formatProfileList(repair.removedProfileIds.toSorted())} from ${shortenHomePath(resolveAuthStorePath(agentDir))}; this agent now inherits main auth.`); } catch (error) { warnings.push(`Failed to remove stale OAuth auth profile shadow from ${shortenHomePath(resolveAuthStorePath(agentDir))}: ${String(error)}`); } } return { changes, warnings }; } //#endregion export { repairStaleOAuthProfileShadows as n, scanStaleOAuthProfileShadows as r, collectStaleOAuthProfileShadowWarnings as t };