diff --git a/src/agents/auth-profiles-watcher.ts b/src/agents/auth-profiles-watcher.ts index fdffa9dfff8..542cf503f3e 100644 --- a/src/agents/auth-profiles-watcher.ts +++ b/src/agents/auth-profiles-watcher.ts @@ -13,9 +13,14 @@ export type AuthProfilesWatcherHandle = { stop: () => Promise; }; +type WatcherLog = { + warn: (msg: string) => void; +}; + export function watchAuthProfilesForChanges(params: { cfg: OpenClawConfig; onChange: () => void; + log?: WatcherLog; }): AuthProfilesWatcherHandle { const watchPaths = listAgentIds(params.cfg).map((agentId) => path.join(resolveAgentDir(params.cfg, agentId), "auth-profiles.json"), @@ -25,6 +30,7 @@ export function watchAuthProfilesForChanges(params: { awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }, usePolling: Boolean(process.env.VITEST), }); + let closed = false; watcher.on("all", () => { try { params.onChange(); @@ -32,9 +38,18 @@ export function watchAuthProfilesForChanges(params: { // onChange errors must not crash the watcher. } }); + watcher.on("error", (err) => { + if (closed) { + return; + } + closed = true; + params.log?.warn(`auth-profile watcher error: ${String(err)}`); + void watcher.close().catch(() => {}); + }); return { stop: async () => { - await watcher.close(); + closed = true; + await watcher.close().catch(() => {}); }, }; } diff --git a/src/gateway/server-startup-post-attach.ts b/src/gateway/server-startup-post-attach.ts index e12d9c2919d..7b842da36e7 100644 --- a/src/gateway/server-startup-post-attach.ts +++ b/src/gateway/server-startup-post-attach.ts @@ -1019,7 +1019,7 @@ export async function startGatewayPostAttachRuntime( }); void sidecarsPromise - .then(async () => { + .then(async (sidecarsResult) => { if (params.minimalTestGateway) { return; } @@ -1027,10 +1027,34 @@ export async function startGatewayPostAttachRuntime( await import("../agents/model-provider-auth.js"); const { setAuthProfileFailureHook } = await import("../agents/auth-profiles.js"); const { watchAuthProfilesForChanges } = await import("../agents/auth-profiles-watcher.js"); - setAuthProfileFailureHook(() => clearCurrentProviderAuthState()); - watchAuthProfilesForChanges({ + const scheduleAuthMapRewarm = (reason: string) => { + const startMs = Date.now(); + void warmCurrentProviderAuthState(params.cfgAtStart) + .then(() => { + params.log.info( + `provider auth state re-warmed (${reason}) in ${Date.now() - startMs}ms`, + ); + }) + .catch((err) => { + params.log.warn(`provider auth state rewarm failed: ${String(err)}`); + }); + }; + setAuthProfileFailureHook(() => { + clearCurrentProviderAuthState(); + scheduleAuthMapRewarm("auth-profile-failure"); + }); + const authProfilesWatcher = watchAuthProfilesForChanges({ cfg: params.cfgAtStart, - onChange: () => clearCurrentProviderAuthState(), + onChange: () => { + clearCurrentProviderAuthState(); + scheduleAuthMapRewarm("auth-profiles.json change"); + }, + log: params.log, + }); + sidecarsResult.postReadySidecars.push({ + stop: () => { + void authProfilesWatcher.stop(); + }, }); const startMs = Date.now(); await warmCurrentProviderAuthState(params.cfgAtStart);