Skip to content

Commit 685b95b

Browse files
authored
fix(cron): warn when allowed web_search has no provider
1 parent 597a0ba commit 685b95b

5 files changed

Lines changed: 325 additions & 6 deletions

File tree

src/cron/isolated-agent/run.test-harness.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ export const getChannelPluginMock = createMock();
8080
export const retireSessionMcpRuntimeMock = createMock();
8181
export const callGatewayMock = createMock();
8282
export const ensureRuntimePluginsLoadedMock = createMock();
83+
export const listWebSearchProvidersMock = createMock();
84+
export const resolveWebSearchProviderIdMock = createMock();
8385

8486
const resolveBootstrapWarningSignaturesSeenMock = createMock();
8587
const resolveCronStyleNowMock = createMock();
@@ -161,6 +163,11 @@ vi.mock("../../plugins/runtime-plugins.runtime.js", () => ({
161163
ensureRuntimePluginsLoaded: ensureRuntimePluginsLoadedMock,
162164
}));
163165

166+
vi.mock("../../web-search/runtime.js", () => ({
167+
listWebSearchProviders: listWebSearchProvidersMock,
168+
resolveWebSearchProviderId: resolveWebSearchProviderIdMock,
169+
}));
170+
164171
vi.mock("../../skills/runtime/cron-snapshot.runtime.js", () => ({
165172
canExecRequestNode: vi.fn(() => false),
166173
getRemoteSkillEligibility: getRemoteSkillEligibilityMock,
@@ -673,6 +680,10 @@ export function resetRunCronIsolatedAgentTurnHarness(): void {
673680
setSessionRuntimeModelMock.mockReturnValue(undefined);
674681
logWarnMock.mockReset();
675682
ensureRuntimePluginsLoadedMock.mockReset();
683+
listWebSearchProvidersMock.mockReset();
684+
listWebSearchProvidersMock.mockReturnValue([{ id: "duckduckgo" }]);
685+
resolveWebSearchProviderIdMock.mockReset();
686+
resolveWebSearchProviderIdMock.mockReturnValue("duckduckgo");
676687
}
677688

678689
export function clearFastTestEnv(): string | undefined {

src/cron/isolated-agent/run.tools-allow.test.ts

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
// Tool allowlist tests cover tool availability for isolated cron runs.
22
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3+
import { MISSING_WEB_SEARCH_PROVIDER_DIAGNOSTIC_MESSAGE } from "../run-diagnostics.js";
34
import "../../agents/test-helpers/fast-coding-tools.js";
45
import {
6+
listWebSearchProvidersMock,
7+
loadModelCatalogMock,
58
loadRunCronIsolatedAgentTurn,
9+
resolveConfiguredModelRefMock,
610
resetRunCronIsolatedAgentTurnHarness,
711
resolveDeliveryTargetMock,
12+
resolveWebSearchProviderIdMock,
813
runEmbeddedAgentMock,
914
runWithModelFallbackMock,
1015
} from "./run.test-harness.js";
@@ -46,6 +51,23 @@ function makeParamsWithToolsAllow(toolsAllow: string[]) {
4651
};
4752
}
4853

54+
function makeParamsWithDefaultToolsAllow(toolsAllow: string[]) {
55+
const params = makeParams();
56+
const job = params.job as Record<string, unknown>;
57+
return {
58+
...params,
59+
job: {
60+
...job,
61+
payload: {
62+
kind: "agentTurn",
63+
message: "check allowed tools",
64+
toolsAllow,
65+
toolsAllowIsDefault: true,
66+
},
67+
} as never,
68+
};
69+
}
70+
4971
function requireEmbeddedAgentCall(): {
5072
jobId?: string;
5173
toolsAllow?: string[];
@@ -127,4 +149,114 @@ describe("runCronIsolatedAgentTurn toolsAllow passthrough", () => {
127149
expect(call.toolsAllow).toEqual(["maniple__check_idle_workers"]);
128150
},
129151
);
152+
153+
it(
154+
"adds cron diagnostics when web_search is allowed without a selected provider",
155+
{ timeout: RUN_TOOLS_ALLOW_TIMEOUT_MS },
156+
async () => {
157+
listWebSearchProvidersMock.mockReturnValue([{ id: "duckduckgo" }]);
158+
resolveWebSearchProviderIdMock.mockReturnValue("");
159+
160+
const result = await runCronIsolatedAgentTurn(makeParamsWithToolsAllow(["web_search"]));
161+
162+
expect(result.status).toBe("ok");
163+
expect(runEmbeddedAgentMock).toHaveBeenCalledTimes(1);
164+
const call = requireEmbeddedAgentCall();
165+
expect(call.toolsAllow).toEqual(["web_search"]);
166+
expect(result.diagnostics?.summary).toBe(MISSING_WEB_SEARCH_PROVIDER_DIAGNOSTIC_MESSAGE);
167+
expect(result.diagnostics?.entries).toEqual([
168+
{
169+
ts: expect.any(Number),
170+
source: "cron-preflight",
171+
severity: "warn",
172+
message: MISSING_WEB_SEARCH_PROVIDER_DIAGNOSTIC_MESSAGE,
173+
toolName: "web_search",
174+
},
175+
]);
176+
},
177+
);
178+
179+
it(
180+
"does not warn for default-derived toolsAllow that includes web_search",
181+
{ timeout: RUN_TOOLS_ALLOW_TIMEOUT_MS },
182+
async () => {
183+
listWebSearchProvidersMock.mockReturnValue([]);
184+
185+
const result = await runCronIsolatedAgentTurn(
186+
makeParamsWithDefaultToolsAllow(["web_search"]),
187+
);
188+
189+
expect(result.status).toBe("ok");
190+
expect(result.diagnostics).toBeUndefined();
191+
},
192+
);
193+
194+
it(
195+
"does not warn when native web_search suppresses the managed provider tool",
196+
{ timeout: RUN_TOOLS_ALLOW_TIMEOUT_MS },
197+
async () => {
198+
listWebSearchProvidersMock.mockReturnValue([]);
199+
resolveConfiguredModelRefMock.mockReturnValue({
200+
provider: "gateway",
201+
model: "gpt-5.5",
202+
});
203+
loadModelCatalogMock.mockResolvedValue([
204+
{
205+
id: "gpt-5.5",
206+
name: "GPT-5.5",
207+
provider: "gateway",
208+
api: "openai-chatgpt-responses",
209+
},
210+
]);
211+
212+
const result = await runCronIsolatedAgentTurn({
213+
...makeParamsWithToolsAllow(["web_search"]),
214+
cfg: {
215+
tools: {
216+
web: {
217+
search: {
218+
enabled: true,
219+
openaiCodex: {
220+
enabled: true,
221+
mode: "cached",
222+
},
223+
},
224+
},
225+
},
226+
},
227+
});
228+
229+
expect(result.status).toBe("ok");
230+
expect(result.diagnostics).toBeUndefined();
231+
},
232+
);
233+
234+
it(
235+
"keeps web_search provider diagnostics when the run aborts",
236+
{ timeout: RUN_TOOLS_ALLOW_TIMEOUT_MS },
237+
async () => {
238+
listWebSearchProvidersMock.mockReturnValue([]);
239+
resolveWebSearchProviderIdMock.mockReturnValue("");
240+
runWithModelFallbackMock.mockResolvedValueOnce({
241+
result: {
242+
payloads: [],
243+
meta: {
244+
aborted: true,
245+
agentMeta: {},
246+
},
247+
},
248+
provider: "openai",
249+
model: "gpt-5.4",
250+
attempts: [],
251+
});
252+
253+
const result = await runCronIsolatedAgentTurn(makeParamsWithToolsAllow(["web_search"]));
254+
255+
expect(result.status).toBe("error");
256+
expect(result.diagnostics?.entries.map((entry) => entry.message)).toEqual([
257+
MISSING_WEB_SEARCH_PROVIDER_DIAGNOSTIC_MESSAGE,
258+
"cron isolated agent run aborted",
259+
]);
260+
},
261+
);
130262
});

0 commit comments

Comments
 (0)