Skip to content

Commit 6adf4d3

Browse files
committed
chore(integration): merge reviewed worker schemas
2 parents 252f401 + 0f7e430 commit 6adf4d3

9 files changed

Lines changed: 574 additions & 27 deletions

File tree

‎src/agent/directors/explorer/package.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { DirectorPackage } from "../types.js";
2-
import { REVIEW_TOOLS } from "../tool-sets.js";
2+
import { READ_TOOLS } from "../tool-sets.js";
33

44
/**
55
* Explorer worker (CL-7020 / CL-7015 rename from explore).
@@ -33,7 +33,7 @@ FINDINGS SHAPE: Findings must be a scannable map — key paths, symbols, call fl
3333
FINISH BIAS: Prefer one thorough pass then report. Expand Findings, change approach, or write the final report — do not keep re-reading the same paths.
3434
3535
OUT OF LANE: product writes, drive-by fixes, shipping features, review severity theater, orchestration, spawning specialists, fleet discovery, becoming Coder/Reviewer/orchestrator as primary.`,
36-
tools: { allow: REVIEW_TOOLS },
36+
tools: { allow: READ_TOOLS },
3737
spawn: { maySpawn: false },
3838
tier: "leaf",
3939
modelRole: "explore",

‎src/agent/directors/registry.test.ts‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@ describe("director registry", () => {
101101
expect(explorer.description).toContain("agent id: explorer");
102102
expect(explorer.capabilities?.mode).toBe("allow");
103103
expect(explorer.capabilities?.tools).toContain("read_file");
104-
expect(explorer.capabilities?.tools).toContain("write_file");
105-
expect(explorer.capabilities?.tools).toContain("edit_file");
106-
expect(explorer.capabilities?.tools).toContain("delete_file");
104+
expect(explorer.capabilities?.tools).not.toContain("write_file");
105+
expect(explorer.capabilities?.tools).not.toContain("edit_file");
106+
expect(explorer.capabilities?.tools).not.toContain("delete_file");
107107
expect(explorer.orchestrator).toBe(false);
108108

109109
const reviewer = packageToProfile(DIRECTOR_REGISTRY.reviewer);
@@ -135,8 +135,13 @@ describe("director registry", () => {
135135
expect(spawners).toEqual(["dispatch"]);
136136
});
137137

138-
test("closed directors mount product write tools", () => {
138+
test("explorer is read-only; other closed directors mount product writes", () => {
139+
const explorerAllow = DIRECTOR_REGISTRY.explorer.tools?.allow ?? [];
140+
expect(explorerAllow).not.toContain("write_file");
141+
expect(explorerAllow).not.toContain("edit_file");
142+
expect(explorerAllow).not.toContain("delete_file");
139143
for (const id of DIRECTOR_IDS) {
144+
if (id === "explorer") continue;
140145
const allow = DIRECTOR_REGISTRY[id].tools?.allow ?? [];
141146
expect(allow).toContain("write_file");
142147
expect(allow).toContain("edit_file");

‎src/agent/directors/tool-sets.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const DOCS_TOOLS = [
5353
...PRODUCT_WRITE_TOOLS,
5454
] as const;
5555

56-
/** Review / planner / explorer: read surface + path writes. */
56+
/** Review / planner / warden / prober: read surface + path writes. */
5757
export const REVIEW_TOOLS = [...READ_TOOLS, ...PRODUCT_WRITE_TOOLS] as const;
5858

