-
Notifications
You must be signed in to change notification settings - Fork 159
CC 2.1.97 compatibility + new patches #671
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,11 +1,51 @@ | ||||||||||||||
| // Please see the note about writing patches in ./index | ||||||||||||||
| import { showDiff } from './index'; | ||||||||||||||
|
|
||||||||||||||
| import { globalReplace } from './index'; | ||||||||||||||
| /** | ||||||||||||||
| * Replaces the hardcoded default context limit (200K) with an env var override. | ||||||||||||||
| * | ||||||||||||||
| * The context window function determines the token limit for each model: | ||||||||||||||
| * ``` | ||||||||||||||
| * function TV(q,K){ | ||||||||||||||
| * if(Cf(q))return 1e6; // [1m] models → 1M | ||||||||||||||
| * if(K?.includes(ni)&&U01(q))return 1e6; // SDK beta + supported model → 1M | ||||||||||||||
| * if(cZ8(q))return 1e6; // coral_reef_sonnet experiment → 1M | ||||||||||||||
| * return eN1 // default: eN1 = 200000 | ||||||||||||||
| * } | ||||||||||||||
| * ``` | ||||||||||||||
| * | ||||||||||||||
| * We patch the final `return eN1` to read from CLAUDE_CODE_CONTEXT_LIMIT env var, | ||||||||||||||
| * falling back to the original eN1 value if unset. | ||||||||||||||
| * | ||||||||||||||
| * ```diff | ||||||||||||||
| * - return eN1 | ||||||||||||||
| * + return Number(process.env.CLAUDE_CODE_CONTEXT_LIMIT??eN1) | ||||||||||||||
| * ``` | ||||||||||||||
| */ | ||||||||||||||
| export const writeContextLimit = (file: string): string | null => { | ||||||||||||||
| // Find the context window function by its unique structure: | ||||||||||||||
| // three 1e6 returns (for special models) then a variable return (default) | ||||||||||||||
| const pattern = | ||||||||||||||
| /function ([$\w]+)\(([$\w]+),([$\w]+)\)\{if\([$\w]+\(\2\)\)return 1e6;if\(\3\?\.includes\([$\w]+\)&&[$\w]+\(\2\)\)return 1e6;if\([$\w]+\(\2\)\)return 1e6;return ([$\w]+)\}/; | ||||||||||||||
|
|
||||||||||||||
| export const writeContextLimit = (oldFile: string): string | null => { | ||||||||||||||
| return globalReplace( | ||||||||||||||
| oldFile, | ||||||||||||||
| /\b200000\b/, | ||||||||||||||
| '(+process.env.CLAUDE_CODE_CONTEXT_LIMIT||200000)' | ||||||||||||||
| ); | ||||||||||||||
| const match = file.match(pattern); | ||||||||||||||
| if (!match || match.index === undefined) { | ||||||||||||||
| console.error( | ||||||||||||||
| 'patch: contextLimit: failed to find context window function' | ||||||||||||||
| ); | ||||||||||||||
| return null; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| const defaultVar = match[4]; | ||||||||||||||
| const oldStr = `return ${defaultVar}}`; | ||||||||||||||
| const newStr = `return Number(process.env.CLAUDE_CODE_CONTEXT_LIMIT??${defaultVar})}`; | ||||||||||||||
|
Comment on lines
+39
to
+40
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard
Graceful fallback for invalid input- const newStr = `return Number(process.env.CLAUDE_CODE_CONTEXT_LIMIT??${defaultVar})}`;
+ const newStr =
+ `return ((v=>Number.isInteger(v)&&v>0?v:${defaultVar})` +
+ `(Number(process.env.CLAUDE_CODE_CONTEXT_LIMIT)))}`As per coding guidelines "Implement error handling with try-catch blocks, log errors with debug(), and return graceful fallbacks". 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| const replaceStart = match.index + match[0].length - oldStr.length; | ||||||||||||||
| const newFile = | ||||||||||||||
| file.slice(0, replaceStart) + | ||||||||||||||
| newStr + | ||||||||||||||
| file.slice(replaceStart + oldStr.length); | ||||||||||||||
|
|
||||||||||||||
| showDiff(file, newFile, newStr, replaceStart, replaceStart + oldStr.length); | ||||||||||||||
|
|
||||||||||||||
| return newFile; | ||||||||||||||
| }; | ||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| import { showDiff } from './index'; | ||
|
|
||
| // ====== | ||
|
|
||
| /** | ||
| * Disable tengu_garnet_loom — prevents auto-downgrade of Opus to Sonnet | ||
| * | ||
| * CC 2.1.97 has a feature gate `tengu_garnet_loom` that, when enabled server-side, | ||
| * auto-downgrades Opus subagents to Sonnet when context is under 200K tokens. | ||
| * | ||
| * The pattern in the subagent model resolver: | ||
| * if(!Y && Jz(j).includes("opus") && R8("tengu_garnet_loom",!1)){ | ||
| * let H=$5("sonnet"); return O(H,"sonnet") | ||
| * } | ||
| * | ||
| * This patch forces the R8("tengu_garnet_loom") check to always return false, | ||
| * ensuring Opus stays Opus regardless of what GrowthBook says. | ||
| */ | ||
| export const writeDisableGarnetLoom = (oldFile: string): string | null => { | ||
| // Pattern: R8("tengu_garnet_loom",!1) | ||
| // This appears in the subagent model resolver function | ||
| const pattern = /R8\("tengu_garnet_loom",!1\)/; | ||
|
|
||
| const match = oldFile.match(pattern); | ||
| if (!match || match.index === undefined) { | ||
| // Try generic feature gate check function names | ||
| const altPattern = /([$\w]+)\("tengu_garnet_loom",![01]\)/; | ||
| const altMatch = oldFile.match(altPattern); | ||
| if (!altMatch || altMatch.index === undefined) { | ||
| console.error( | ||
| 'patch: garnetLoom: failed to find tengu_garnet_loom feature gate check' | ||
| ); | ||
| return null; | ||
| } | ||
|
|
||
| // Replace the entire check with false | ||
| const replacement = '!1'; | ||
| const newFile = | ||
| oldFile.slice(0, altMatch.index) + | ||
| replacement + | ||
| oldFile.slice(altMatch.index + altMatch[0].length); | ||
| showDiff( | ||
| oldFile, | ||
| newFile, | ||
| replacement, | ||
| altMatch.index, | ||
| altMatch.index + altMatch[0].length | ||
| ); | ||
| return newFile; | ||
| } | ||
|
|
||
| // Replace R8("tengu_garnet_loom",!1) with !1 (always false = never downgrade) | ||
| const replacement = '!1'; | ||
| const newFile = | ||
| oldFile.slice(0, match.index) + | ||
| replacement + | ||
| oldFile.slice(match.index + match[0].length); | ||
| showDiff( | ||
| oldFile, | ||
| newFile, | ||
| replacement, | ||
| match.index, | ||
| match.index + match[0].length | ||
| ); | ||
| return newFile; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -333,6 +333,35 @@ export const findBoxComponent = (fileContents: string): string | undefined => { | |
| return boxDisplayNameMatch[1]; | ||
| } | ||
|
|
||
| // Method 4: Find Box by createElement usage near known anchor (CC 2.1.97+) | ||
| // In 2.1.97, ink-box is no longer used via createElement directly in most code. | ||
| // Instead, a Box wrapper component is assigned to a short local variable (e.g. `u`) | ||
| // and used as createElement(u, {flexDirection:...}). We find it by locating a known | ||
| // anchor (the "Claude Code" bold text in the header) and checking which variable | ||
| // is used with flexDirection props nearby. | ||
| const headerAnchor = fileContents.match( | ||
| /createElement\([$\w]+,\{bold:!0\},"Claude Code"\)/ | ||
| ); | ||
| if (headerAnchor && headerAnchor.index !== undefined) { | ||
| const searchStart = Math.max(0, headerAnchor.index - 2000); | ||
| const searchEnd = Math.min(fileContents.length, headerAnchor.index + 2000); | ||
| const region = fileContents.slice(searchStart, searchEnd); | ||
| const boxUsage = region.match( | ||
| /createElement\(([$\w]+),\{(?:flexDirection|gap|marginBottom)/ | ||
| ); | ||
| if (boxUsage) { | ||
| return boxUsage[1]; | ||
| } | ||
|
Comment on lines
+346
to
+354
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose the Box candidate closest to the header anchor. Lines 346-350 search a ±2000-char window, but Suggested fix if (headerAnchor && headerAnchor.index !== undefined) {
const searchStart = Math.max(0, headerAnchor.index - 2000);
const searchEnd = Math.min(fileContents.length, headerAnchor.index + 2000);
const region = fileContents.slice(searchStart, searchEnd);
- const boxUsage = region.match(
- /createElement\(([$\w]+),\{(?:flexDirection|gap|marginBottom)/
- );
- if (boxUsage) {
- return boxUsage[1];
+ const anchorOffset = headerAnchor.index - searchStart;
+ const boxUsages = Array.from(
+ region.matchAll(
+ /createElement\(([$\w]+),\{(?:flexDirection|gap|marginBottom)/g
+ )
+ );
+ const boxUsage =
+ boxUsages.find((match) => (match.index ?? 0) >= anchorOffset) ??
+ boxUsages.at(-1);
+ if (boxUsage) {
+ return boxUsage[1];
}
}🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| // Method 4b: Fallback — find Box wrapper by border prop destructuring | ||
| const borderBoxPattern = | ||
| /function ([$\w]+)\([$\w]+\)\{.{0,200}borderColor:[$\w]+,borderTopColor:[$\w]+,borderBottomColor:[$\w]+,borderLeftColor:[$\w]+,borderRightColor:[$\w]+,backgroundColor:[$\w]+,children:[$\w]+,ref:[$\w]+/; | ||
| const borderBoxMatch = fileContents.match(borderBoxPattern); | ||
| if (borderBoxMatch) { | ||
| return borderBoxMatch[1]; | ||
| } | ||
|
|
||
| console.error( | ||
| 'patch: findBoxComponent: failed to find Box component (neither ink-box createElement nor displayName found)' | ||
| ); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't short-circuit on
AGENTS.mdalone.This no-op ignores the actual
altNamesrequested by the caller. If a user configures extra names likeGEMINI.mdorQWEN.md, this branch can return the original bundle unchanged as soon as CC ships any built-inAGENTS.mdhandling, so the additional fallbacks never get injected.src/patches/index.ts:812-818shows this patch is driven by the fullconfig.settings.claudeMdAltNameslist.Suggested fix
📝 Committable suggestion
🤖 Prompt for AI Agents