






























@@ -50,6 +50,9 @@ const hoisted = vi.hoisted(() => {
5050}));
5151const resolveEmbeddedAgentRuntime = vi.fn(() => "pi");
5252const ensureOpenClawModelsJson = vi.fn(async () => {});
53+const clearCurrentProviderAuthState = vi.fn();
54+const warmCurrentProviderAuthState = vi.fn(async (_cfg?: unknown, _options?: unknown) => {});
55+const setAuthProfileFailureHook = vi.fn();
5356return {
5457 startPluginServices,
5558 startGmailWatcherWithLogs,
@@ -79,6 +82,9 @@ const hoisted = vi.hoisted(() => {
7982 getModelRefStatus,
8083 resolveEmbeddedAgentRuntime,
8184 ensureOpenClawModelsJson,
85+ clearCurrentProviderAuthState,
86+ warmCurrentProviderAuthState,
87+ setAuthProfileFailureHook,
8288};
8389});
8490@@ -194,6 +200,21 @@ vi.mock("../agents/models-config.js", () => ({
194200ensureOpenClawModelsJson: hoisted.ensureOpenClawModelsJson,
195201}));
196202203+vi.mock("../agents/model-provider-auth.js", () => ({
204+clearCurrentProviderAuthState: hoisted.clearCurrentProviderAuthState,
205+warmCurrentProviderAuthState: hoisted.warmCurrentProviderAuthState,
206+}));
207+208+vi.mock("../agents/auth-profiles.js", async () => {
209+const actual = await vi.importActual<typeof import("../agents/auth-profiles.js")>(
210+"../agents/auth-profiles.js",
211+);
212+return {
213+ ...actual,
214+setAuthProfileFailureHook: hoisted.setAuthProfileFailureHook,
215+};
216+});
217+197218vi.mock("./server-tailscale.js", () => ({
198219startGatewayTailscaleExposure: hoisted.startGatewayTailscaleExposure,
199220}));
@@ -324,6 +345,10 @@ describe("startGatewayPostAttachRuntime", () => {
324345hoisted.resolveEmbeddedAgentRuntime.mockReturnValue("pi");
325346hoisted.ensureOpenClawModelsJson.mockReset();
326347hoisted.ensureOpenClawModelsJson.mockResolvedValue(undefined);
348+hoisted.clearCurrentProviderAuthState.mockClear();
349+hoisted.warmCurrentProviderAuthState.mockReset();
350+hoisted.warmCurrentProviderAuthState.mockResolvedValue(undefined);
351+hoisted.setAuthProfileFailureHook.mockClear();
327352});
328353329354afterEach(() => {
@@ -856,6 +881,179 @@ describe("startGatewayPostAttachRuntime", () => {
856881}
857882});
858883884+it("delays provider auth prewarm so post-ready gateway work can run first", async () => {
885+vi.useFakeTimers();
886+const postReadyRequestTurn = vi.fn();
887+const onPostReadySidecars = vi.fn();
888+const onGatewayLifetimeSidecars = vi.fn();
889+const log = { info: vi.fn(), warn: vi.fn() };
890+891+try {
892+await startGatewayPostAttachRuntime({
893+ ...createPostAttachParams(),
894+ log,
895+deferSidecars: true,
896+providerAuthPrewarm: { enabled: true, delayMs: 1_000 },
897+ onPostReadySidecars,
898+ onGatewayLifetimeSidecars,
899+onSidecarsReady: () => {
900+setImmediate(postReadyRequestTurn);
901+},
902+});
903+904+await vi.advanceTimersToNextTimerAsync();
905+await vi.advanceTimersToNextTimerAsync();
906+expect(postReadyRequestTurn).toHaveBeenCalledTimes(1);
907+expect(onPostReadySidecars.mock.calls[0]?.[0]).toHaveLength(0);
908+expect(onGatewayLifetimeSidecars.mock.calls[0]?.[0]).toHaveLength(1);
909+await vi.dynamicImportSettled();
910+await vi.waitFor(() => {
911+expect(hoisted.setAuthProfileFailureHook).toHaveBeenCalledTimes(1);
912+});
913+expect(hoisted.warmCurrentProviderAuthState).not.toHaveBeenCalled();
914+915+await vi.advanceTimersByTimeAsync(1_000);
916+await vi.waitFor(() => {
917+expect(hoisted.warmCurrentProviderAuthState).toHaveBeenCalledTimes(1);
918+});
919+} finally {
920+vi.useRealTimers();
921+}
922+});
923+924+it("keeps provider auth prewarm alive when Gmail post-ready sidecars stop", async () => {
925+vi.useFakeTimers();
926+const onPostReadySidecars = vi.fn();
927+const onGatewayLifetimeSidecars = vi.fn();
928+const log = { info: vi.fn(), warn: vi.fn() };
929+930+try {
931+await startGatewayPostAttachRuntime({
932+ ...createPostAttachParams({
933+cfgAtStart: {
934+hooks: {
935+enabled: true,
936+internal: { enabled: false },
937+gmail: { account: "me" },
938+},
939+} as never,
940+gatewayPluginConfigAtStart: {
941+hooks: {
942+enabled: true,
943+internal: { enabled: false },
944+gmail: { account: "me" },
945+},
946+} as never,
947+}),
948+ log,
949+deferSidecars: true,
950+providerAuthPrewarm: { enabled: true, delayMs: 1_000 },
951+ onPostReadySidecars,
952+ onGatewayLifetimeSidecars,
953+});
954+955+await vi.advanceTimersToNextTimerAsync();
956+const gmailSidecars = onPostReadySidecars.mock.calls[0]?.[0] as
957+| { stop: () => void }[]
958+| undefined;
959+const lifetimeSidecars = onGatewayLifetimeSidecars.mock.calls[0]?.[0] as
960+| { stop: () => void }[]
961+| undefined;
962+expect(gmailSidecars).toHaveLength(1);
963+expect(lifetimeSidecars).toHaveLength(1);
964+965+for (const sidecar of gmailSidecars ?? []) {
966+sidecar.stop();
967+}
968+await vi.dynamicImportSettled();
969+await vi.waitFor(() => {
970+expect(hoisted.setAuthProfileFailureHook).toHaveBeenCalledTimes(1);
971+});
972+973+await vi.advanceTimersByTimeAsync(1_000);
974+await vi.waitFor(() => {
975+expect(hoisted.warmCurrentProviderAuthState).toHaveBeenCalledTimes(1);
976+});
977+978+const hook = hoisted.setAuthProfileFailureHook.mock.calls[0]?.[0] as (() => void) | undefined;
979+hook?.();
980+expect(hoisted.clearCurrentProviderAuthState).toHaveBeenCalledTimes(1);
981+expect(hoisted.warmCurrentProviderAuthState).toHaveBeenCalledTimes(2);
982+} finally {
983+vi.useRealTimers();
984+}
985+});
986+987+it("cancels delayed provider auth prewarm when the sidecar stops before the timer fires", async () => {
988+vi.useFakeTimers();
989+const log = { info: vi.fn(), warn: vi.fn() };
990+991+try {
992+const sidecar = testing.scheduleProviderAuthStatePrewarm({
993+getConfig: () => ({ marker: "current" }) as never,
994+ log,
995+delayMs: 1_000,
996+});
997+await vi.dynamicImportSettled();
998+await vi.waitFor(() => {
999+expect(hoisted.setAuthProfileFailureHook).toHaveBeenCalledTimes(1);
1000+});
1001+1002+sidecar.stop();
1003+await vi.advanceTimersByTimeAsync(1_000);
1004+expect(hoisted.warmCurrentProviderAuthState).not.toHaveBeenCalled();
1005+1006+const hook = hoisted.setAuthProfileFailureHook.mock.calls[0]?.[0] as (() => void) | undefined;
1007+hook?.();
1008+expect(hoisted.clearCurrentProviderAuthState).not.toHaveBeenCalled();
1009+expect(hoisted.warmCurrentProviderAuthState).not.toHaveBeenCalled();
1010+} finally {
1011+vi.useRealTimers();
1012+}
1013+});
1014+1015+it("uses the current provider auth config when the delayed prewarm fires", async () => {
1016+vi.useFakeTimers();
1017+const startupCfg = { marker: "startup" } as never;
1018+const reloadedCfg = { marker: "reloaded" } as never;
1019+const afterFailureCfg = { marker: "after-failure" } as never;
1020+let currentCfg = startupCfg;
1021+const log = { info: vi.fn(), warn: vi.fn() };
1022+1023+try {
1024+testing.scheduleProviderAuthStatePrewarm({
1025+getConfig: () => currentCfg,
1026+ log,
1027+delayMs: 0,
1028+});
1029+currentCfg = reloadedCfg;
1030+await vi.dynamicImportSettled();
1031+await vi.waitFor(() => {
1032+expect(hoisted.setAuthProfileFailureHook).toHaveBeenCalledTimes(1);
1033+});
1034+await vi.advanceTimersByTimeAsync(0);
1035+await vi.waitFor(() => {
1036+expect(hoisted.warmCurrentProviderAuthState).toHaveBeenCalledTimes(1);
1037+});
1038+1039+const hook = hoisted.setAuthProfileFailureHook.mock.calls[0]?.[0] as (() => void) | undefined;
1040+if (!hook) {
1041+throw new Error("Expected provider auth failure hook to be registered");
1042+}
1043+1044+hook();
1045+currentCfg = afterFailureCfg;
1046+hook();
1047+expect(hoisted.clearCurrentProviderAuthState).toHaveBeenCalledTimes(2);
1048+expect(hoisted.warmCurrentProviderAuthState).toHaveBeenCalledTimes(3);
1049+expect(hoisted.warmCurrentProviderAuthState.mock.calls[0]?.[0]).toBe(reloadedCfg);
1050+expect(hoisted.warmCurrentProviderAuthState.mock.calls[1]?.[0]).toBe(reloadedCfg);
1051+expect(hoisted.warmCurrentProviderAuthState.mock.calls[2]?.[0]).toBe(afterFailureCfg);
1052+} finally {
1053+vi.useRealTimers();
1054+}
1055+});
1056+8591057it("prewarms models.json in the configured default agent dir", async () => {
8601058const cfg = {
8611059agents: {
@@ -1654,6 +1852,7 @@ function createPostAttachParams(overrides: Partial<PostAttachParams> = {}): Post
16541852error: vi.fn(),
16551853},
16561854unavailableGatewayMethods: new Set<string>(),
1855+providerAuthPrewarm: { enabled: false },
16571856 ...overrides,
16581857};
16591858}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。