Skip to content

Commit 9943068

Browse files
committed
test(cl-8310): failing gpt narrate-before-tools residual tests
1 parent 2c38c6f commit 9943068

3 files changed

Lines changed: 159 additions & 0 deletions

File tree

‎src/agent/model-family-policy.test.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,25 @@ describe("resolveModelFamilyPolicy", () => {
102102
expect(muse.toolDisciplineRules).toContain("Never re-read a file");
103103
expect(base.toolDisciplineRules).toBeUndefined();
104104
});
105+
106+
test("gpt resolves its own family on permissive default thresholds (CL-8310)", () => {
107+
const gpt = resolveModelFamilyPolicy({
108+
providerName: "codex/default",
109+
model: "gpt-5.5",
110+
});
111+
const base = resolveModelFamilyPolicy({
112+
providerName: "anthropic",
113+
model: "claude-sonnet-4",
114+
});
115+
expect(gpt.family).toBe("gpt");
116+
// No eval characterization for gpt tool-only stretches yet: ship the
117+
// permissive default, no finish-bias, no discipline rules. The
118+
// narrate-before-tools residual is prompt-level (see prompts.ts), not a
119+
// threshold.
120+
expect(gpt.toolOnlyTurnNudgeAt).toBe(base.toolOnlyTurnNudgeAt);
121+
expect(gpt.subAgentStallTimeoutMs).toBe(base.subAgentStallTimeoutMs);
122+
expect(gpt.applyGrokFinishBias).toBe(false);
123+
expect(gpt.toolDisciplineRules).toBeUndefined();
124+
expect(gpt.advertisedToolDeny).toEqual([]);
125+
});
105126
});

‎src/agent/prompts.test.ts‎

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { describe, expect, it } from "bun:test";
22
import {
33
buildChatSystemPrompt,
4+
buildGptNarrateBeforeToolsNote,
45
buildGrokLeafAntiThrashNote,
56
buildGuidelines,
67
buildPromptDisciplineBlock,
@@ -370,3 +371,79 @@ describe("grok finish-bias residual gating (extends existing provider-family tes
370371
expect(prompt.toLowerCase()).not.toContain("kimi");
371372
});
372373
});
374+
375+
describe("gpt narrate-before-tools residual (CL-8310)", () => {
376+
it("is a 3-line narrate-before-tools note, not manage_tasks ceremony", () => {
377+
const note = buildGptNarrateBeforeToolsNote();
378+
expect(note).toContain("Narrate before tools (GPT worker):");
379+
expect(note).toMatch(/before.*tool call.*one short line/is);
380+
expect(note).toMatch(/no narration between them/i);
381+
expect(note).toContain("write the report envelope");
382+
expect(note.toLowerCase()).not.toContain("manage_tasks");
383+
});
384+
385+
it("appears exactly once on a gpt leaf prompt", () => {
386+
const prompt = buildSubAgentSystemPrompt(undefined, undefined, undefined, {
387+
orchestrator: false,
388+
gptNarrateBeforeTools: true,
389+
});
390+
const note = buildGptNarrateBeforeToolsNote();
391+
expect(countOccurrences(prompt, note)).toBe(1);
392+
expect(prompt.trimEnd().endsWith(note)).toBe(true);
393+
});
394+
395+
it("appears exactly once on a gpt primary prompt", () => {
396+
const prompt = buildChatSystemPrompt(
397+
undefined,
398+
undefined,
399+
undefined,
400+
[],
401+
"orchestrator",
402+
undefined,
403+
undefined,
404+
{ gptNarrateBeforeTools: true },
405+
);
406+
const note = buildGptNarrateBeforeToolsNote();
407+
expect(countOccurrences(prompt, note)).toBe(1);
408+
});
409+
410+
it("is absent by default on both primary and leaf", () => {
411+
const leaf = buildSubAgentSystemPrompt(undefined, undefined, undefined, {
412+
orchestrator: false,
413+
grokAntiThrash: false,
414+
});
415+
const primary = buildChatSystemPrompt(
416+
undefined,
417+
undefined,
418+
undefined,
419+
[],
420+
"orchestrator",
421+
);
422+
expect(leaf).not.toContain("Narrate before tools (GPT worker):");
423+
expect(primary).not.toContain("Narrate before tools (GPT worker):");
424+
});
425+
426+
it("is absent on grok and claude prompts", () => {
427+
const grokLeaf = buildSubAgentSystemPrompt(undefined, undefined, undefined, {
428+
orchestrator: false,
429+
grokAntiThrash: true,
430+
});
431+
const claudeLeaf = buildSubAgentSystemPrompt(undefined, undefined, undefined, {
432+
orchestrator: false,
433+
grokAntiThrash: false,
434+
});
435+
const claudePrimary = buildChatSystemPrompt(
436+
undefined,
437+
undefined,
438+
undefined,
439+
[],
440+
"orchestrator",
441+
);
442+
for (const prompt of [grokLeaf, claudeLeaf, claudePrimary]) {
443+
expect(prompt).not.toContain("Narrate before tools (GPT worker):");
444+
expect(prompt).not.toContain("Narrate before tools (GPT");
445+
}
446+
// The grok row keeps its own residual, untouched.
447+
expect(grokLeaf).toContain("Finish bias (xAI / Grok worker):");
448+
});
449+
});

