





















@@ -15,6 +15,10 @@ const mocks = vi.hoisted(() => ({
1515resolveAgentWorkspaceDir: vi.fn(() => "/tmp/openclaw-workspace"),
1616resolveDefaultAgentId: vi.fn(() => "default"),
1717note: vi.fn(),
18+loadModelCatalog: vi.fn(async () => []),
19+getModelRefStatus: vi.fn(() => ({ allowed: true, inCatalog: true, key: "openai/gpt-5.5" })),
20+resolveConfiguredModelRef: vi.fn(() => ({ provider: "openai", model: "gpt-5.5" })),
21+resolveHooksGmailModel: vi.fn(() => ({ provider: "openai", model: "gpt-5.5" })),
1822replaceConfigFile: vi.fn().mockResolvedValue(undefined),
1923readConfigFileSnapshot: vi.fn().mockResolvedValue({
2024exists: true,
@@ -57,6 +61,16 @@ vi.mock("../terminal/note.js", () => ({
5761note: mocks.note,
5862}));
596364+vi.mock("../agents/model-catalog.js", () => ({
65+loadModelCatalog: mocks.loadModelCatalog,
66+}));
67+68+vi.mock("../agents/model-selection.js", () => ({
69+getModelRefStatus: mocks.getModelRefStatus,
70+resolveConfiguredModelRef: mocks.resolveConfiguredModelRef,
71+resolveHooksGmailModel: mocks.resolveHooksGmailModel,
72+}));
73+6074vi.mock("../version.js", () => ({
6175VERSION: "2026.5.2-test",
6276}));
@@ -138,6 +152,18 @@ describe("doctor health contributions", () => {
138152mocks.resolveDefaultAgentId.mockReset();
139153mocks.resolveDefaultAgentId.mockReturnValue("default");
140154mocks.note.mockReset();
155+mocks.loadModelCatalog.mockReset();
156+mocks.loadModelCatalog.mockResolvedValue([]);
157+mocks.getModelRefStatus.mockReset();
158+mocks.getModelRefStatus.mockReturnValue({
159+allowed: true,
160+inCatalog: true,
161+key: "openai/gpt-5.5",
162+});
163+mocks.resolveConfiguredModelRef.mockReset();
164+mocks.resolveConfiguredModelRef.mockReturnValue({ provider: "openai", model: "gpt-5.5" });
165+mocks.resolveHooksGmailModel.mockReset();
166+mocks.resolveHooksGmailModel.mockReturnValue({ provider: "openai", model: "gpt-5.5" });
141167mocks.readConfigFileSnapshot.mockReset();
142168mocks.readConfigFileSnapshot.mockResolvedValue({
143169exists: true,
@@ -249,6 +275,25 @@ describe("doctor health contributions", () => {
249275expect(ids.indexOf("doctor:skills")).toBeLessThan(ids.indexOf("doctor:write-config"));
250276});
251277278+it("uses the read-only model catalog for hooks.gmail.model warnings", async () => {
279+const contribution = requireDoctorContribution("doctor:hooks-model");
280+const cfg = {
281+hooks: {
282+gmail: {
283+model: "openai/gpt-5.5",
284+},
285+},
286+};
287+const ctx = {
288+ cfg,
289+options: {},
290+} as Parameters<(typeof contribution)["run"]>[0];
291+292+await contribution.run(ctx);
293+294+expect(mocks.loadModelCatalog).toHaveBeenCalledWith({ config: cfg, readOnly: true });
295+});
296+252297it("runs structured repairs before legacy skill repairs and config writes", () => {
253298const ids = resolveDoctorHealthContributions().map((entry) => entry.id);
254299此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。