From 04459a6e73257d0d2e99e3fa51a801759a81fc37 Mon Sep 17 00:00:00 2001 From: kapu Date: Sat, 1 Aug 2026 20:01:32 +0900 Subject: [PATCH 1/2] fix(claude): honor authoritative context windows in generated profiles --- src/claude/agents-inject.ts | 17 ++++++++++++----- tests/claude-agents-inject.test.ts | 10 +++++----- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/claude/agents-inject.ts b/src/claude/agents-inject.ts index a994b1d82a..b86886742b 100644 --- a/src/claude/agents-inject.ts +++ b/src/claude/agents-inject.ts @@ -15,7 +15,7 @@ import { lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync import { join } from "node:path"; import type { OcxConfig } from "../types"; import { claudeCodeAlias, claudeCodeNativeAlias } from "./alias"; -import { resolveAutoContext, stripOneMillionMarker, withOneMillionMarker } from "./context-windows"; +import { stripOneMillionMarker, withOneMillionMarker } from "./context-windows"; import { claudeConfigDir } from "./gateway-cache"; import { DEFAULT_SUBAGENT_MODELS, hasOwnProvider } from "../config"; import { effectiveBlockedSkillNames, resolveInboundModel } from "./inbound"; @@ -55,6 +55,15 @@ function pickerDefaultModel(configDir: string): string | null { } } +/** + * Generated subagents cannot rely on parent auto-context compaction before + * routed requests reach the real model limit. + */ +function withSubagentContextMarker(selector: string, windows: Record): string { + const bare = stripOneMillionMarker(selector); + return withOneMillionMarker(bare, windows) ?? bare; +} + /** Roster entry -> alias + display parts. Entries are bare native slugs or "provider/id". * Codex-facing encoded ids (`provider/vendor-model`) decode to the native slash id first * so the alias joins the raw-native context-window map (context-windows.ts). */ @@ -72,7 +81,6 @@ function entryParts(entry: string, config: OcxConfig): { alias: string; id: stri } export function buildClaudeAgentDefs(config: OcxConfig, windows: Record, configDir = claudeConfigDir()): ClaudeAgentDef[] { - const auto = resolveAutoContext(config.claudeCode); const blockedSkills = effectiveBlockedSkillNames(config.claudeCode); const blockedSkillsFor = (model: string): readonly string[] => { const unmarked = stripOneMillionMarker(model); @@ -87,8 +95,7 @@ export function buildClaudeAgentDefs(config: OcxConfig, windows: Record(); const push = (name: string, alias: string, description: string) => { - // Effective model value: [1m] marking follows the same predicate as env slots. - const model = withOneMillionMarker(alias, windows, auto) ?? alias; + const model = withSubagentContextMarker(alias, windows); const bare = alias.toLowerCase(); if (coveredModels.has(bare)) return; coveredModels.add(bare); @@ -120,7 +127,7 @@ export function buildClaudeAgentDefs(config: OcxConfig, windows: Record no self def. const selfModel = pickerDefaultModel(configDir) ?? (config.claudeCode?.model?.trim() || null); if (selfModel) { - const marked = withOneMillionMarker(selfModel, windows, auto) ?? selfModel; + const marked = withSubagentContextMarker(selfModel, windows); defs.push({ file: `${OWNED_PREFIX}self.md`, name: `${OWNED_PREFIX}self`, diff --git a/tests/claude-agents-inject.test.ts b/tests/claude-agents-inject.test.ts index 623a193b2b..560c202f4b 100644 --- a/tests/claude-agents-inject.test.ts +++ b/tests/claude-agents-inject.test.ts @@ -24,19 +24,19 @@ function generatedBodies(config: OcxConfig, dir: string): string[] { } describe("buildClaudeAgentDefs (devlog 070 + audit 071)", () => { - test("roster + pinned self from settings.json; [1m] marking; name collision suffix", () => { + test("roster + pinned self mark only authoritative 1M windows; name collision suffix", () => { const windows = { "claude-ocx-native--gpt-5.6-sol": 372_000, "claude-ocx-cursor--gpt-5.6-sol": 1_000_000 }; const dir = tempDir(); writeFileSync(join(dir, "settings.json"), JSON.stringify({ model: "claude-ocx-native--gpt-5.6-sol[1m]" })); const defs = buildClaudeAgentDefs(cfg({ subagentModels: ["gpt-5.6-sol", "cursor/gpt-5.6-sol"], - claudeCode: {}, + claudeCode: { autoContext: true }, }), windows, dir); const byName = Object.fromEntries(defs.map(d => [d.name, d])); - expect(byName["ocx-gpt-5-6-sol"]!.model).toBe("claude-ocx-native--gpt-5.6-sol[1m]"); // 372k >= 350k default + expect(byName["ocx-gpt-5-6-sol"]!.model).toBe("claude-ocx-native--gpt-5.6-sol"); expect(byName["ocx-gpt-5-6-sol-2"]!.model).toBe("claude-ocx-cursor--gpt-5.6-sol[1m]"); // collision suffix - // Self pins the picker-saved default (inherit disproven live — devlog 072). - expect(byName["ocx-self"]!.model).toBe("claude-ocx-native--gpt-5.6-sol[1m]"); + // Self pins the picker-saved default but cannot inherit an unsafe auto-context marker. + expect(byName["ocx-self"]!.model).toBe("claude-ocx-native--gpt-5.6-sol"); expect(defs).toHaveLength(3); // Dispatcher directive (live repro: model:"fable" override broke inherit). for (const d of defs) expect(d.description).toContain("`model` argument is ignored"); From bde96f6a4b09dfebdc6197cbb7d73dcf59c28854 Mon Sep 17 00:00:00 2001 From: kapu Date: Sun, 2 Aug 2026 17:51:45 +0900 Subject: [PATCH 2/2] fix(claude): preserve marked selectors without metadata --- src/claude/agents-inject.ts | 13 +++++----- tests/claude-agents-inject.test.ts | 38 ++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/claude/agents-inject.ts b/src/claude/agents-inject.ts index 6ebb7e9652..47508b7bdd 100644 --- a/src/claude/agents-inject.ts +++ b/src/claude/agents-inject.ts @@ -61,14 +61,15 @@ function pickerDefaultModel(configDir: string): string | null { */ function withSubagentContextMarker(selector: string, windows: Record): string { const bare = stripOneMillionMarker(selector); - const canonicalExact = selector === bare ? selector : `${bare}[1m]`; - const exactWindow = windows[selector] ?? windows[canonicalExact]; - if (typeof exactWindow === "number" && exactWindow > 0) { - return shouldMarkOneMillion(exactWindow, AUTO_CONTEXT_OFF) + const wasMarked = selector !== bare; + const canonicalExact = wasMarked ? `${bare}[1m]` : selector; + const authoritativeWindow = windows[selector] ?? windows[canonicalExact] ?? windows[bare]; + if (typeof authoritativeWindow === "number" && authoritativeWindow > 0) { + return shouldMarkOneMillion(authoritativeWindow, AUTO_CONTEXT_OFF) ? (withOneMillionMarker(selector, windows) ?? selector) - : stripOneMillionMarker(selector); + : bare; } - return withOneMillionMarker(bare, windows) ?? bare; + return wasMarked ? selector : bare; } /** Roster entry -> alias + display parts. Entries are bare native slugs or "provider/id". diff --git a/tests/claude-agents-inject.test.ts b/tests/claude-agents-inject.test.ts index 74e1cb7c5f..182a233e4a 100644 --- a/tests/claude-agents-inject.test.ts +++ b/tests/claude-agents-inject.test.ts @@ -144,6 +144,44 @@ describe("buildClaudeAgentDefs (devlog 070 + audit 071)", () => { }); }); + test("generated profiles preserve marked catalog selectors when context metadata is incomplete", () => { + const anthropic = structuredClone(OAUTH_PROVIDERS.anthropic.providerConfig); + anthropic.liveModels = false; + const config = cfg({ + defaultProvider: "anthropic", + providers: { anthropic }, + subagentModels: [ + "anthropic/claude-opus-4-6[1m]", + "anthropic/claude-opus-4-7[1m]", + ], + }); + const catalog = [ + { provider: "anthropic", id: "claude-opus-4-6[1m]" }, + { provider: "anthropic", id: "claude-opus-4-7[1m]" }, + ]; + const windows = buildClaudeContextWindows([], catalog); + const dir = tempDir(); + writeFileSync(join(dir, "settings.json"), JSON.stringify({ model: "claude-opus-4-7[1m]" })); + const models = (contextWindows: Record) => Object.fromEntries( + buildClaudeAgentDefs(config, contextWindows, dir).map(def => [def.name, def.model]), + ); + + expect(windows).toEqual({}); + expect(models(windows)).toEqual({ + "ocx-claude-opus-4-6-1m": "claude-opus-4-6[1m]", + "ocx-claude-opus-4-7-1m": "claude-opus-4-7[1m]", + "ocx-self": "claude-opus-4-7[1m]", + }); + expect(models({ + "claude-opus-4-6[1m]": 200_000, + "claude-opus-4-7[1m]": 200_000, + })).toEqual({ + "ocx-claude-opus-4-6-1m": "claude-opus-4-6", + "ocx-claude-opus-4-7-1m": "claude-opus-4-7", + "ocx-self": "claude-opus-4-7", + }); + }); + test("placeholder guidance recommends haiku, never sonnet (issue #252)", () => { const dir = tempDir(); writeFileSync(join(dir, "settings.json"), JSON.stringify({ model: "claude-ocx-native--gpt-5.6-sol" }));