



























@@ -5,6 +5,7 @@ import {
55resolveDefaultAgentDir,
66listAgentEntries,
77} from "../../../agents/agent-scope.js";
8+import { AUTH_STORE_LOCK_OPTIONS } from "../../../agents/auth-profiles/constants.js";
89import {
910areOAuthCredentialsEquivalent,
1011hasUsableOAuthCredential,
@@ -16,6 +17,7 @@ import { saveAuthProfileStore } from "../../../agents/auth-profiles/store.js";
1617import type { AuthProfileStore, OAuthCredential } from "../../../agents/auth-profiles/types.js";
1718import { resolveStateDir } from "../../../config/paths.js";
1819import type { OpenClawConfig } from "../../../config/types.openclaw.js";
20+import { withFileLock } from "../../../infra/file-lock.js";
1921import { shortenHomePath } from "../../../utils.js";
20222123type StaleOAuthProfileShadow = {
@@ -126,31 +128,84 @@ export async function scanStaleOAuthProfileShadows(params: {
126128return hits;
127129}
128130129-function removeProfilesFromStore(
130-store: AuthProfileStore,
131-profileIds: Set<string>,
132-): AuthProfileStore {
133-const profiles = { ...store.profiles };
134-const usageStats = store.usageStats ? { ...store.usageStats } : undefined;
135-for (const profileId of profileIds) {
131+function removeStaleProfilesFromStore(params: {
132+store: AuthProfileStore;
133+mainStore: AuthProfileStore;
134+profileIds: Set<string>;
135+now: number;
136+}): { store: AuthProfileStore; removedProfileIds: string[] } {
137+const removedProfileIds: string[] = [];
138+const profiles = { ...params.store.profiles };
139+const usageStats = params.store.usageStats ? { ...params.store.usageStats } : undefined;
140+for (const profileId of params.profileIds) {
141+const local = profiles[profileId];
142+const main = params.mainStore.profiles[profileId];
143+if (
144+local?.type !== "oauth" ||
145+!shouldRemoveLocalOAuthShadow({
146+ local,
147+main: main?.type === "oauth" ? main : undefined,
148+now: params.now,
149+})
150+) {
151+continue;
152+}
136153delete profiles[profileId];
137154if (usageStats) {
138155delete usageStats[profileId];
139156}
157+removedProfileIds.push(profileId);
140158}
141159return {
142- ...store,
143- profiles,
144- ...(usageStats && Object.keys(usageStats).length > 0
145- ? { usageStats }
146- : { usageStats: undefined }),
160+store: {
161+ ...params.store,
162+ profiles,
163+ ...(usageStats && Object.keys(usageStats).length > 0
164+ ? { usageStats }
165+ : { usageStats: undefined }),
166+},
167+ removedProfileIds,
147168};
148169}
149170150171function formatProfileList(profileIds: string[]): string {
151172return profileIds.length === 1 ? profileIds[0] : `${profileIds.length} profiles`;
152173}
153174175+async function repairStaleOAuthProfilesForAgent(params: {
176+agentDir: string;
177+mainStore: AuthProfileStore;
178+profileIds: Set<string>;
179+now: number;
180+}): Promise<
181+{ status: "changed"; removedProfileIds: string[] } | { status: "missing" | "unchanged" }
182+> {
183+return await withFileLock(
184+resolveAuthStorePath(params.agentDir),
185+AUTH_STORE_LOCK_OPTIONS,
186+async () => {
187+const store = loadPersistedAuthProfileStore(params.agentDir);
188+if (!store) {
189+return { status: "missing" };
190+}
191+const result = removeStaleProfilesFromStore({
192+ store,
193+mainStore: params.mainStore,
194+profileIds: params.profileIds,
195+now: params.now,
196+});
197+if (result.removedProfileIds.length === 0) {
198+return { status: "unchanged" };
199+}
200+saveAuthProfileStore(result.store, params.agentDir);
201+return {
202+status: "changed",
203+removedProfileIds: result.removedProfileIds,
204+};
205+},
206+);
207+}
208+154209export function collectStaleOAuthProfileShadowWarnings(params: {
155210hits: StaleOAuthProfileShadow[];
156211doctorFixCommand: string;
@@ -166,7 +221,9 @@ export async function repairStaleOAuthProfileShadows(params: {
166221env?: NodeJS.ProcessEnv;
167222now?: number;
168223}): Promise<{ changes: string[]; warnings: string[] }> {
169-const hits = await scanStaleOAuthProfileShadows(params);
224+const env = params.env ?? process.env;
225+const now = params.now ?? Date.now();
226+const hits = await scanStaleOAuthProfileShadows({ ...params, env, now });
170227const changes: string[] = [];
171228const warnings: string[] = [];
172229const byAgentDir = new Map<string, StaleOAuthProfileShadow[]>();
@@ -176,18 +233,25 @@ export async function repairStaleOAuthProfileShadows(params: {
176233byAgentDir.set(hit.agentDir, existing);
177234}
178235for (const [agentDir, agentHits] of byAgentDir) {
179-const store = loadPersistedAuthProfileStore(agentDir);
180-if (!store) {
236+const mainStore = loadPersistedAuthProfileStore(resolveDefaultAgentDir({}, env));
237+if (!mainStore) {
181238continue;
182239}
183240const profileIds = new Set(agentHits.map((hit) => hit.profileId));
184241try {
185-saveAuthProfileStore(removeProfilesFromStore(store, profileIds), agentDir);
186-changes.push(
187-`Removed stale OAuth auth profile shadow ${formatProfileList(
188- [...profileIds].toSorted(),
189- )} from ${shortenHomePath(resolveAuthStorePath(agentDir))}; this agent now inherits main auth.`,
190-);
242+const repair = await repairStaleOAuthProfilesForAgent({
243+ agentDir,
244+ mainStore,
245+ profileIds,
246+ now,
247+});
248+if (repair.status === "changed") {
249+changes.push(
250+`Removed stale OAuth auth profile shadow ${formatProfileList(
251+ repair.removedProfileIds.toSorted(),
252+ )} from ${shortenHomePath(resolveAuthStorePath(agentDir))}; this agent now inherits main auth.`,
253+);
254+}
191255} catch (error) {
192256warnings.push(
193257`Failed to remove stale OAuth auth profile shadow from ${shortenHomePath(
@@ -200,5 +264,7 @@ export async function repairStaleOAuthProfileShadows(params: {
200264}
201265202266export const __testing = {
267+ removeStaleProfilesFromStore,
268+ repairStaleOAuthProfilesForAgent,
203269 shouldRemoveLocalOAuthShadow,
204270};
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。