





















@@ -40,6 +40,10 @@ const hoisted = vi.hoisted(() => ({
4040activeEmbeddedRunSessionKeys: [] as string[],
4141markRestartAbortedMainSessions: vi.fn(async (_params: unknown) => ({ marked: 1, skipped: 0 })),
4242runtimeConfig: { value: { session: { store: "/tmp/active-sessions.json" } } as OpenClawConfig },
43+reloadEvents: [] as string[],
44+resetModelCatalogCache: vi.fn(() => {}),
45+clearCurrentProviderAuthState: vi.fn(() => {}),
46+warmCurrentProviderAuthState: vi.fn(async (_cfg: OpenClawConfig) => {}),
4347}));
44484549vi.mock("../hooks/gmail-watcher.js", () => ({
@@ -95,6 +99,24 @@ vi.mock("../config/config.js", () => ({
9599getRuntimeConfig: () => hoisted.runtimeConfig.value,
96100}));
97101102+vi.mock("../agents/model-catalog.js", () => ({
103+resetModelCatalogCache: () => {
104+hoisted.reloadEvents.push("reset-model-catalog");
105+hoisted.resetModelCatalogCache();
106+},
107+}));
108+109+vi.mock("../agents/model-provider-auth.js", () => ({
110+clearCurrentProviderAuthState: () => {
111+hoisted.reloadEvents.push("clear-provider-auth");
112+hoisted.clearCurrentProviderAuthState();
113+},
114+warmCurrentProviderAuthState: async (cfg: OpenClawConfig) => {
115+hoisted.reloadEvents.push("warm-provider-auth");
116+await hoisted.warmCurrentProviderAuthState(cfg);
117+},
118+}));
119+98120function createReloadHandlersForTest(logReload = { info: vi.fn(), warn: vi.fn() }) {
99121const cron = { start: vi.fn(async () => {}), stop: vi.fn() };
100122const heartbeatRunner = {
@@ -139,6 +161,76 @@ afterEach(() => {
139161hoisted.activeEmbeddedRunSessionKeys.length = 0;
140162hoisted.markRestartAbortedMainSessions.mockClear();
141163hoisted.runtimeConfig.value = { session: { store: "/tmp/active-sessions.json" } };
164+hoisted.reloadEvents.length = 0;
165+hoisted.resetModelCatalogCache.mockClear();
166+hoisted.clearCurrentProviderAuthState.mockClear();
167+hoisted.warmCurrentProviderAuthState.mockClear();
168+});
169+170+describe("gateway hot reload model state", () => {
171+it("resets prepared model runtime state for every hot reload and rewarms after plugin reload", async () => {
172+const reloadPlugins = vi.fn(async (): Promise<GatewayPluginReloadResult> => {
173+hoisted.reloadEvents.push("reload-plugins");
174+return {
175+restartChannels: new Set(),
176+activeChannels: new Set(),
177+};
178+});
179+const { applyHotReload } = createGatewayReloadHandlers({
180+deps: {} as never,
181+broadcast: vi.fn(),
182+getState: () => ({
183+hooksConfig: {} as never,
184+hookClientIpConfig: {} as never,
185+heartbeatRunner: { stop: vi.fn(), updateConfig: vi.fn() } as never,
186+cronState: {
187+cron: { start: vi.fn(async () => {}), stop: vi.fn() },
188+storePath: "/tmp/cron.json",
189+cronEnabled: false,
190+} as never,
191+channelHealthMonitor: null,
192+}),
193+setState: vi.fn(),
194+startChannel: vi.fn(async () => {}),
195+stopChannel: vi.fn(async () => {}),
196+ reloadPlugins,
197+logHooks: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
198+logChannels: { info: vi.fn(), error: vi.fn() },
199+logCron: { error: vi.fn() },
200+logReload: { info: vi.fn(), warn: vi.fn() },
201+createHealthMonitor: () => null,
202+});
203+204+const nextConfig = { plugins: { enabled: true } } as OpenClawConfig;
205+await applyHotReload(
206+{
207+changedPaths: ["plugins.enabled"],
208+restartGateway: false,
209+restartReasons: [],
210+hotReasons: ["plugins.enabled"],
211+reloadHooks: false,
212+restartGmailWatcher: false,
213+restartCron: false,
214+restartHeartbeat: false,
215+restartHealthMonitor: false,
216+reloadPlugins: true,
217+restartChannels: new Set(),
218+disposeMcpRuntimes: false,
219+noopPaths: [],
220+},
221+nextConfig,
222+);
223+224+expect(hoisted.reloadEvents).toEqual([
225+"reset-model-catalog",
226+"clear-provider-auth",
227+"reload-plugins",
228+"reset-model-catalog",
229+"clear-provider-auth",
230+"warm-provider-auth",
231+]);
232+expect(hoisted.warmCurrentProviderAuthState).toHaveBeenCalledWith(nextConfig);
233+});
142234});
143235144236describe("gateway restart deferral preflight", () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。