‎src/subagent/provider-family.test.ts‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { describe, expect, test } from "bun:test";
22
import {
33
detectModelFamily,
4+
isGptProvider,
45
isKimiLeafProvider,
56
isXaiGrokLeafProvider,
67
shouldApplyGrokAntiThrash,
78
} from "./provider-family.js";
9+
import { CODEX_DEFAULT_MODELS } from "../auth/codex/constants.js";
810

911
describe("isXaiGrokLeafProvider", () => {
1012
test("matches xai/ OAuth provider names", () => {
@@ -139,3 +141,62 @@ describe("detectModelFamily", () => {
139141
).toBe("default");
140142
});
141143
});
144+
145+
describe("isGptProvider (CL-8310)", () => {
146+
test("matches codex OAuth provider names", () => {
147+
expect(isGptProvider({ providerName: "codex/default" })).toBe(true);
148+
expect(isGptProvider({ providerName: "codex/work" })).toBe(true);
149+
});
150+
151+
test("matches codex adapter ids and bare codex names", () => {
152+
expect(isGptProvider({ providerName: "codex-responses" })).toBe(true);
153+
expect(isGptProvider({ providerName: "codex" })).toBe(true);
154+
});
155+
156+
test("matches gpt-* model ids on any provider", () => {
157+
expect(
158+
isGptProvider({ providerName: "openai", model: "gpt-5.5" }),
159+
).toBe(true);
160+
expect(
161+
isGptProvider({ providerName: "opencode-go", model: "gpt-5.1" }),
162+
).toBe(true);
163+
expect(
164+
isGptProvider({ providerName: "openai-compat", model: "gpt-5.6-luna" }),
165+
).toBe(true);
166+
});
167+
168+
test("covers every in-tree codex catalog model without naming cells", () => {
169+
// No terra/sol/astra special-casing: every served codex id resolves via
170+
// the generic codex-provider / gpt-* match, so future cells ride along.
171+
for (const model of CODEX_DEFAULT_MODELS) {
172+
expect(isGptProvider({ providerName: "codex/default", model })).toBe(
173+
true,
174+
);
175+
expect(detectModelFamily({ providerName: "codex/default", model })).toBe(
176+
"gpt",
177+
);
178+
}
179+
});
180+
181+
test("rejects grok, kimi, muse, and claude", () => {
182+
expect(
183+
isGptProvider({ providerName: "xai/default", model: "grok-4.6" }),
184+
).toBe(false);
185+
expect(isGptProvider({ providerName: "moonshot", model: "kimi-k2" })).toBe(
186+
false,
187+
);
188+
expect(
189+
isGptProvider({
190+
providerName: "opencode-go",
191+
model: "muse-spark-1.3-contributor",
192+
}),
193+
).toBe(false);
194+
expect(
195+
isGptProvider({
196+
providerName: "anthropic",
197+
model: "claude-sonnet-4",
198+
}),
199+
).toBe(false);
200+
expect(isGptProvider({ providerName: "anthropic" })).toBe(false);
201+
});
202+
});

0 commit comments

Comments
 (0)