5959
/**

‎src/agent/tool-schema-normalize.test.ts‎

Lines changed: 228 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { defined } from "../../testkit/defined.js";
22
import { describe, expect, test } from "bun:test";
3+
import type { ToolDefinition } from "@intx/types/runtime";
34
import { presentDefinition } from "./director.js";
45
import { manageTasksDefinition } from "./tasks.js";
56
import {
@@ -152,7 +153,6 @@ describe("normalizeToolDefinitionsForProvider", () => {
152153
for (const ctx of [
153154
{ providerName: "anthropic", model: "claude-sonnet-4" },
154155
{ providerName: "openai", model: "gpt-5.6" },
155-
{ providerName: "xai/default", model: "grok-4.5" },
156156
{ providerName: "opencode-go", model: "gpt-5.1" },
157157
] as const) {
158158
const out = normalizeToolDefinitionsForProvider(defs, ctx);
@@ -163,6 +163,233 @@ describe("normalizeToolDefinitionsForProvider", () => {
163163
}
164164
});
165165

166+
test("grok sanitizes untyped properties and strips $schema", () => {
167+
const untyped: ToolDefinition = {
168+
name: "submit_result",
169+
description: "test",
170+
inputSchema: {
171+
$schema: "https://json-schema.org/draft/2020-12/schema",
172+
type: "object",
173+
properties: {
174+
turn_token: { type: "string" },
175+
result: { description: "The structured result payload." },
176+
},
177+
required: ["turn_token", "result"],
178+
},
179+
};
180+
const out = normalizeToolDefinitionsForProvider([untyped], {
181+
providerName: "xai/default-2",
182+
model: "grok-4.6",
183+
});
184+
const schema = defined(out[0]).inputSchema as {
185+
$schema?: unknown;
186+
properties?: {
187+
result?: { type?: string; additionalProperties?: boolean };
188+
};
189+
};
190+
expect(schema.$schema).toBeUndefined();
191+
expect(schema.properties?.result?.type).toBe("object");
192+
expect(schema.properties?.result?.additionalProperties).toBe(true);
193+
});
194+
195+
test("grok sanitizer leaves anthropic identity and typed properties alone", () => {
196+
const typed: ToolDefinition = {
197+
name: "ask_director",
198+
description: "test",
199+
inputSchema: {
200+
type: "object",
201+
properties: {
202+
question: { type: "string", description: "q" },
203+
},
204+
required: ["question"],
205+
},
206+
};
207+
const input = [typed];
208+
const grok = normalizeToolDefinitionsForProvider(input, {
209+
providerName: "xai/default",
210+
model: "grok-4.6",
211+
});
212+
const anthropic = normalizeToolDefinitionsForProvider(input, {
213+
providerName: "anthropic",
214+
model: "claude-sonnet-4",
215+
});
216+
expect(anthropic).toBe(input);
217+
expect(grok).not.toBe(input);
218+
const grokQuestion = (
219+
defined(grok[0]).inputSchema as {
220+
properties?: { question?: { type?: string } };
221+
}
222+
).properties?.question;
223+
expect(grokQuestion?.type).toBe("string");
224+
});
225+
226+
test.each([
227+
{ values: ["open", "closed"], types: ["string"] },
228+
{ values: [1, 2.5], types: ["number"] },
229+
{ values: [true, false], types: ["boolean"] },
230+
{ values: [null], types: ["null"] },
231+
{ values: [[1], [2]], types: ["array"] },
232+
{ values: [{ state: "open" }], types: ["object"] },
233+
{
234+
values: ["open", null, 1, true, [], {}],
235+
types: ["string", "null", "number", "boolean", "array", "object"],
236+
},
237+
])("grok preserves enum values with types $types", ({ values, types }) => {
238+
const def: ToolDefinition = {
239+
name: "mcp__tracker__set_state",
240+
description: "Set state",
241+
inputSchema: {
242+
type: "object",
243+
properties: { state: { enum: values } },
244+
required: ["state"],
245+
},
246+
};
247+
const before = structuredClone(def);
248+
const normalized = defined(
249+
normalizeToolDefinitionsForProvider([def], {
250+
providerName: "xai/default",
251+
model: "grok-4.6",
252+
})[0],
253+
);
254+
const schema = normalized.inputSchema as {
255+
properties: {
256+
state: {
257+
enum: unknown[];
258+
type?: string;
259+
anyOf?: { type: string }[];
260+
};
261+
};
262+
};
263+
const state = schema.properties.state;
264+
expect(state.enum).toEqual([...values]);
265+
expect(state.anyOf?.map((branch) => branch.type) ?? [state.type]).toEqual([
266+
...types,
267+
]);
268+
expect(def).toEqual(before);
269+
expect(
270+
normalizeToolDefinitionsForProvider([def], {
271+
providerName: "anthropic",
272+
model: "claude-sonnet-4",
273+
})[0],
274+
).toBe(def);
275+
});
276+
277+
test.each([
278+
{ value: "open", expectedType: "string" },
279+
{ value: 1.5, expectedType: "number" },
280+
{ value: true, expectedType: "boolean" },
281+
{ value: null, expectedType: "null" },
282+
{ value: ["open"], expectedType: "array" },
283+
{ value: { state: "open" }, expectedType: "object" },
284+
])(
285+
"grok preserves $expectedType const payloads",
286+
({ value, expectedType }) => {
287+
const def: ToolDefinition = {
288+
name: "mcp__tracker__set_state",
289+
description: "Set state",
290+
inputSchema: {
291+
type: "object",
292+
properties: { state: { const: value } },
293+
required: ["state"],
294+
},
295+
};
296+
const before = structuredClone(def);
297+
const normalized = defined(
298+
normalizeToolDefinitionsForProvider([def], {
299+
providerName: "xai/default",
300+
model: "grok-4.6",
301+
})[0],
302+
);
303+
expect(normalized.inputSchema).toMatchObject({
304+
properties: { state: { const: value, type: expectedType } },
305+
});
306+
expect(def).toEqual(before);
307+
},
308+
);
309+
310+
test("grok sanitizes schema nodes without rewriting literal payloads or property names", () => {
311+
const literal = {
312+
$schema: "literal schema value",
313+
$id: "literal id value",
314+
properties: { payload: { description: "literal, not a schema" } },
315+
};
316+
const def: ToolDefinition = {
317+
name: "mcp__schema__save",
318+
description: "Save a schema document",
319+
inputSchema: {
320+
type: "object",
321+
$schema: "https://json-schema.org/draft/2020-12/schema",
322+
$id: "https://example.invalid/tool",
323+
properties: {
324+
$schema: { type: "string" },
325+
$id: { type: "string" },
326+
document: {
327+
enum: [literal],
328+
const: literal,
329+
default: literal,
330+
examples: [literal],
331+
},
332+
entries: {
333+
type: "array",
334+
items: {
335+
$id: "https://example.invalid/entry",
336+
type: "object",
337+
properties: { payload: { description: "untyped payload" } },
338+
},
339+
},
340+
},
341+
$defs: {
342+
$schema: {
343+
type: "object",
344+
properties: { payload: { description: "untyped definition" } },
345+
},
346+
},
347+
},
348+
};
349+
const before = structuredClone(def);
350+
const normalized = defined(
351+
normalizeToolDefinitionsForProvider([def], {
352+
providerName: "xai/default",
353+
model: "grok-4.6",
354+
})[0],
355+
);
356+
expect(normalized.inputSchema).toMatchObject({
357+
properties: {
358+
$schema: { type: "string" },
359+
$id: { type: "string" },
360+
document: {
361+
enum: [literal],
362+
const: literal,
363+
default: literal,
364+
examples: [literal],
365+
},
366+
entries: {
367+
items: {
368+
properties: {
369+
payload: { type: "object", additionalProperties: true },
370+
},
371+
},
372+
},
373+
},
374+
$defs: {
375+
$schema: {
376+
properties: {
377+
payload: { type: "object", additionalProperties: true },
378+
},
379+
},
380+
},
381+
});
382+
const schema = normalized.inputSchema as {
383+
$schema?: unknown;
384+
$id?: unknown;
385+
properties: { entries: { items: { $id?: unknown } } };
386+
};
387+
expect(schema.$schema).toBeUndefined();
388+
expect(schema.$id).toBeUndefined();
389+
expect(schema.properties.entries.items.$id).toBeUndefined();
390+
expect(def).toEqual(before);
391+
});
392+
166393
test("kimi rewrite leaves non-present tools untouched", () => {
167394
const out = normalizeToolDefinitionsForProvider(defs, {
168395
providerName: "moonshot",

0 commit comments

Comments
 (0)