diff --git a/.gitignore b/.gitignore index 3b7ad26d3b..991a4c08fa 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,7 @@ docs/bot-detection.md # Infisical config (user-specific) .infisical.json +.gstack/ + +# Buffbench DEBUG_ERROR dumps +evals/buffbench/*-error-*.json diff --git a/agents/__tests__/base2.test.ts b/agents/__tests__/base2.test.ts index 3e13fc3996..d86393dc0f 100644 --- a/agents/__tests__/base2.test.ts +++ b/agents/__tests__/base2.test.ts @@ -5,6 +5,7 @@ import { FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID, FREEBUFF_KIMI_MODEL_ID, FREEBUFF_MINIMAX_MODEL_ID, + FREEBUFF_MINIMAX_M3_MODEL_ID, FREEBUFF_MIMO_V25_MODEL_ID, FREEBUFF_MIMO_V25_PRO_MODEL_ID, } from '@codebuff/common/constants/freebuff-models' @@ -30,6 +31,7 @@ describe('base2 reviewer selection', () => { test.each([ [FREEBUFF_MINIMAX_MODEL_ID, 'code-reviewer-minimax'], + [FREEBUFF_MINIMAX_M3_MODEL_ID, 'code-reviewer-minimax-m3'], [FREEBUFF_KIMI_MODEL_ID, 'code-reviewer-kimi'], [FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID, 'code-reviewer-deepseek'], [FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID, 'code-reviewer-deepseek-flash'], diff --git a/agents/__tests__/editor.test.ts b/agents/__tests__/editor.test.ts index ff72e103c1..f4ed457d1b 100644 --- a/agents/__tests__/editor.test.ts +++ b/agents/__tests__/editor.test.ts @@ -26,7 +26,7 @@ describe('editor agent', () => { }) test('uses opus model by default', () => { - expect(editor.model).toBe('anthropic/claude-opus-4.7') + expect(editor.model).toBe('anthropic/claude-opus-4.8') }) test('has output mode set to structured_output', () => { @@ -52,7 +52,7 @@ describe('editor agent', () => { describe('createCodeEditor', () => { test('creates opus editor by default', () => { const opusEditor = createCodeEditor({ model: 'opus' }) - expect(opusEditor.model).toBe('anthropic/claude-opus-4.7') + expect(opusEditor.model).toBe('anthropic/claude-opus-4.8') }) test('creates gpt-5 editor', () => { diff --git a/agents/__tests__/thinker.test.ts b/agents/__tests__/thinker.test.ts index 0e44a9743e..3852a1de6f 100644 --- a/agents/__tests__/thinker.test.ts +++ b/agents/__tests__/thinker.test.ts @@ -29,7 +29,7 @@ describe('thinker agent', () => { }) test('uses opus model', () => { - expect(thinker.model).toBe('anthropic/claude-opus-4.7') + expect(thinker.model).toBe('anthropic/claude-opus-4.8') }) test('has output mode set to structured_output', () => { diff --git a/agents/base-chat.ts b/agents/base-chat.ts new file mode 100644 index 0000000000..c8a14c7492 --- /dev/null +++ b/agents/base-chat.ts @@ -0,0 +1,40 @@ +import { FREEBUFF_DEEPSEEK_V4_FLASH_FIREWORKS_MODEL_ID } from '@codebuff/common/constants/freebuff-models' + +import { publisher } from './constants' + +import type { SecretAgentDefinition } from './types/secret-agent-definition' + +/** + * Conversational agent behind freebuff.com/chat. Runs with no filesystem, but + * can spawn researcher-web to look things up on the live internet and call + * gravity_index to recommend third-party developer services. The chat server + * may override `model` per request (DeepSeek Flash vs Pro for full-access + * users). + */ +const definition: SecretAgentDefinition = { + id: 'base-chat', + publisher, + model: FREEBUFF_DEEPSEEK_V4_FLASH_FIREWORKS_MODEL_ID, + displayName: 'Freebuff Chat', + spawnerPrompt: 'General-purpose chat assistant for freebuff.com/chat.', + inputSchema: { + prompt: { + type: 'string', + description: 'The user message to respond to.', + }, + }, + outputMode: 'last_message', + toolNames: ['spawn_agents', 'gravity_index'], + spawnableAgents: ['researcher-web'], + + systemPrompt: `You are Freebuff Chat, a friendly, sharp assistant made by Freebuff (freebuff.com), the home of free AI coding tools. You are chatting with a user in a web interface that renders markdown.`, + instructionsPrompt: `Be direct and helpful. Use markdown when it improves clarity (code blocks, lists, tables), and keep answers as short as they can be while fully answering the question. + +When the user is choosing a third-party developer service (database, auth, payments, hosting, email, monitoring, analytics, AI APIs, storage, CMS, search, etc.) or asks what provider to use for something, use the gravity_index tool instead of answering from memory: \`search\` with a query that includes their stack and constraints when they want a recommendation, or \`browse\`/\`list_categories\`/\`get_service\` to explore options. Ground your answer in the result. When a search result includes a tracked setup link (\`credential_request.setup_url\` or \`click_url\`), present that exact URL prominently as a markdown link like "Get your {service} API key" — never swap in the vendor homepage for it. Since you can't edit the user's files, share the relevant setup steps and env vars in chat instead of trying to install anything. + +You can search the live internet by spawning the researcher-web agent. Spawn it whenever the answer depends on current or recent information (news, prices, releases, versions, schedules, scores, docs), whenever the user asks you to look something up, or whenever you are not confident in your knowledge. Give it a focused question; you can spawn several in parallel for independent questions. After it reports back, answer the user in your own words and cite source URLs when useful. Don't spawn it for questions you can already answer well (general knowledge, coding help, writing, math). + +You do not have access to the user's files or a filesystem — if asked to do something that requires those, say so briefly and help with what you can instead.`, +} + +export default definition diff --git a/agents/base2/base2-free-minimax-m3.ts b/agents/base2/base2-free-minimax-m3.ts new file mode 100644 index 0000000000..a1d41af84d --- /dev/null +++ b/agents/base2/base2-free-minimax-m3.ts @@ -0,0 +1,13 @@ +import { FREEBUFF_MINIMAX_M3_MODEL_ID } from '@codebuff/common/constants/freebuff-models' + +import { createBase2 } from './base2' + +const definition = { + ...createBase2('free', { + model: FREEBUFF_MINIMAX_M3_MODEL_ID, + }), + id: 'base2-free-minimax-m3', + displayName: 'Buffy the MiniMax M3 Free Orchestrator', +} + +export default definition diff --git a/agents/base2/base2-kimi-2-7-code.ts b/agents/base2/base2-kimi-2-7-code.ts new file mode 100644 index 0000000000..d5b9af70d1 --- /dev/null +++ b/agents/base2/base2-kimi-2-7-code.ts @@ -0,0 +1,13 @@ +import { moonshotModels } from '@codebuff/common/constants/model-config' + +import { createBase2 } from './base2' + +const definition = { + ...createBase2('free', { + model: moonshotModels.kimiK27Code, + }), + id: 'base2-kimi-2-7-code', + displayName: 'Buffy the Kimi K2.7 Code Orchestrator', +} + +export default definition diff --git a/agents/base2/base2.ts b/agents/base2/base2.ts index c021b722c9..f4e68b9d76 100644 --- a/agents/base2/base2.ts +++ b/agents/base2/base2.ts @@ -56,7 +56,9 @@ export function createBase2( ? deepseekModels.deepseekV4Flash : mode === 'free' ? FREEBUFF_MINIMAX_MODEL_ID - : 'anthropic/claude-opus-4.7') + : isMax + ? 'anthropic/claude-fable-5' + : 'anthropic/claude-opus-4.8') // Smart freebuff model variants (Kimi, DeepSeek) can offload deeper // reasoning. Fast MiniMax omits the extra round trip by construction. const hasFreeGeminiThinker = @@ -126,7 +128,7 @@ export function createBase2( 'basher', isDefault && 'thinker', (isDefault || isMax) && ['opus-agent', 'gpt-5-agent'], - isMax && 'thinker-best-of-n-opus', + isMax && 'thinker-best-of-n-fable', isDefault && 'editor', isMax && 'editor-multi-prompt', 'tmux-cli', @@ -204,7 +206,7 @@ Use the spawn_agents tool to spawn specialized agents to help you complete the u isDefault && '- Spawn the editor agent to implement the changes after you have gathered all the context you need.', (isDefault || isMax) && - `- Spawn the ${isDefault ? 'thinker' : 'thinker-best-of-n-opus'} after gathering context to solve complex problems or when the user asks you to think about a problem. (gpt-5-agent is a last resort for complex problems)`, + `- Spawn the ${isDefault ? 'thinker' : 'thinker-best-of-n-fable'} after gathering context to solve complex problems or when the user asks you to think about a problem. (gpt-5-agent is a last resort for complex problems)`, isMax && `- IMPORTANT: You must spawn the editor-multi-prompt agent to implement the changes after you have gathered all the context you need. You must spawn this agent for non-trivial changes, since it writes much better code than you would with the str_replace or write_file tools. Don't spawn the editor in parallel with context-gathering agents.`, isFree && @@ -601,6 +603,8 @@ function buildImplementationStepPrompt({ isFree && !noReview && `You must spawn a ${freeCodeReviewerAgentId} to review the changes after you have implemented the changes and in parallel with typechecking or testing.`, + (isDefault || isMax || (isFree && !noReview)) && + `Don't spawn a code reviewer if you haven't made code changes, e.g. when you only wrote a plan or answered a question.`, `When the user request is complete, summarize your changes in a sentence${isFast ? '' : ' or a few short bullet points'}.${isSonnet ? " Don't create any summary markdown files or example documentation files, unless asked by the user." : ''}.`, !noAskUser && `At the end of your turn, you must use the suggest_followups tool to suggest around 3 next steps the user might want to take even if the user just asks a question.`, diff --git a/agents/context-pruner.ts b/agents/context-pruner.ts index 1a333a8f09..6b87d55c23 100644 --- a/agents/context-pruner.ts +++ b/agents/context-pruner.ts @@ -54,6 +54,7 @@ const definition: AgentDefinition = { 'researcher-docs', 'basher', 'code-reviewer', + 'code-reviewer-fable', 'code-reviewer-multi-prompt', 'librarian', 'tmux-cli', diff --git a/agents/editor/best-of-n/best-of-n-selector2.ts b/agents/editor/best-of-n/best-of-n-selector2.ts index cc28b24116..4aaf5a4e94 100644 --- a/agents/editor/best-of-n/best-of-n-selector2.ts +++ b/agents/editor/best-of-n/best-of-n-selector2.ts @@ -5,25 +5,28 @@ import { } from '../../types/secret-agent-definition' export const createBestOfNSelector2 = (options: { - model: 'sonnet' | 'opus' | 'gpt-5' + model: 'sonnet' | 'opus' | 'fable' | 'gpt-5' }): Omit => { const { model } = options const isSonnet = model === 'sonnet' const isOpus = model === 'opus' + const isFable = model === 'fable' const isGpt5 = model === 'gpt-5' return { publisher, model: isSonnet ? 'anthropic/claude-sonnet-4.5' : isOpus - ? 'anthropic/claude-opus-4.7' - : 'openai/gpt-5.4', + ? 'anthropic/claude-opus-4.8' + : isFable + ? 'anthropic/claude-fable-5' + : 'openai/gpt-5.4', ...(isGpt5 && { reasoningOptions: { effort: 'high', }, }), - ...(isOpus && { + ...((isOpus || isFable) && { providerOptions: { only: ['amazon-bedrock'], }, @@ -32,7 +35,9 @@ export const createBestOfNSelector2 = (options: { ? 'Best-of-N GPT-5 Diff Selector' : isOpus ? 'Best-of-N Opus Diff Selector' - : 'Best-of-N Sonnet Diff Selector', + : isFable + ? 'Best-of-N Fable Diff Selector' + : 'Best-of-N Sonnet Diff Selector', spawnerPrompt: 'Analyzes multiple implementation proposals (as unified diffs) and selects the best one', @@ -131,7 +136,7 @@ Try to select an implementation that fulfills all the requirements in the user's ## Response Format -${isSonnet || isOpus +${isSonnet || isOpus || isFable ? `Use tags to write out your thoughts about the implementations as needed to pick the best implementation. IMPORTANT: You should think really really hard to make sure you pick the absolute best implementation! Also analyze the non-chosen implementations for any valuable techniques or approaches that could improve the selected one. Then, do not write any other explanations AT ALL. You should directly output a single tool call to set_output with the selected implementationId, short reason, and suggestedImprovements array.` @@ -141,7 +146,7 @@ Then, do not write any other explanations AT ALL. You should directly output a s } const definition: SecretAgentDefinition = { - ...createBestOfNSelector2({ model: 'opus' }), + ...createBestOfNSelector2({ model: 'fable' }), id: 'best-of-n-selector2', } diff --git a/agents/editor/best-of-n/editor-implementor-opus.ts b/agents/editor/best-of-n/editor-implementor-fable.ts similarity index 57% rename from agents/editor/best-of-n/editor-implementor-opus.ts rename to agents/editor/best-of-n/editor-implementor-fable.ts index e5d1c09649..a3f6973ee9 100644 --- a/agents/editor/best-of-n/editor-implementor-opus.ts +++ b/agents/editor/best-of-n/editor-implementor-fable.ts @@ -1,7 +1,7 @@ import { createBestOfNImplementor } from './editor-implementor' const definition = { - ...createBestOfNImplementor({ model: 'opus' }), - id: 'editor-implementor-opus', + ...createBestOfNImplementor({ model: 'fable' }), + id: 'editor-implementor-fable', } export default definition diff --git a/agents/editor/best-of-n/editor-implementor.ts b/agents/editor/best-of-n/editor-implementor.ts index 2afc66d68e..27cc3ebc01 100644 --- a/agents/editor/best-of-n/editor-implementor.ts +++ b/agents/editor/best-of-n/editor-implementor.ts @@ -3,11 +3,12 @@ import { publisher } from '../../constants' import type { SecretAgentDefinition } from '../../types/secret-agent-definition' export const createBestOfNImplementor = (options: { - model: 'sonnet' | 'opus' | 'gpt-5' | 'gemini' + model: 'sonnet' | 'opus' | 'fable' | 'gpt-5' | 'gemini' }): Omit => { const { model } = options const isSonnet = model === 'sonnet' const isOpus = model === 'opus' + const isFable = model === 'fable' const isGpt5 = model === 'gpt-5' const isGemini = model === 'gemini' @@ -16,11 +17,13 @@ export const createBestOfNImplementor = (options: { model: isSonnet ? 'anthropic/claude-sonnet-4.5' : isOpus - ? 'anthropic/claude-opus-4.7' - : isGemini - ? 'google/gemini-3-pro-preview' - : 'openai/gpt-5.1', - ...(isOpus && { + ? 'anthropic/claude-opus-4.8' + : isFable + ? 'anthropic/claude-fable-5' + : isGemini + ? 'google/gemini-3-pro-preview' + : 'openai/gpt-5.1', + ...((isOpus || isFable) && { providerOptions: { only: ['amazon-bedrock'], }, diff --git a/agents/editor/best-of-n/editor-multi-prompt.ts b/agents/editor/best-of-n/editor-multi-prompt.ts index 922fb43f22..607380d3d6 100644 --- a/agents/editor/best-of-n/editor-multi-prompt.ts +++ b/agents/editor/best-of-n/editor-multi-prompt.ts @@ -11,7 +11,7 @@ import type { SecretAgentDefinition } from '../../types/secret-agent-definition' export function createMultiPromptEditor(): Omit { return { publisher, - model: 'anthropic/claude-opus-4.7', + model: 'anthropic/claude-fable-5', providerOptions: { only: ['amazon-bedrock'], }, @@ -31,7 +31,7 @@ export function createMultiPromptEditor(): Omit { ], spawnableAgents: [ 'best-of-n-selector2', - 'editor-implementor-opus', + 'editor-implementor-fable', 'editor-implementor-gpt-5', ], @@ -94,10 +94,10 @@ function* handleStepsMultiPrompt({ includeToolCall: false, } satisfies ToolCall<'set_messages'> - // Spawn one opus implementor per prompt + // Spawn one fable implementor per prompt const implementorAgents: { agent_type: string; prompt?: string }[] = prompts.map((prompt) => ({ - agent_type: 'editor-implementor-opus', + agent_type: 'editor-implementor-fable', prompt: `Strategy: ${prompt}`, })) diff --git a/agents/editor/editor.ts b/agents/editor/editor.ts index a0cac064c6..60b649a210 100644 --- a/agents/editor/editor.ts +++ b/agents/editor/editor.ts @@ -12,7 +12,7 @@ type CodeEditorVariant = const EDITOR_MODEL_BY_VARIANT: Record = { 'gpt-5': 'openai/gpt-5.1', - opus: 'anthropic/claude-opus-4.7', + opus: 'anthropic/claude-opus-4.8', glm: 'z-ai/glm-5.1', kimi: 'moonshotai/kimi-k2.6', deepseek: 'deepseek/deepseek-v4-pro', diff --git a/agents/general-agent/general-agent.ts b/agents/general-agent/general-agent.ts index 14d12e440d..f025f5a67e 100644 --- a/agents/general-agent/general-agent.ts +++ b/agents/general-agent/general-agent.ts @@ -12,7 +12,7 @@ export const createGeneralAgent = (options: { return { publisher, - model: isGpt5 ? 'openai/gpt-5.4' : 'anthropic/claude-opus-4.7', + model: isGpt5 ? 'openai/gpt-5.4' : 'anthropic/claude-opus-4.8', ...(!isGpt5 && { providerOptions: { only: ['amazon-bedrock'], diff --git a/agents/researcher/researcher-web.ts b/agents/researcher/researcher-web.ts index 3be3071928..851b862241 100644 --- a/agents/researcher/researcher-web.ts +++ b/agents/researcher/researcher-web.ts @@ -6,7 +6,7 @@ const definition: SecretAgentDefinition = { id: 'researcher-web', publisher, model: 'google/gemini-3.1-flash-lite-preview', - displayName: 'Weeb', + displayName: 'Web Researcher', spawnerPrompt: `Browses the web to find relevant information.`, inputSchema: { prompt: { @@ -19,16 +19,19 @@ const definition: SecretAgentDefinition = { toolNames: ['web_search', 'read_url'], spawnableAgents: [], - systemPrompt: `You are an expert researcher who can search the web to find relevant information. Your goal is to answer the user's question from current search results and useful source pages. Use web_search to get Serper JSON search results. Use read_url to fetch and extract readable text from pages that would help answer the user's question.`, + systemPrompt: `You are an expert researcher who can search the web to find relevant information. Your goal is to answer the user's question from current search results and useful source pages. Use web_search to get Serper JSON search results. Use read_url to fetch and extract readable text from pages that would help answer the user's question. Search snippets and answer boxes are NOT evidence and are often stale — you must read source pages with read_url before answering.`, instructionsPrompt: `Provide comprehensive research on the user's prompt. -Use web_search to find current information. The tool returns JSON search results, so inspect the titles, links, snippets, answer boxes, and related results before deciding what to fetch next. - -Use read_url to fetch any web page that would help answer the user's question. Prefer targeted, relevant pages from the search results, especially official or primary sources. Avoid fetching pages that are unlikely to add useful evidence. +Research iteratively, in multiple rounds: +1. Start with 1-2 web_search calls. Inspect the titles, links, snippets, answer boxes, and related results. +2. Call read_url on the most promising results, especially official or primary sources. Call read_url on several pages at once, in parallel. +3. After reading, check what is still missing, uncertain, or worth verifying. Run follow-up searches with refined queries (using new terms you learned from the pages) and read more pages until the question is well covered from multiple sources. If read_url cannot handle a source, choose a different result or explain the limitation. Then, write up a concise answer that includes key findings for the user's prompt and cites source URLs when useful. + +HARD RULE: You may not write your final answer until you have successfully fetched at least 3 pages with read_url — for multi-part or comparative questions, fetch 5 or more. Search results alone are never sufficient, no matter how complete they look. If you are about to answer and have fewer than 3 read_url fetches, call read_url instead. `.trim(), } diff --git a/agents/reviewer/code-reviewer-fable.ts b/agents/reviewer/code-reviewer-fable.ts new file mode 100644 index 0000000000..b828e3f568 --- /dev/null +++ b/agents/reviewer/code-reviewer-fable.ts @@ -0,0 +1,15 @@ +import { createReviewer } from './code-reviewer' +import { publisher } from '../constants' + +import type { SecretAgentDefinition } from '../types/secret-agent-definition' + +const definition: SecretAgentDefinition = { + id: 'code-reviewer-fable', + publisher, + ...createReviewer('anthropic/claude-fable-5'), + providerOptions: { + only: ['amazon-bedrock'], + }, +} + +export default definition diff --git a/agents/reviewer/code-reviewer-minimax-m3.ts b/agents/reviewer/code-reviewer-minimax-m3.ts new file mode 100644 index 0000000000..945a9ef8a2 --- /dev/null +++ b/agents/reviewer/code-reviewer-minimax-m3.ts @@ -0,0 +1,13 @@ +import { FREEBUFF_MINIMAX_M3_MODEL_ID } from '@codebuff/common/constants/freebuff-models' + +import { publisher } from '../constants' +import type { SecretAgentDefinition } from '../types/secret-agent-definition' +import { createReviewer } from './code-reviewer' + +const definition: SecretAgentDefinition = { + id: 'code-reviewer-minimax-m3', + publisher, + ...createReviewer(FREEBUFF_MINIMAX_M3_MODEL_ID), +} + +export default definition diff --git a/agents/reviewer/code-reviewer.ts b/agents/reviewer/code-reviewer.ts index 31b261d992..8b6363ff8e 100644 --- a/agents/reviewer/code-reviewer.ts +++ b/agents/reviewer/code-reviewer.ts @@ -64,7 +64,7 @@ Be extremely concise.`, const definition: SecretAgentDefinition = { id: 'code-reviewer', publisher, - ...createReviewer('anthropic/claude-opus-4.7'), + ...createReviewer('anthropic/claude-opus-4.8'), providerOptions: { only: ['amazon-bedrock'], }, diff --git a/agents/reviewer/multi-prompt/code-reviewer-multi-prompt.ts b/agents/reviewer/multi-prompt/code-reviewer-multi-prompt.ts index e7bac906eb..773755163b 100644 --- a/agents/reviewer/multi-prompt/code-reviewer-multi-prompt.ts +++ b/agents/reviewer/multi-prompt/code-reviewer-multi-prompt.ts @@ -14,7 +14,7 @@ export function createCodeReviewerMultiPrompt(): Omit< > { return { publisher, - model: 'anthropic/claude-opus-4.7', + model: 'anthropic/claude-fable-5', providerOptions: { only: ['amazon-bedrock'], }, @@ -26,7 +26,7 @@ export function createCodeReviewerMultiPrompt(): Omit< inheritParentSystemPrompt: true, toolNames: ['spawn_agents', 'set_output'], - spawnableAgents: ['code-reviewer'], + spawnableAgents: ['code-reviewer-fable'], inputSchema: { params: { @@ -88,7 +88,7 @@ function* handleStepsMultiPrompt({ // Spawn one code-reviewer per prompt const reviewerAgents: { agent_type: string; prompt: string }[] = prompts.map( (prompt) => ({ - agent_type: 'code-reviewer', + agent_type: 'code-reviewer-fable', prompt: `Review the above code changes with the following focus: ${prompt}`, }), ) diff --git a/agents/thinker/best-of-n/thinker-best-of-n-opus.ts b/agents/thinker/best-of-n/thinker-best-of-n-fable.ts similarity index 61% rename from agents/thinker/best-of-n/thinker-best-of-n-opus.ts rename to agents/thinker/best-of-n/thinker-best-of-n-fable.ts index 811d702b73..b6f8760688 100644 --- a/agents/thinker/best-of-n/thinker-best-of-n-opus.ts +++ b/agents/thinker/best-of-n/thinker-best-of-n-fable.ts @@ -1,7 +1,7 @@ import { createThinkerBestOfN } from './thinker-best-of-n' const definition = { - ...createThinkerBestOfN('opus'), - id: 'thinker-best-of-n-opus', + ...createThinkerBestOfN('fable'), + id: 'thinker-best-of-n-fable', } export default definition diff --git a/agents/thinker/best-of-n/thinker-best-of-n.ts b/agents/thinker/best-of-n/thinker-best-of-n.ts index 5c09fae840..6b95914415 100644 --- a/agents/thinker/best-of-n/thinker-best-of-n.ts +++ b/agents/thinker/best-of-n/thinker-best-of-n.ts @@ -8,27 +8,27 @@ import type { import type { SecretAgentDefinition } from '../../types/secret-agent-definition' export function createThinkerBestOfN( - model: 'sonnet' | 'gpt-5' | 'opus', + model: 'sonnet' | 'gpt-5' | 'fable', ): Omit { const isGpt5 = model === 'gpt-5' - const isOpus = model === 'opus' + const isFable = model === 'fable' return { publisher, model: isGpt5 ? 'openai/gpt-5.1' - : isOpus - ? 'anthropic/claude-opus-4.7' + : isFable + ? 'anthropic/claude-fable-5' : 'anthropic/claude-sonnet-4.5', - ...(isOpus && { + ...(isFable && { providerOptions: { only: ['amazon-bedrock'], }, }), displayName: isGpt5 ? 'Best-of-N GPT-5 Thinker' - : isOpus - ? 'Best-of-N Opus Thinker' + : isFable + ? 'Best-of-N Fable Thinker' : 'Best-of-N Thinker', spawnerPrompt: 'Generates deep thinking by orchestrating multiple thinker agents, selects the best thinking output. Use this to help solve a hard problem. You must first gather all the relevant context *BEFORE* spawning this agent, as it can only think.', @@ -37,7 +37,7 @@ export function createThinkerBestOfN( inheritParentSystemPrompt: true, toolNames: ['spawn_agents'], - spawnableAgents: [isOpus ? 'thinker-selector-opus' : 'thinker-selector'], + spawnableAgents: [isFable ? 'thinker-selector-fable' : 'thinker-selector'], inputSchema: { prompt: { @@ -64,7 +64,7 @@ Use the tag to think deeply about the user request. When satisfied, write out a brief response to the user's request. The parent agent will see your response -- no need to call any tools. In particular, do not use the spawn_agents tool or the set_output tool or any tools at all! `, - handleSteps: isOpus ? handleStepsOpus : handleStepsDefault, + handleSteps: isFable ? handleStepsFable : handleStepsDefault, } } function* handleStepsDefault({ @@ -152,14 +152,14 @@ function* handleStepsDefault({ } } -function* handleStepsOpus({ +function* handleStepsFable({ agentState, prompt, params, }: AgentStepContext): ReturnType< NonNullable > { - const selectorAgentType = 'thinker-selector-opus' + const selectorAgentType = 'thinker-selector-fable' const n = Math.min(10, Math.max(1, (params?.n as number | undefined) ?? 3)) // Use GENERATE_N to generate n thinking outputs diff --git a/agents/thinker/best-of-n/thinker-selector-opus.ts b/agents/thinker/best-of-n/thinker-selector-fable.ts similarity index 61% rename from agents/thinker/best-of-n/thinker-selector-opus.ts rename to agents/thinker/best-of-n/thinker-selector-fable.ts index dfa99a57b7..011bb50867 100644 --- a/agents/thinker/best-of-n/thinker-selector-opus.ts +++ b/agents/thinker/best-of-n/thinker-selector-fable.ts @@ -1,8 +1,8 @@ import { createThinkerSelector } from './thinker-selector' const definition = { - ...createThinkerSelector('opus'), - id: 'thinker-selector-opus', + ...createThinkerSelector('fable'), + id: 'thinker-selector-fable', } export default definition diff --git a/agents/thinker/best-of-n/thinker-selector.ts b/agents/thinker/best-of-n/thinker-selector.ts index 62bf834208..9e5c34b8d0 100644 --- a/agents/thinker/best-of-n/thinker-selector.ts +++ b/agents/thinker/best-of-n/thinker-selector.ts @@ -2,22 +2,22 @@ import { publisher } from '../../constants' import { type SecretAgentDefinition } from '../../types/secret-agent-definition' export function createThinkerSelector( - model: 'sonnet' | 'opus', + model: 'sonnet' | 'fable', ): Omit { - const isOpus = model === 'opus' + const isFable = model === 'fable' return { publisher, - model: isOpus - ? 'anthropic/claude-opus-4.7' + model: isFable + ? 'anthropic/claude-fable-5' : 'anthropic/claude-sonnet-4.5', - ...(isOpus && { + ...(isFable && { providerOptions: { only: ['amazon-bedrock'], }, }), - displayName: isOpus - ? 'Opus Thinker Output Selector' + displayName: isFable + ? 'Fable Thinker Output Selector' : 'Thinker Output Selector', spawnerPrompt: 'Analyzes multiple thinking outputs and selects the best one', diff --git a/agents/thinker/thinker.ts b/agents/thinker/thinker.ts index 6a9f7d808d..a81297ea44 100644 --- a/agents/thinker/thinker.ts +++ b/agents/thinker/thinker.ts @@ -5,7 +5,7 @@ import type { SecretAgentDefinition } from '../types/secret-agent-definition' const definition: SecretAgentDefinition = { id: 'thinker', publisher, - model: 'anthropic/claude-opus-4.7', + model: 'anthropic/claude-opus-4.8', providerOptions: { only: ['amazon-bedrock'], }, diff --git a/agents/types/agent-definition.ts b/agents/types/agent-definition.ts index 39f8ec9bad..6315dbeb17 100644 --- a/agents/types/agent-definition.ts +++ b/agents/types/agent-definition.ts @@ -379,7 +379,9 @@ export type ModelName = | 'openai/gpt-5-nano' // Anthropic + | 'anthropic/claude-fable-5' | 'anthropic/claude-sonnet-4.6' + | 'anthropic/claude-opus-4.8' | 'anthropic/claude-opus-4.7' | 'anthropic/claude-opus-4.6' | 'anthropic/claude-opus-4.5' @@ -434,6 +436,7 @@ export type ModelName = | 'moonshotai/kimi-k2' | 'moonshotai/kimi-k2:nitro' | 'moonshotai/kimi-k2.6' + | 'moonshotai/kimi-k2.7-code' | 'z-ai/glm-5' | 'z-ai/glm-5.1' | 'z-ai/glm-4.6' diff --git a/bun.lock b/bun.lock index bc237892c8..f4a415a4f6 100644 --- a/bun.lock +++ b/bun.lock @@ -66,6 +66,7 @@ "terminal-image": "^4.1.0", "ts-pattern": "^5.9.0", "unified": "^11.0.0", + "wsl-utils": "^0.1.0", "yoga-layout": "^3.2.1", "zod": "^4.2.1", "zustand": "^5.0.8", @@ -166,6 +167,7 @@ "diff": "8.0.3", "gray-matter": "^4.0.3", "ignore": "7.0.5", + "ipaddr.js": "^1.9.1", "micromatch": "^4.0.8", "web-tree-sitter": "0.25.10", "ws": "^8.18.0", @@ -233,21 +235,25 @@ "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], - "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + "@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="], "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], - "@eslint/config-array": ["@eslint/config-array@0.23.5", "", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="], + "@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="], - "@eslint/config-helpers": ["@eslint/config-helpers@0.6.0", "", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA=="], + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], - "@eslint/core": ["@eslint/core@1.2.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="], + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], - "@eslint/object-schema": ["@eslint/object-schema@3.0.5", "", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="], + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.5", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg=="], - "@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.2", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A=="], + "@eslint/js": ["@eslint/js@9.39.4", "", {}, "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], "@gravity-ai/api": ["@gravity-ai/api@0.1.2", "", { "dependencies": { "axios": "^1.13.2" } }, "sha512-txsAhyzvwB/TNrj5R8DoNqw8afM3JY2ahl7aaeaD5ZsxP+7rxff7C7keGI7+gU2KT3d2Mcw4QB1nHhbTSCJYHw=="], @@ -381,23 +387,23 @@ "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="], - "@next/env": ["@next/env@16.2.7", "", {}, "sha512-tMJizPlj6ZYpBMMdK8S0LJufrP4QTdR6pcv9KQ/bVETPAmg0j1mlHE9G2c38UyGHxoBapgwuj7XjbGJ2RcDFOg=="], + "@next/env": ["@next/env@16.2.9", "", {}, "sha512-ki5VxxXfzD/9TDe13wyeTKIjQTAwBVpnr8KhRDUr8ltMUq1/NBpWNT5tiPoxiGl+PHM4X2ahSOiPk6iAimIzPg=="], - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vm1EDI/pVaBNNiychmxk3fft+OhQPVD9cIM/tReLZIQ3TfQ4kqI9DwKk00dzuS1ulC7icbrzCFrmRRlk9PfNdw=="], + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HkfxNYUCmcct0Xsqib5KxqMSHV4AHJq857BNRchyBDs4YS19aHzVfn1kDuBYKqLLQBjXgnkIsjV2Kd4d2wzYhw=="], - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.2.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-O3IRSv1ZBL1zs0WrIgefTEcTKFVn+ryxBNe54erJ6KsD+2f/Mmt7g2jOYh8PSBdUwPtKQJuCsTMlZ7tIu2AcsQ=="], + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.2.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-7IAtK4MeybpqRV9GRABWEhJ62mOS+rzWOzOTFie4cSEtm12xsoOMJRcECoZx3FHPzFAqN/IJtHqWAFOLfl152w=="], - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.2.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-Re6PZtjBDd0aMU+VcZcC/PrIvj4WhrjDYtMhhCVQamWN4L90EVP0pcEOBQD25prSlw7OzNw5QpHLWMilRLsRNw=="], + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.2.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-hBD75iWpUtkL9SmQmcRhmLomn9jgkPzCEkbOcLgHymPEKzv+6ONy13RRiIEz/iEObjkS2Jlb5gYS2XGoS3X4rw=="], - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.2.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-qyogG9QtBzWxgJfeGBvOEHI3851gTfCF3wLZ5RDLTBJGAmE9p1qDwKCOdrBrvBzRvYDT+gUDp72pzlSEfAXgNA=="], + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.2.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-qZTI3pf9SGc/obr8NkQAekBxmp1QK+kVm+VAf3BALLfFAj+1kUhkTxmrWpVos9R/UYIA8AWX2p6cGI5WdwzVUA=="], - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.2.7", "", { "os": "linux", "cpu": "x64" }, "sha512-Vhe4ZDuBpmMogrGi5D4R2Kq4JAQlj6+wvgaFYy31zfES0zPmt6TLA+cuYpM/OLrPZjo2MYQTHVqNUSCR6+fDZQ=="], + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.2.9", "", { "os": "linux", "cpu": "x64" }, "sha512-xm0HfRNX+UkH4R3c18ynswjj5o5uEj/7iI9p9omdtTSIsRCzQqkGMA+10nzJ4EHnYC3as65IMhbbl5fWRUWHYg=="], - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.2.7", "", { "os": "linux", "cpu": "x64" }, "sha512-srvian89JahFLw1YLBEuhvPJ0DO5lpUeJQMXy4xYo7g628ZlNgXdNkqoxSAv9OYrBfByh6vxISMwW/mRbzCY+g=="], + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.2.9", "", { "os": "linux", "cpu": "x64" }, "sha512-QumimHkGEG6vM3PfEDWKyKen03NcqLOkeKB1EfcPe7VxzmEiCa4jNnMyBn/US5zcd/VE1CI+O8Ovb3lfjVHfGw=="], - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.2.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-GX3wvLpULFuRFJzwHaKfm7QZJ18F4ZSuxlPJ96BoBglCzBmdSjyeBKF+ZhWhvL/ckxNfLnNa7bsObO2ipYpszw=="], + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.2.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-hzQpKZvw8rAwI6A2uQh6SacCSvNAXaIkPNsWwzqqfRiIMiXMfH936skDhz1OO6KpvdKkJrgHHtqQOq5PIXOvdQ=="], - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.7", "", { "os": "win32", "cpu": "x64" }, "sha512-J4WlM72NMk076Qsg0jTdK3SNXatlSdnjW7L7oNGLst1tAGjHrJh/FYi+pw9wyIjEtGRKDNzD0zuiY16oWYWVaw=="], + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.9", "", { "os": "win32", "cpu": "x64" }, "sha512-qr2VL3Ce5QrwgO2yh1ujSBawrimjVKX8FGF/cOynmdYKJY0BdHpGVNIRK1tqONB10Vkm25Ub1BD2bkjWs4+96w=="], "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], @@ -427,9 +433,9 @@ "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], - "@posthog/core": ["@posthog/core@1.30.8", "", { "dependencies": { "@posthog/types": "1.380.1" } }, "sha512-rRJxn7UjPR5LWgRwicJgHWD7tu3P2IebdWjGJ1xpXkbNqpFyW+SbSDGjhunmmXXl2c59ejOICtnbrwN6njS1lw=="], + "@posthog/core": ["@posthog/core@1.32.3", "", { "dependencies": { "@posthog/types": "1.386.3" } }, "sha512-vwOEMfZvGv5XxNWV7p9I52NSmvFNMhyW2IHpIoUHW5jLkgUrknzJW1H/qxVGSIrNNVQkfsoaDFzDhJdg10pgrA=="], - "@posthog/types": ["@posthog/types@1.380.1", "", {}, "sha512-GaeyU1vPxwZvYlSWdpxbLCRPqY2WKUZYUNjBlJHAlaAXbMmCfLgB2cvkwjidr8lhX8nyxINjjvQMiOSSfSSxcg=="], + "@posthog/types": ["@posthog/types@1.386.3", "", {}, "sha512-LqJoiQi2eyWn7rCUgnn+D+F3Efp6+04o72bjSX6kWHx0nFaYNC/nJuAIRliDTY/X7GPIUAaHAcSjbMI/9wfX1Q=="], "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], @@ -463,8 +469,6 @@ "@types/diff": ["@types/diff@8.0.0", "", { "dependencies": { "diff": "*" } }, "sha512-o7jqJM04gfaYrdCecCVMbZhNdG6T1MHg/oQoRFdERLV+4d+V7FijhiEAbFu0Usww84Yijk9yH58U4Jk4HbtzZw=="], - "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], - "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], "@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="], @@ -481,7 +485,7 @@ "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], - "@types/node": ["@types/node@22.19.19", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew=="], + "@types/node": ["@types/node@22.19.21", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-VMeFBSCKQKmm2swI2kW51SFusDqekC6q9trBCvJ/JliDchFSuoYYKN7yVNjPthP1HKZcx3U1gI/wTcEBjEFKTA=="], "@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="], @@ -527,7 +531,7 @@ "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], - "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -547,7 +551,7 @@ "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "any-base": ["any-base@1.1.0", "", {}, "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="], @@ -593,7 +597,7 @@ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.10.33", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.37", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig=="], "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], @@ -623,7 +627,9 @@ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], - "caniuse-lite": ["caniuse-lite@1.0.30001793", "", {}, "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA=="], + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001799", "", {}, "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw=="], "canvas": ["canvas@3.2.3", "", { "dependencies": { "node-addon-api": "^7.0.0", "prebuild-install": "^7.1.3" } }, "sha512-PzE5nJZPz72YUAfo8oTp0u3fqqY7IzlTubneAihqDYAUcBk7ryeCmBbdJBEdaH0bptSOe2VT2Zwcb3UaFyaSWw=="], @@ -747,7 +753,7 @@ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - "eslint": ["eslint@10.4.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.6.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw=="], + "eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="], "eslint-config-prettier": ["eslint-config-prettier@9.1.2", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ=="], @@ -759,11 +765,11 @@ "eslint-plugin-unused-imports": ["eslint-plugin-unused-imports@4.4.1", "", { "peerDependencies": { "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", "eslint": "^10.0.0 || ^9.0.0 || ^8.0.0" }, "optionalPeers": ["@typescript-eslint/eslint-plugin"] }, "sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ=="], - "eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], "eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - "espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="], + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], @@ -833,7 +839,7 @@ "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], - "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + "form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="], "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], @@ -847,7 +853,7 @@ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - "function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="], + "function.prototype.name": ["function.prototype.name@1.2.0", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2", "hasown": "^2.0.4", "is-callable": "^1.2.7", "is-document.all": "^1.0.0" } }, "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew=="], "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="], @@ -875,6 +881,8 @@ "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], "globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="], @@ -889,6 +897,8 @@ "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], "has-proto": ["has-proto@1.2.0", "", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="], @@ -899,7 +909,7 @@ "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], - "hono": ["hono@4.12.23", "", {}, "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA=="], + "hono": ["hono@4.12.25", "", {}, "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ=="], "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], @@ -919,6 +929,8 @@ "immer": ["immer@10.2.0", "", {}, "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw=="], + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], @@ -951,6 +963,8 @@ "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + "is-document.all": ["is-document.all@1.0.0", "", { "dependencies": { "call-bound": "^1.0.4" } }, "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g=="], + "is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="], "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], @@ -1035,6 +1049,8 @@ "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "log-update": ["log-update@8.0.0", "", { "dependencies": { "ansi-escapes": "^7.3.0", "cli-cursor": "^5.0.0", "slice-ansi": "^9.0.0", "string-width": "^8.2.0", "strip-ansi": "^7.2.0", "wrap-ansi": "^10.0.0" } }, "sha512-lddSgOt3bPASrylL54ZSpy8nBHns+vBVSoILlVOx+dei300pnLRN958rj/EdlVLKuWlSESU3qdnDZdAI7FXYGg=="], "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], @@ -1169,7 +1185,7 @@ "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], - "next": ["next@16.2.7", "", { "dependencies": { "@next/env": "16.2.7", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.7", "@next/swc-darwin-x64": "16.2.7", "@next/swc-linux-arm64-gnu": "16.2.7", "@next/swc-linux-arm64-musl": "16.2.7", "@next/swc-linux-x64-gnu": "16.2.7", "@next/swc-linux-x64-musl": "16.2.7", "@next/swc-win32-arm64-msvc": "16.2.7", "@next/swc-win32-x64-msvc": "16.2.7", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-eMJxgjRzBaj3olkP4cBamHDXL79A8FC6u1GcsO1D1Tsx8bw/LLXUJCaoajVxtnhD3A1IJqIT8IcRJjgBIPJq4w=="], + "next": ["next@16.2.9", "", { "dependencies": { "@next/env": "16.2.9", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.9", "@next/swc-darwin-x64": "16.2.9", "@next/swc-linux-arm64-gnu": "16.2.9", "@next/swc-linux-arm64-musl": "16.2.9", "@next/swc-linux-x64-gnu": "16.2.9", "@next/swc-linux-x64-musl": "16.2.9", "@next/swc-win32-arm64-msvc": "16.2.9", "@next/swc-win32-x64-msvc": "16.2.9", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-MEOJiq/UvuezAdqVSceHbqDgZt1kDw2tpGVOlsdIoJsQdbN2JY2hpVG4xnXGkbdJUOEWhnRfiu/O4Hpc9Juwww=="], "next-auth": ["next-auth@4.24.14", "", { "dependencies": { "@babel/runtime": "^7.20.13", "@panva/hkdf": "^1.0.2", "cookie": "^0.7.0", "jose": "^4.15.5", "oauth": "^0.9.15", "openid-client": "^5.4.0", "preact": "^10.6.3", "preact-render-to-string": "^5.1.19", "uuid": "^8.3.2" }, "peerDependencies": { "@auth/core": "0.34.3", "next": "^12.2.5 || ^13 || ^14 || ^15 || ^16", "nodemailer": "^7.0.7", "react": "^17.0.2 || ^18 || ^19", "react-dom": "^17.0.2 || ^18 || ^19" }, "optionalPeers": ["@auth/core", "nodemailer"] }, "sha512-YRz6xFDXKUwiXSMMChbrBEWyFktZ1qZXEgeSHQQ3nsy08B4c/xLk6REeutRsIFwkjY/1+ShHnu07DN3JeJguig=="], @@ -1237,6 +1253,8 @@ "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + "parse-bmfont-ascii": ["parse-bmfont-ascii@1.0.6", "", {}, "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA=="], "parse-bmfont-binary": ["parse-bmfont-binary@1.0.6", "", {}, "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA=="], @@ -1307,7 +1325,7 @@ "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], - "posthog-node": ["posthog-node@5.36.2", "", { "dependencies": { "@posthog/core": "1.30.8" }, "peerDependencies": { "rxjs": "^7.0.0" }, "optionalPeers": ["rxjs"] }, "sha512-k+URjhZyxR0PJ92JZkYcgyk7+2U+T8r0fnfsQFNkW4GeKcuYH6t13VLzjI+bH4YLSknUuLmDDg4CczGO9nad2Q=="], + "posthog-node": ["posthog-node@5.37.0", "", { "dependencies": { "@posthog/core": "^1.32.3" }, "peerDependencies": { "rxjs": "^7.0.0" }, "optionalPeers": ["rxjs"] }, "sha512-wFwWGcqAqZ1WJRlNNYc92veV83d1lOQcP4Lq0q7Kar9GdZLPpiFYHeudyybYJnjZjkI9v06vLvY/Og5CZIfByg=="], "preact": ["preact@10.29.2", "", {}, "sha512-7tNmwg/7mzzAoB/8kSg6Hl37JraAZw3Z3A0JSY7VXlZwo82Xn0G7wKbNNs2qoF4ZEEsQGTwDAroNdqKs1ofJxQ=="], @@ -1317,7 +1335,7 @@ "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - "prettier": ["prettier@3.8.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw=="], + "prettier": ["prettier@3.8.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q=="], "pretty-format": ["pretty-format@3.8.0", "", {}, "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="], @@ -1381,6 +1399,8 @@ "resolve": ["resolve@2.0.0-next.7", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.2", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ=="], + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], @@ -1413,7 +1433,7 @@ "seedrandom": ["seedrandom@3.0.5", "", {}, "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="], - "semver": ["semver@7.8.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ=="], + "semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="], "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], @@ -1435,7 +1455,7 @@ "shell-quote": ["shell-quote@1.8.4", "", {}, "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ=="], - "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + "side-channel": ["side-channel@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ=="], "side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="], @@ -1469,9 +1489,9 @@ "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], - "string.prototype.trim": ["string.prototype.trim@1.2.10", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-object-atoms": "^1.0.0", "has-property-descriptors": "^1.0.2" } }, "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA=="], + "string.prototype.trim": ["string.prototype.trim@1.2.11", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.2", "es-object-atoms": "^1.1.2", "has-property-descriptors": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w=="], - "string.prototype.trimend": ["string.prototype.trimend@1.0.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ=="], + "string.prototype.trimend": ["string.prototype.trimend@1.0.10", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.2" } }, "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw=="], "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="], @@ -1485,7 +1505,7 @@ "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], - "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], "stripe": ["stripe@16.12.0", "", { "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" } }, "sha512-H7eFVLDxeTNNSn4JTRfL2//LzCbDrMSZ+2q1c7CanVWgK2qIW5TwS+0V7N9KcKZZNpYh/uCqK0PyZh/2UsaAtQ=="], @@ -1493,6 +1513,8 @@ "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], "supports-terminal-graphics": ["supports-terminal-graphics@0.1.0", "", {}, "sha512-+KdfozhS0Fw8y5Sghw8kkZNGT8nWYzJ1EzcoIvVjxhl+26TJTs26y02yfBgvc1jh5AS/c8jcI3xtahhR95KRyQ=="], @@ -1595,7 +1617,7 @@ "which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="], - "which-typed-array": ["which-typed-array@1.1.21", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw=="], + "which-typed-array": ["which-typed-array@1.1.22", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw=="], "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], @@ -1651,12 +1673,18 @@ "@codebuff/sdk/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], - "@eslint/config-array/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + "@eslint/eslintrc/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], + + "@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "@eslint/eslintrc/js-yaml": ["js-yaml@4.2.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw=="], "@opentui/core/diff": ["diff@9.0.0", "", {}, "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw=="], "@opentui/core/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + "@types/diff/diff": ["diff@9.0.0", "", {}, "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw=="], + "@typescript-eslint/eslint-plugin/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0" } }, "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA=="], @@ -1685,11 +1713,11 @@ "eslint/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], - "eslint/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "eslint/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "eslint/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], - "eslint/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + "eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], @@ -1701,7 +1729,7 @@ "eslint-plugin-import/tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="], - "espree/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "espree/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], "execa/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], @@ -1729,12 +1757,16 @@ "plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], + "rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + "react-devtools-core/ws": ["ws@7.5.11", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA=="], "react-reconciler/scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], "send/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + "slice-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], "ts-node/diff": ["diff@4.0.4", "", {}, "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ=="], @@ -1749,6 +1781,8 @@ "typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@7.18.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw=="], + "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "wrap-ansi/string-width": ["string-width@8.2.1", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA=="], "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -1757,7 +1791,9 @@ "@codebuff/evals/pino/process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="], - "@eslint/config-array/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], + "@eslint/eslintrc/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "@eslint/eslintrc/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], @@ -1771,14 +1807,10 @@ "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "eslint-plugin-import/tsconfig-paths/json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], "eslint/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - "eslint/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], - "express/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], "p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], @@ -1807,12 +1839,8 @@ "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "@eslint/config-array/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], - "eslint/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@7.18.0", "", {}, "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ=="], "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^1.3.0" } }, "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA=="], diff --git a/cli/package.json b/cli/package.json index ba2373d5e4..82ecda523f 100644 --- a/cli/package.json +++ b/cli/package.json @@ -52,6 +52,7 @@ "terminal-image": "^4.1.0", "ts-pattern": "^5.9.0", "unified": "^11.0.0", + "wsl-utils": "^0.1.0", "yoga-layout": "^3.2.1", "zod": "^4.2.1", "zustand": "^5.0.8" diff --git a/cli/release/index.js b/cli/release/index.js index f751a07807..13eb3d6773 100644 --- a/cli/release/index.js +++ b/cli/release/index.js @@ -718,6 +718,11 @@ function printSpawnFailure(err) { console.error('') } +function exitOnSpawnFailure(err) { + printSpawnFailure(err) + process.exit(1) +} + function spawnInstalledBinary(options = {}) { if (!fs.existsSync(CONFIG.binaryPath)) { try { @@ -729,19 +734,24 @@ function spawnInstalledBinary(options = {}) { `downloaded binary is missing at ${CONFIG.binaryPath}`, ) error.code = 'BINARY_MISSING' - printSpawnFailure(error) - process.exit(1) + exitOnSpawnFailure(error) } - const child = spawn(CONFIG.binaryPath, process.argv.slice(2), { - stdio: 'inherit', - ...options, - }) + // spawn() only emits 'error' asynchronously for a few errno values + // (EACCES, EAGAIN, EMFILE, ENFILE, ENOENT); everything else — notably + // UNKNOWN on Windows when antivirus or Smart App Control blocks the + // exe or the download is corrupt — is thrown synchronously. + let child + try { + child = spawn(CONFIG.binaryPath, process.argv.slice(2), { + stdio: 'inherit', + ...options, + }) + } catch (err) { + exitOnSpawnFailure(err) + } - child.on('error', (err) => { - printSpawnFailure(err) - process.exit(1) - }) + child.on('error', exitOnSpawnFailure) return child } diff --git a/cli/release/package.json b/cli/release/package.json index 67cae0188e..f899e9c756 100644 --- a/cli/release/package.json +++ b/cli/release/package.json @@ -1,6 +1,6 @@ { "name": "codebuff", - "version": "1.0.680", + "version": "1.0.681", "description": "AI coding agent", "license": "MIT", "bin": { diff --git a/cli/src/chat.tsx b/cli/src/chat.tsx index a5d6b702f4..799601f725 100644 --- a/cli/src/chat.tsx +++ b/cli/src/chat.tsx @@ -13,7 +13,7 @@ import { useShallow } from 'zustand/react/shallow' import { getAdsEnabled } from './commands/ads' import { routeUserPrompt, addBashMessageToHistory } from './commands/router' -import { ChoiceAdBanner } from './components/choice-ad-banner' +import { SingleAdBanner } from './components/ad-banner' import { ChatInputBar } from './components/chat-input-bar' import { FreebuffActiveSessionSummary } from './components/freebuff-active-session-summary' import { LoadPreviousButton } from './components/load-previous-button' @@ -1466,9 +1466,9 @@ export const Chat = ({ /> )} - {ads && (IS_FREEBUFF || getAdsEnabled()) && ( - diff --git a/cli/src/components/__tests__/choice-ad-banner.test.tsx b/cli/src/components/__tests__/ad-banner.test.tsx similarity index 83% rename from cli/src/components/__tests__/choice-ad-banner.test.tsx rename to cli/src/components/__tests__/ad-banner.test.tsx index b787c97709..36b3aa04c4 100644 --- a/cli/src/components/__tests__/choice-ad-banner.test.tsx +++ b/cli/src/components/__tests__/ad-banner.test.tsx @@ -1,8 +1,8 @@ import { describe, expect, test } from 'bun:test' -import { getAdDisplayLabel } from '../choice-ad-banner' +import { getAdDisplayLabel } from '../ad-banner' -describe('choice ad banner display label', () => { +describe('ad banner display label', () => { test('uses the display domain when the ad has a URL', () => { expect( getAdDisplayLabel({ diff --git a/cli/src/components/__tests__/multiline-input.test.tsx b/cli/src/components/__tests__/multiline-input.test.tsx index a024ad9d3e..7fcf7eaa17 100644 --- a/cli/src/components/__tests__/multiline-input.test.tsx +++ b/cli/src/components/__tests__/multiline-input.test.tsx @@ -677,6 +677,7 @@ describe('MultilineInput - newline keyboard shortcuts', () => { meta?: boolean shift?: boolean option?: boolean + source?: 'raw' | 'kitty' }, hasBackslashBeforeCursor: boolean = false, ): 'newline' | 'submit' | 'ignore' { @@ -697,7 +698,9 @@ describe('MultilineInput - newline keyboard shortcuts', () => { if (!isEnterKey && !isCtrlJ) return 'ignore' const isAltLikeModifier = isAltModifier(key) + const isKittyKey = key.source === 'kitty' const hasEscapePrefix = + !isKittyKey && typeof key.sequence === 'string' && key.sequence.length > 0 && key.sequence.charCodeAt(0) === 0x1b @@ -710,7 +713,7 @@ describe('MultilineInput - newline keyboard shortcuts', () => { !key.option && !isAltLikeModifier && (!hasEscapePrefix || keypadEnter) && - (key.sequence === '\r' || keypadEnter) && + (key.sequence === '\r' || keypadEnter || isKittyKey) && !hasBackslashBeforeCursor const isShiftEnter = isEnterKey && (Boolean(key.shift) || key.sequence === '\n') @@ -984,6 +987,67 @@ describe('MultilineInput - newline keyboard shortcuts', () => { expect(getEnterKeyAction(key, false)).toBe('submit') }) + // --- Kitty keyboard protocol tests --- + // Some terminals (e.g. VSCode-fork embedded terminals on Linux) encode a + // plain Enter press as CSI u (\x1b[13u) once the kitty protocol is active. + // The escape prefix must not be mistaken for Alt+Enter in that case. + + test('Kitty CSI-u plain Enter submits', () => { + const key = { + name: 'return', + sequence: '\x1b[13u', + ctrl: false, + meta: false, + shift: false, + option: false, + source: 'kitty' as const, + } + + expect(getEnterKeyAction(key, false)).toBe('submit') + }) + + test('Kitty Shift+Enter inserts newline', () => { + const key = { + name: 'return', + sequence: '\x1b[13;2u', + ctrl: false, + meta: false, + shift: true, + option: false, + source: 'kitty' as const, + } + + expect(getEnterKeyAction(key)).toBe('newline') + }) + + test('Kitty Alt+Enter inserts newline', () => { + const key = { + name: 'return', + sequence: '\x1b[13;3u', + ctrl: false, + meta: true, + shift: false, + option: true, + source: 'kitty' as const, + } + + expect(getEnterKeyAction(key)).toBe('newline') + }) + + test('Kitty plain Enter with backslash before cursor inserts newline', () => { + const key = { + name: 'return', + sequence: '\x1b[13u', + ctrl: false, + meta: false, + shift: false, + option: false, + source: 'kitty' as const, + } + + expect(getEnterKeyAction(key, true)).toBe('newline') + }) + // --- Non-Enter key tests --- test('Regular J key (no ctrl) is ignored', () => { diff --git a/cli/src/components/ad-banner.tsx b/cli/src/components/ad-banner.tsx new file mode 100644 index 0000000000..05d527bb86 --- /dev/null +++ b/cli/src/components/ad-banner.tsx @@ -0,0 +1,217 @@ +import { TextAttributes } from '@opentui/core' +import { safeOpen } from '../utils/open-url' +import React, { useState, useMemo, useEffect } from 'react' + +import { Button } from './button' +import { useTerminalDimensions } from '../hooks/use-terminal-dimensions' +import { useTheme } from '../hooks/use-theme' +import { BORDER_CHARS } from '../utils/ui-constants' + +import type { AdResponse } from '../hooks/use-gravity-ad' + +interface ChoiceAdBannerProps { + ads: AdResponse[] + onClick?: (ad: AdResponse) => void + onImpression?: (ad: AdResponse) => void +} + +export const AD_CARD_HEIGHT = 5 // border-top + 2 lines description + spacer + cta row + border-bottom +const MAX_DESC_LINES = 2 +const MIN_CARD_WIDTH = 60 // Minimum width per ad card to remain readable + +function truncateToLines(text: string, lineWidth: number, maxLines: number): string { + if (lineWidth <= 0) return text + const maxChars = lineWidth * maxLines + if (text.length <= maxChars) return text + return text.slice(0, maxChars - 1) + '…' +} + +function truncateToWidth(text: string, width: number): string { + if (width <= 0) return '' + if (text.length <= width) return text + return text.slice(0, width - 1) + '…' +} + +export const extractDomain = (url: string): string => { + try { + const parsed = new URL(url) + return parsed.hostname.replace(/^www\./, '') + } catch { + return url + } +} + +export function getAdDisplayLabel( + ad: Pick, +): { text: string; variant: 'domain' | 'title' } { + const url = ad.url.trim() + if (url) { + return { text: extractDomain(url), variant: 'domain' } + } + + return { text: ad.title.trim() || 'Sponsored', variant: 'title' } +} + +/** + * Calculate evenly distributed column widths that sum exactly to availableWidth. + * Distributes remainder pixels across the first N columns so there's no gap. + */ +function columnWidths(count: number, availableWidth: number): number[] { + const base = Math.floor(availableWidth / count) + const remainder = availableWidth - base * count + return Array.from({ length: count }, (_, i) => base + (i < remainder ? 1 : 0)) +} + +/** + * A single ad card. Used full-width by {@link SingleAdBanner} (chat) and in a + * row of columns by {@link ChoiceAdBanner} (waiting room). Manages its own + * hover state so each card highlights independently. + */ +const AdCard: React.FC<{ + ad: AdResponse + width: number + onClick?: (ad: AdResponse) => void +}> = ({ ad, width, onClick }) => { + const theme = useTheme() + const [isHovered, setIsHovered] = useState(false) + + const ctaText = ad.cta || ad.title || 'Learn more' + const label = getAdDisplayLabel(ad) + const labelMaxWidth = Math.max(0, width - ctaText.length - 5) + const labelText = truncateToWidth(label.text, labelMaxWidth) + + return ( + + ) +} + +/** + * Single ad shown below the chat response. The ad-placement experiment found + * one ad outperformed the multi-ad "choice" format, so the chat surface always + * renders exactly one ad spanning the full width. + */ +export const SingleAdBanner: React.FC<{ + ad: AdResponse + onClick?: (ad: AdResponse) => void + onImpression?: (ad: AdResponse) => void +}> = ({ ad, onClick, onImpression }) => { + const { terminalWidth } = useTerminalDimensions() + + // Full width minus left/right margin of 1 each. + const width = terminalWidth - 2 + + useEffect(() => { + onImpression?.(ad) + }, [ad, onImpression]) + + return ( + + + + ) +} + +/** + * Up to four ads shown in a row. Still used by the freebuff waiting room, which + * intentionally fills the space with multiple ads. + */ +export const ChoiceAdBanner: React.FC = ({ + ads, + onClick, + onImpression, +}) => { + const { terminalWidth } = useTerminalDimensions() + + // Available width for cards (terminal minus left/right margin of 1 each) + const colAvail = terminalWidth - 2 + + // Only show as many ads as fit with a healthy minimum width; hide the rest + const maxVisible = Math.max(1, Math.floor(colAvail / MIN_CARD_WIDTH)) + const visibleAds = useMemo( + () => (ads.length > maxVisible ? ads.slice(0, maxVisible) : ads), + [ads, maxVisible], + ) + + const widths = useMemo(() => columnWidths(visibleAds.length, colAvail), [visibleAds.length, colAvail]) + + // Fire impressions only for visible ads + useEffect(() => { + if (onImpression) { + for (const ad of visibleAds) { + onImpression(ad) + } + } + }, [visibleAds, onImpression]) + + return ( + + {/* Card columns */} + + {visibleAds.map((ad, i) => ( + + ))} + + + ) +} diff --git a/cli/src/components/chat-input-bar.tsx b/cli/src/components/chat-input-bar.tsx index a95b8cbfb4..8f49844f36 100644 --- a/cli/src/components/chat-input-bar.tsx +++ b/cli/src/components/chat-input-bar.tsx @@ -1,3 +1,7 @@ +import { + isShallowScanRoot, + SHALLOW_SCAN_MAX_DEPTH, +} from '@codebuff/common/project-file-tree' import React from 'react' import { AgentModeToggle } from './agent-mode-toggle' @@ -10,6 +14,7 @@ import { PublishContainer } from './publish-container' import { SuggestionMenu, type SuggestionItem } from './suggestion-menu' import { useAskUserBridge } from '../hooks/use-ask-user-bridge' import { useEvent } from '../hooks/use-event' +import { tryGetProjectRoot } from '../project-files' import { useChatStore } from '../state/chat-store' import { shouldInterceptChatInputKey } from '../utils/chat-input-key-intercept' import { getInputModeConfig } from '../utils/input-modes' @@ -118,6 +123,12 @@ export const ChatInputBar = ({ const askUserState = useChatStore((state) => state.askUserState) const hasAnyPreview = hasSuggestionMenu + // In the home directory (or an ancestor) the file tree is only scanned a few + // levels deep, so tell the user why deeper files don't show up. + const mentionMenuFooter = isShallowScanRoot(tryGetProjectRoot()) + ? `Files shown up to ${SHALLOW_SCAN_MAX_DEPTH} levels deep — open a project folder for full results` + : undefined + // Increase menu size on larger screen heights const normalModeMaxVisible = terminalHeight > 35 ? 15 : 10 const { submitAnswers, skip } = useAskUserBridge() @@ -311,6 +322,7 @@ export const ChatInputBar = ({ maxVisible={5} prefix="@" onItemClick={onMentionItemClick} + footer={mentionMenuFooter} /> ) : null} ) : null} void - onImpression?: (ad: AdResponse) => void -} - -export const CHOICE_AD_BANNER_HEIGHT = 5 // border-top + 2 lines description + spacer + cta row + border-bottom -const MAX_DESC_LINES = 2 -const MIN_CARD_WIDTH = 60 // Minimum width per ad card to remain readable - -function truncateToLines(text: string, lineWidth: number, maxLines: number): string { - if (lineWidth <= 0) return text - const maxChars = lineWidth * maxLines - if (text.length <= maxChars) return text - return text.slice(0, maxChars - 1) + '…' -} - -function truncateToWidth(text: string, width: number): string { - if (width <= 0) return '' - if (text.length <= width) return text - return text.slice(0, width - 1) + '…' -} - -export const extractDomain = (url: string): string => { - try { - const parsed = new URL(url) - return parsed.hostname.replace(/^www\./, '') - } catch { - return url - } -} - -export function getAdDisplayLabel( - ad: Pick, -): { text: string; variant: 'domain' | 'title' } { - const url = ad.url.trim() - if (url) { - return { text: extractDomain(url), variant: 'domain' } - } - - return { text: ad.title.trim() || 'Sponsored', variant: 'title' } -} - -/** - * Calculate evenly distributed column widths that sum exactly to availableWidth. - * Distributes remainder pixels across the first N columns so there's no gap. - */ -function columnWidths(count: number, availableWidth: number): number[] { - const base = Math.floor(availableWidth / count) - const remainder = availableWidth - base * count - return Array.from({ length: count }, (_, i) => base + (i < remainder ? 1 : 0)) -} - -export const ChoiceAdBanner: React.FC = ({ - ads, - onClick, - onImpression, -}) => { - const theme = useTheme() - const { terminalWidth } = useTerminalDimensions() - const [hoveredIndex, setHoveredIndex] = useState(null) - - // Available width for cards (terminal minus left/right margin of 1 each) - const colAvail = terminalWidth - 2 - - // Only show as many ads as fit with a healthy minimum width; hide the rest - const maxVisible = Math.max(1, Math.floor(colAvail / MIN_CARD_WIDTH)) - const visibleAds = useMemo( - () => (ads.length > maxVisible ? ads.slice(0, maxVisible) : ads), - [ads, maxVisible], - ) - - const widths = useMemo(() => columnWidths(visibleAds.length, colAvail), [visibleAds.length, colAvail]) - - // Fire impressions only for visible ads - useEffect(() => { - if (onImpression) { - for (const ad of visibleAds) { - onImpression(ad) - } - } - }, [visibleAds, onImpression]) - - const hoverBorderColor = theme.primary - - return ( - - {/* Card columns */} - - {visibleAds.map((ad, i) => { - const isHovered = hoveredIndex === i - const ctaText = ad.cta || ad.title || 'Learn more' - const label = getAdDisplayLabel(ad) - const labelMaxWidth = Math.max(0, widths[i] - ctaText.length - 5) - const labelText = truncateToWidth(label.text, labelMaxWidth) - - return ( - - ) - })} - - - - - ) -} diff --git a/cli/src/components/freebuff-active-session-summary.tsx b/cli/src/components/freebuff-active-session-summary.tsx index f22be89fb5..712c755888 100644 --- a/cli/src/components/freebuff-active-session-summary.tsx +++ b/cli/src/components/freebuff-active-session-summary.tsx @@ -36,7 +36,10 @@ export const FreebuffActiveSessionSummary: React.FC< 'accessTier' in session && session.accessTier === 'limited' ? 'sessions' : 'premium sessions' - + // recentCount already includes the active session's 1.0-unit reservation + // (written as an admit row at promotion), so it reflects everything counted + // against the quota — spent plus in-flight. Show it as the total used to match + // the model selection menu and the other session-status screens. return ( {' '} - {label} used today · resets in {resetCountdown} + {label} used · resets in {resetCountdown} diff --git a/cli/src/components/freebuff-model-selector.tsx b/cli/src/components/freebuff-model-selector.tsx index 0f944ad5a3..b063a58094 100644 --- a/cli/src/components/freebuff-model-selector.tsx +++ b/cli/src/components/freebuff-model-selector.tsx @@ -11,6 +11,7 @@ import React, { import { Button } from './button' import { FALLBACK_FREEBUFF_MODEL_ID, + FREEBUFF_PREMIUM_SESSION_LIMIT, getFreebuffDeploymentAvailabilityLabel, getFreebuffModelsForAccessTier, isFreebuffModelAvailable, @@ -28,6 +29,10 @@ import { freebuffModelNavigationDirectionForKey, nextFreebuffModelId, } from '../utils/freebuff-model-navigation' +import { + formatFreebuffPremiumResetCountdown, + getFreebuffPremiumResetAt, +} from '../utils/freebuff-premium-reset' import { isPlainEnterKey } from '../utils/terminal-enter-detection' import type { FreebuffModelOption } from '@codebuff/common/constants/freebuff-models' @@ -36,10 +41,12 @@ import type { KeyEvent, ScrollBoxRenderable } from '@opentui/core' // Section grouping: model rows keep their product/availability tiers, but all // selectable Freebuff models share the same daily session quota. // Putting the tier on a section header lets each row drop its redundant -// "Premium"/"Unlimited" chip. The shared 0/5 counter lives in the page title -// (rendered by the parent), not the section header — this picker is purely a -// list of choices grouped by tier. Empty sections are filtered so a model set -// with no premium (or no unlimited) entries doesn't render an orphan header. +// "Premium"/"Unlimited" chip. The shared 0/5 counter renders below the picker +// (by the parent, landing only); headers carry only what the counter can't: +// "no daily limit" on UNLIMITED, and a reset countdown on PREMIUM once the +// quota is exhausted (the moment its rows grey out). Empty sections are +// filtered so a model set with no premium (or no unlimited) entries doesn't +// render an orphan header. // // `label` may be empty: limited-tier users only see the constrained model set, // so the "LIMITED" header would just leak the internal tier name without @@ -62,8 +69,8 @@ type Section = { * Space) commits the focused row. Mouse click commits in one step. * * Layout: rows are grouped into PREMIUM / UNLIMITED sections so the tier is - * visible without a per-row chip; the shared 0/5 counter sits inside the - * PREMIUM section header. Names align in a column so taglines line up across + * visible without a per-row chip; the shared 0/5 counter renders below the + * picker (by the parent). Names align in a column so taglines line up across * rows. On narrow terminals the secondary details (warning / deployment * hours) drop onto an indented second line under the row. * @@ -170,6 +177,23 @@ export const FreebuffModelSelector: React.FC = ({ const committedModelId = session?.status === 'queued' ? session.model : null const rateLimitsByModel = getRateLimitsByModel(session) + // PREMIUM-header reset countdown, shown only once the shared quota is + // exhausted — that's when the premium rows grey out, and (in the queued + // state) the only place that explains why. All premium models share one + // pool; the server replicates the same snapshot under every model id, so + // any entry has the right count. + const sharedRateLimit = rateLimitsByModel + ? Object.values(rateLimitsByModel)[0] + : undefined + const premiumExhausted = + (sharedRateLimit?.recentCount ?? 0) >= FREEBUFF_PREMIUM_SESSION_LIMIT + const premiumResetCountdown = premiumExhausted + ? formatFreebuffPremiumResetCountdown( + getFreebuffPremiumResetAt({ rateLimitsByModel, nowMs: now }), + now, + ) + : null + const BUTTON_CHROME = 4 // 2 border + 2 padding const NAME_GAP = 2 // spaces between name column and details column @@ -455,8 +479,22 @@ export const FreebuffModelSelector: React.FC = ({ marginTop: sectionIdx === 0 ? 0 : SECTION_GAP, }} > + {/* wrapMode 'none' pins headers to one row — the offset math above + assumes exactly 1 row per header, so a wrap would desync the + focused-row auto-scroll. */} {section.label && ( - {section.label} + + {section.label} + {section.key === 'premium' && premiumResetCountdown && ( + + {' '} + · resets in {premiumResetCountdown} + + )} + {section.key === 'unlimited' && ( + · no daily limit + )} + )} {section.models.map(renderModelButton)} diff --git a/cli/src/components/help-banner.tsx b/cli/src/components/help-banner.tsx index ccf39bdf82..f62983dda4 100644 --- a/cli/src/components/help-banner.tsx +++ b/cli/src/components/help-banner.tsx @@ -93,6 +93,10 @@ export const HelpBanner = () => { Use @ to reference agents to spawn or files to read + + Drag to select text — it copies automatically (or click ⎘ on a + message) + Esc to cancel the current response diff --git a/cli/src/components/login-modal.tsx b/cli/src/components/login-modal.tsx index aa0a9f7b89..a03716985b 100644 --- a/cli/src/components/login-modal.tsx +++ b/cli/src/components/login-modal.tsx @@ -1,5 +1,11 @@ import { useRenderer } from '@opentui/react' -import React, { useCallback, useEffect, useRef, useState } from 'react' +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react' import { Button } from './button' import { useLoginMutation } from '../hooks/use-auth-query' @@ -209,11 +215,13 @@ export const LoginModal = ({ maxUrlWidth, } = calculateResponsiveLayout(terminalWidth, terminalHeight) - // Format login URL lines - const formatLoginUrlLines = useCallback( - (text: string, width?: number) => formatUrl(text, width ?? maxUrlWidth), - [maxUrlWidth], + const loginUrlLines = useMemo( + () => (loginUrl ? formatUrl(loginUrl, maxUrlWidth) : []), + [loginUrl, maxUrlWidth], ) + // A wrapped URL is a trap: terminal link detection and drag-select only + // capture the first row, so the auth code arrives truncated. + const loginUrlWrapped = loginUrlLines.length > 1 // Use custom hook for sheen animation const blockColor = getLogoBlockColor(theme.name) @@ -381,7 +389,7 @@ export const LoginModal = ({ alignItems: 'flex-start', }} > - {formatLoginUrlLines(loginUrl, maxUrlWidth).map((line, index) => ( + {loginUrlLines.map((line, index) => ( ))} + {loginUrlWrapped && ( + + + ⚠ The link wraps across lines — clicking it will cut it off. + Press c to copy the full link instead. + + + )} 0 && key.sequence.charCodeAt(0) === 0x1b @@ -589,7 +595,10 @@ export const MultilineInput = forwardRef< !key.option && !isAltLikeModifier && (!hasEscapePrefix || keypadEnter) && - (key.sequence === '\r' || key.sequence === '\n' || keypadEnter) && + (key.sequence === '\r' || + key.sequence === '\n' || + keypadEnter || + isKittyKey) && !hasBackslashBeforeCursor const isShiftEnter = isEnterKey && Boolean(key.shift) const isOptionEnter = diff --git a/cli/src/components/suggestion-menu.tsx b/cli/src/components/suggestion-menu.tsx index a0302083b3..90350efcdd 100644 --- a/cli/src/components/suggestion-menu.tsx +++ b/cli/src/components/suggestion-menu.tsx @@ -19,6 +19,8 @@ interface SuggestionMenuProps { maxVisible: number prefix?: string onItemClick?: (index: number) => void + /** Muted hint line rendered below the suggestions */ + footer?: string } export const SuggestionMenu = ({ @@ -27,6 +29,7 @@ export const SuggestionMenu = ({ maxVisible, prefix = '/', onItemClick, + footer, }: SuggestionMenuProps) => { const theme = useTheme() const { terminalWidth } = useTerminalDimensions() @@ -202,6 +205,11 @@ export const SuggestionMenu = ({ onMouseOut={() => setHoveredIndex(null)} > {visibleItems.map(renderSuggestionItem)} + {footer ? ( + + {footer} + + ) : null} ) } diff --git a/cli/src/components/tools/__tests__/gravity-index.test.ts b/cli/src/components/tools/__tests__/gravity-index.test.ts index d2e5e95b01..21e6319ffc 100644 --- a/cli/src/components/tools/__tests__/gravity-index.test.ts +++ b/cli/src/components/tools/__tests__/gravity-index.test.ts @@ -10,7 +10,7 @@ describe('getGravityIndexParts', () => { query: 'transactional email for a Next.js app', }), ).toEqual({ - name: 'Services · Search', + name: 'Search services', description: 'transactional email for a Next.js app', }) }) @@ -23,7 +23,7 @@ describe('getGravityIndexParts', () => { q: 'send', }), ).toEqual({ - name: 'Services · Browse', + name: 'Browse services', description: 'Email · send', }) }) @@ -35,7 +35,7 @@ describe('getGravityIndexParts', () => { slug: 'sendgrid', }), ).toEqual({ - name: 'Services · Fetch', + name: 'Fetch service', description: 'sendgrid', }) }) @@ -47,18 +47,18 @@ describe('getGravityIndexParts', () => { integrated_slug: 'sendgrid', }), ).toEqual({ - name: 'Services · Report', + name: 'Report integration', description: 'sendgrid integration', }) }) test('names the action even when the target is missing', () => { expect(getGravityIndexParts({ action: 'search' })).toEqual({ - name: 'Services · Search', + name: 'Search services', description: '', }) expect(getGravityIndexParts({ action: 'list_categories' })).toEqual({ - name: 'Services · Categories', + name: 'List service categories', description: '', }) }) diff --git a/cli/src/components/tools/gravity-index.tsx b/cli/src/components/tools/gravity-index.tsx index b7ea37569b..3d0269eac1 100644 --- a/cli/src/components/tools/gravity-index.tsx +++ b/cli/src/components/tools/gravity-index.tsx @@ -6,26 +6,23 @@ import type { ToolRenderConfig } from './types' const asTrimmedString = (value: unknown): string => typeof value === 'string' ? value.trim() : '' -const BRAND = 'Services' - -/** Bold "Services · " prefix that names the action like a CLI subcommand. */ -const withSubcommand = (verb: string): string => `${BRAND} · ${verb}` +const DEFAULT_NAME = 'Services' export interface GravityIndexParts { - /** Bold label: the brand plus the action subcommand. */ + /** Bold label naming the action in plain verb-first language. */ name: string /** Non-bold target the action operates on (query, slug, category). May be empty. */ description: string } /** - * Splits a gravity_index tool call into a bold "Service Catalog · " label - * and the plain target it acts on, so the action reads as one bold unit instead - * of repeating the verb in non-bold text after the brand. + * Splits a gravity_index tool call into a bold verb-first label (e.g. + * "Search services") and the plain target it acts on, so the action reads as + * a single natural phrase instead of a "Brand · Verb" subcommand. */ export const getGravityIndexParts = (input: unknown): GravityIndexParts => { if (!input || typeof input !== 'object') { - return { name: BRAND, description: '' } + return { name: DEFAULT_NAME, description: '' } } const params = input as Record @@ -34,33 +31,33 @@ export const getGravityIndexParts = (input: unknown): GravityIndexParts => { switch (action) { case 'search': return { - name: withSubcommand('Search'), + name: 'Search services', description: asTrimmedString(params.query), } case 'browse': { const category = asTrimmedString(params.category) const keyword = asTrimmedString(params.q) return { - name: withSubcommand('Browse'), + name: 'Browse services', description: [category, keyword].filter(Boolean).join(' · '), } } case 'list_categories': - return { name: withSubcommand('Categories'), description: '' } + return { name: 'List service categories', description: '' } case 'get_service': return { - name: withSubcommand('Fetch'), + name: 'Fetch service', description: asTrimmedString(params.slug), } case 'report_integration': { const slug = asTrimmedString(params.integrated_slug) return { - name: withSubcommand('Report'), + name: 'Report integration', description: slug ? `${slug} integration` : 'integration', } } default: - return { name: BRAND, description: '' } + return { name: DEFAULT_NAME, description: '' } } } diff --git a/cli/src/components/tools/render-ui.tsx b/cli/src/components/tools/render-ui.tsx index 3fea341d74..5049889433 100644 --- a/cli/src/components/tools/render-ui.tsx +++ b/cli/src/components/tools/render-ui.tsx @@ -31,21 +31,21 @@ const isRenderUIButtonWidget = ( ) } +/** + * The button is an accent-colored outline with a matching label. It stays + * unfilled in every state — a fill would bleed past the rounded corners — so + * hover is signalled by underlining the label rather than inverting the fill. + */ const getButtonColors = ( theme: ReturnType, variant: RenderUIButtonVariant, - isHovered: boolean, ) => { - if (variant === 'secondary') { - return { - backgroundColor: isHovered ? theme.surfaceHover : theme.surface, - foregroundColor: theme.foreground, - } - } - + const accent = variant === 'secondary' ? theme.secondary : theme.primary return { - backgroundColor: theme.primary, - foregroundColor: theme.name === 'dark' ? '#111827' : '#ffffff', + // Unfilled: the interior shows the terminal background in every state. + backgroundColor: undefined, + foregroundColor: accent, + borderColor: accent, } } @@ -57,10 +57,9 @@ const RenderUIButton = ({ widget }: { widget: RenderUIButtonWidget }) => { const [isClicked, setIsClicked] = useState(false) const clickTimeoutRef = useRef | null>(null) const variant = widget.variant ?? 'primary' - const { backgroundColor, foregroundColor } = getButtonColors( + const { backgroundColor, foregroundColor, borderColor } = getButtonColors( theme, variant, - isHovered, ) useEffect(() => { @@ -83,11 +82,13 @@ const RenderUIButton = ({ widget }: { widget: RenderUIButtonWidget }) => { ) }, [widget.link]) + // Bold reads as a button label; underline on hover signals it's clickable; + // dim briefly on click to acknowledge the press. const textAttributes = isClicked ? TextAttributes.DIM : isHovered - ? TextAttributes.BOLD - : undefined + ? TextAttributes.BOLD | TextAttributes.UNDERLINE + : TextAttributes.BOLD return ( { onMouseOut={() => setIsHovered(false)} style={{ backgroundColor, + borderStyle: 'rounded', + borderColor, paddingLeft: 1, paddingRight: 1, }} @@ -110,6 +113,8 @@ const RenderUIButton = ({ widget }: { widget: RenderUIButtonWidget }) => { {widget.text} + {/* Trailing arrow signals the button opens an external link. */} + {' ↗'} diff --git a/cli/src/components/waiting-room-screen.tsx b/cli/src/components/waiting-room-screen.tsx index 51cd34c9ad..b3569cf1fa 100644 --- a/cli/src/components/waiting-room-screen.tsx +++ b/cli/src/components/waiting-room-screen.tsx @@ -3,7 +3,7 @@ import { useKeyboard, useRenderer } from '@opentui/react' import React, { useCallback, useEffect, useMemo, useState } from 'react' import { Button } from './button' -import { ChoiceAdBanner, CHOICE_AD_BANNER_HEIGHT } from './choice-ad-banner' +import { ChoiceAdBanner, AD_CARD_HEIGHT } from './ad-banner' import { FreebuffModelSelector } from './freebuff-model-selector' import { ShimmerText } from './shimmer-text' import { @@ -107,11 +107,31 @@ const formatPrivacySignalList = ( return `${labels.slice(0, -1).join(', ')}, or ${labels[labels.length - 1]}` } -const getLimitedModeReason = ( +/** "BR" → "Brazil". Falls back to the raw code when the runtime can't + * resolve it (malformed code, missing ICU data). */ +const formatCountryName = (countryCode: string): string => { + try { + return ( + new Intl.DisplayNames(['en'], { type: 'region' }).of(countryCode) ?? + countryCode + ) + } catch { + return countryCode + } +} + +// Tone matters here: this is shown to users who, through no fault of their +// own, get the smaller model set. Frame it as model *availability* ("aren't +// available in BR yet"), never as restricted *access* ("limited mode", +// "blocked") — clear enough to answer "why these models?" for someone who +// goes looking, quiet enough to ignore for someone who doesn't. The VPN case +// is the one the user can act on, so it leads with the action. Rendered +// directly under the model list — that's where "why these models?" gets asked. +const getLimitedModeNotice = ( session: FreebuffSessionResponse | null, ): string | null => { if (!session || !('countryBlockReason' in session)) { - return 'reduced free model access' + return "Some models aren't available on this connection" } const countryCode = @@ -123,19 +143,21 @@ const getLimitedModeReason = ( switch (session.countryBlockReason) { case 'anonymous_network': - return `${formatPrivacySignalList( + return `Using a ${formatPrivacySignalList( session.ipPrivacySignals ?? undefined, - )} detected` + )}? More models are available on a direct connection` case 'country_not_allowed': - return `based on detected country${countryCode ? `: ${countryCode}` : ''}` + return `Some models aren't available in ${ + countryCode ? formatCountryName(countryCode) : 'your region' + } yet` case 'anonymized_or_unknown_country': case 'missing_client_ip': case 'unresolved_client_ip': - return 'location could not be verified' + return "We couldn't confirm your region, so we're showing models available everywhere" case 'ip_privacy_lookup_failed': - return 'network check could not finish' + return "We couldn't finish a network check, so we're showing models available everywhere" default: - return 'reduced free model access' + return "Some models aren't available on this connection" } } @@ -260,19 +282,19 @@ const TakeoverPrompt: React.FC = () => { * doesn't jump once they earn their first day. */ const StreakInlineLine: React.FC<{ streak: number - marginBottom: number -}> = ({ streak, marginBottom }) => { + marginTop: number +}> = ({ streak, marginTop }) => { const theme = useTheme() if (streak <= 0) { - return + return } return ( = ({ const isQueued = session?.status === 'queued' const accessTier = session && 'accessTier' in session ? session.accessTier : 'full' - const limitedModeReason = - accessTier === 'limited' ? getLimitedModeReason(session) : null + // Hidden in compact terminals: the notice is nice-to-have context, and + // below 22 rows every line competes with the picker itself. + const limitedModeNotice = + accessTier === 'limited' && !compact ? getLimitedModeNotice(session) : null // 'none' = user hasn't joined any queue yet. We're in the pre-chat landing // state: show the picker with live N-in-line hints and a prompt. Picking a // model triggers joinFreebuffQueue, which POSTs and transitions us to @@ -389,7 +413,7 @@ export const WaitingRoomScreen: React.FC = ({ accessTier === 'limited' ? FREEBUFF_LIMITED_SESSION_LIMIT : FREEBUFF_PREMIUM_SESSION_LIMIT - const sessionLabel = 'sessions' + const sessionLabel = accessTier === 'limited' ? 'sessions' : 'premium sessions' const formattedSharedSessionUsed = formatSessionUnits(sharedSessionUsed) const sessionResetAt = getFreebuffPremiumResetAt({ rateLimitsByModel, @@ -406,7 +430,7 @@ export const WaitingRoomScreen: React.FC = ({ // estimate, no blanket safety row) so the scrollbox fills the available // space with no dead band below it: // - top bar: paddingTop 1 + the ✕ row = 2 - // - ad banner: CHOICE_AD_BANNER_HEIGHT, only when shown + // - ad banner: AD_CARD_HEIGHT, only when shown // - main box: its paddingTop (text-logo tier only) + paddingBottom 1 // - logo block: lines + marginBottom 1 (always, when shown) + gap (full) // - the prompt/counter (landing) or the position panel (queued) @@ -422,19 +446,23 @@ export const WaitingRoomScreen: React.FC = ({ ? 0 : logoLines + 1 /* marginBottom */ + (logoMode === 'full' ? 1 : 0) const mainPaddingRows = (logoMode === 'text' ? 1 : 0) + 1 - const adRows = showAds ? CHOICE_AD_BANNER_HEIGHT : 0 - // Streak is rendered inline as a one-line row directly under the counter - // (landing) or title (queued), with the same bottom margin as its neighbor - // so the picker still sits flush below it. - const streakLandingRows = reserveStreakSlot ? 1 + textMarginBottom : 0 - const streakQueuedRows = reserveStreakSlot ? 1 + 1 : 0 + const adRows = showAds ? AD_CARD_HEIGHT : 0 + // Status lines render below the picker, each with marginTop 1: the session + // counter (landing only), then the limited-mode notice, then the streak. + // They still eat into the picker's height budget regardless of being above + // or below it. + const streakRows = reserveStreakSlot ? 1 + 1 : 0 + const noticeRows = limitedModeNotice + ? 1 /* marginTop */ + wrappedRows(limitedModeNotice) + : 0 + const belowPickerRows = streakRows + noticeRows + const counterRows = 1 /* marginTop */ + wrappedRows(counterText) const reservedChrome = 2 + adRows + mainPaddingRows + logoBlockRows const landingTextRows = wrappedRows('Pick a model to start') + textMarginBottom + - wrappedRows(counterText) + - textMarginBottom + - streakLandingRows + counterRows + + belowPickerRows const queuedTitleText = session?.status === 'queued' && session.position === 1 ? "You're next in line" @@ -443,7 +471,7 @@ export const WaitingRoomScreen: React.FC = ({ wrappedRows(queuedTitleText) + 1 + 4 /* position panel */ + - streakQueuedRows + belowPickerRows const selectorMaxHeight = Math.max( 3, terminalHeight - @@ -485,16 +513,9 @@ export const WaitingRoomScreen: React.FC = ({ flexShrink: 0, }} > - - {limitedModeReason && ( - - - Limited mode - - · {limitedModeReason} - - )} - + {/* Empty spacer: justifyContent space-between needs a left sibling to + keep the ✕ pushed to the right. */} +