diff --git a/.gitignore b/.gitignore index ad23d07463..b7a96db995 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,6 @@ .idea/ .vercel -# Exported user lists (PII) - scripts/export-user-emails.ts -user-emails-*.csv - # Environment files (secrets) - Bun loads .env.* files natively .env .env.* @@ -28,9 +25,6 @@ npm-app/src/__tests__/data/ debug/ .context/ docs/bot-detection.md -# Issue-specific plans/specs belong in Linear, not the repository. Existing -# tracked historical specs remain tracked; this keeps new local artifacts out. -docs/specs/ .codex # Nx cache directories @@ -72,13 +66,3 @@ scratch-* # Ad-hoc investigation scripts — scratch, never committed scripts/_tmp-* - -# Local operator launch manifests contain real advertiser identifiers. The -# checked-in example is intentionally non-routable. -ads-pilot-launch.local.json - -# Linked git worktrees created by agent tooling. Each is a full checkout with -# its own .git file, so committing one nests a second working tree inside this -# one — and because a worktree carries its own generated migrations, it also -# drags a DUPLICATE migration index into whichever branch swept it up. -.claude/worktrees/ diff --git a/README.md b/README.md index 2f8dcb4540..d1d2761852 100644 --- a/README.md +++ b/README.md @@ -34,15 +34,12 @@ Freebuff includes a curated model catalog. The regular picker currently offers: | Model | Access | Best for | | --------------------------- | ----------------------- | ----------------------------------------------------------------- | -| **GLM 5.3 Flash** | Full access | The default in full mode; deepest reasoning, unmetered | -| **GPT-5.6 Luna** | Full access | Strong all-around with native images | -| **DeepSeek V4 Flash 07/31** | Full access | Fast coding and tool use; pauses during peak hours | +| **DeepSeek V4 Flash 07/31** | Full access | The default everywhere in full mode; fast coding and tool use | +| **GPT-5.6 Luna** | Full access | Strong all-around with native images; two sessions a day | | **MiMo 2.5** | Full and limited access | The limited-mode default; balanced performance with image support | -| **Solar Pro 4** | Full access | Limited-time trial; 524K context, text only | +| **DeepSeek V4 Pro** | Full access | Deepest reasoning; one session a day | -Most models draw on your normal daily sessions rather than a separate limit. GLM 5.3 Flash and MiMo 2.5 are unmetered and cost no session at all. Models may still serve from a quantized (Q8_0) build. - -DeepSeek V4 Pro was retired from the catalog; GLM 5.3 Flash replaces it as the deep-reasoning pick. +These limits are **temporary**, and they exist because the providers serving DeepSeek now charge more than free mode can carry. V4 Pro is one session a day; GPT-5.6 Luna is two sessions a day; models may serve from a quantized (Q8_0) build. MiMo 2.5 stays unlimited. All of it is intended to be reverted. Beyond the regular picker: diff --git a/README.zh-CN.md b/README.zh-CN.md index 8965932aec..c4e500d93f 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -34,15 +34,12 @@ Freebuff 提供经过筛选的模型目录。常规模型选择器目前包括 | 模型 | 访问范围 | 适用场景 | | --------------------------- | -------------- | ------------------------------------------------ | -| **GLM 5.3 Flash** | 完整访问 | 完整模式下的默认模型;推理最深入,且不消耗会话 | -| **GPT-5.6 Luna** | 完整访问 | 综合能力强,原生支持图像 | -| **DeepSeek V4 Flash 07/31** | 完整访问 | 快速编程和工具调用;高峰时段暂停 | +| **DeepSeek V4 Flash 07/31** | 完整访问 | 完整模式下所有平台的默认模型;快速编程和工具调用 | +| **GPT-5.6 Luna** | 完整访问 | 综合能力强,原生支持图像;每天两次会话 | | **MiMo 2.5** | 完整和受限访问 | 受限模式的默认模型;均衡性能并支持图像 | -| **Solar Pro 4** | 完整访问 | 限时试用;52.4 万上下文,仅支持文本 | +| **DeepSeek V4 Pro** | 完整访问 | 推理最深入;每天一次会话 | -大多数模型使用你的常规每日会话,而不再各自设限。GLM 5.3 Flash 和 MiMo 2.5 保持无限使用,完全不消耗会话。模型仍可能由量化(Q8_0)版本提供服务。 - -DeepSeek V4 Pro 已从模型目录中下线,由 GLM 5.3 Flash 接替其深度推理的位置。 +以下限制是**临时**的,原因是为 DeepSeek 提供服务的供应商收费已超出免费模式可承受的范围。V4 Pro 每天一次会话;GPT-5.6 Luna 每天两次会话;模型可能由量化(Q8_0)版本提供服务。MiMo 2.5 保持无限使用。这些调整都计划在之后恢复。 常规模型选择器之外: diff --git a/agents/__tests__/base3.test.ts b/agents/__tests__/base3.test.ts index f4870bcce1..2385499611 100644 --- a/agents/__tests__/base3.test.ts +++ b/agents/__tests__/base3.test.ts @@ -12,12 +12,9 @@ import base3FreeDeepseekFlash from '../base3-free-deepseek-flash' import base3FreeDeepseekFlashEvals from '../base3-free-deepseek-flash-evals' import base3FreeFable from '../base3-free-fable' import base3FreeGlm from '../base3-free-glm' -import base3FreeGlmV53Flash from '../base3-free-glm-5-3-flash' import base3FreeLuna from '../base3-free-luna' import base3FreeMimo from '../base3-free-mimo' import base3FreeMinimaxM3 from '../base3-free-minimax-m3' -import base3FreeOxAlpha from '../base3-free-ox-alpha' -import base3FreeSolarPro4 from '../base3-free-solar-pro4' import base3Lite from '../base3-lite' /** @@ -47,18 +44,13 @@ const CLI_ROOTS = [ base3FreeMinimaxM3, base3FreeMimo, base3FreeGlm, - base3FreeGlmV53Flash, base3FreeLuna, base3FreeFable, - base3FreeOxAlpha, - base3FreeSolarPro4, ] describe('base3 CLI roots', () => { test('keeps the efficiency flags the runtime reads', () => { - // 14 since Solar Pro 4 reached the CLI. The count is asserted so a root - // added without the flags below cannot slip in unnoticed. - expect(CLI_ROOTS.length).toBe(14) + expect(CLI_ROOTS.length).toBe(11) for (const agent of CLI_ROOTS) { // Windowed reads + the 100-entry glob cap + search-first tool wording. expect(agent.windowedFileReads).toBe(true) @@ -91,9 +83,6 @@ describe('base3 CLI roots', () => { // requires a canonical opening at byte 0, so prepending 403s every turn. for (const agent of CLI_ROOTS) { expect(hasFreebuffRootSystemPromptOpening(agent.systemPrompt!)).toBe(true) - expect( - agent.systemPrompt!.match(/\{CODEBUFF_GIT_CHANGES_PROMPT\}/g), - ).toHaveLength(1) } }) diff --git a/agents/base-chat.ts b/agents/base-chat.ts index 0ce94245ac..3511823258 100644 --- a/agents/base-chat.ts +++ b/agents/base-chat.ts @@ -66,10 +66,6 @@ End every response by calling the suggest_followups tool with exactly 3 followup 'meta/muse-spark-1.2-contributor': 1_000_000, // Ox Alpha: 1,048,576 published, entered low for the same reason. 'stealth/ox-alpha': 1_000_000, - // GLM 5.3 Flash: 1,310,720 published, entered low for the same reason. - 'z-ai/glm-5.3-flash': 1_000_000, - // Solar Pro 4: 524,288 published, entered low for the same reason. - 'upstage/solar-pro4': 500_000, } /** For any model not listed above. Assuming a window is smaller than it is diff --git a/agents/base2/base2-free-glm-5-3-flash.ts b/agents/base2/base2-free-glm-5-3-flash.ts deleted file mode 100644 index e8206b9a2b..0000000000 --- a/agents/base2/base2-free-glm-5-3-flash.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { FREEBUFF_GLM_V53_FLASH_MODEL_ID } from '@codebuff/common/constants/freebuff-models' - -import { createBase2 } from './base2' - -const definition = { - ...createBase2('free', { - model: FREEBUFF_GLM_V53_FLASH_MODEL_ID, - }), - id: 'base2-free-glm-5-3-flash', - displayName: 'Buffy the GLM 5.3 Flash Free Orchestrator', -} - -export default definition diff --git a/agents/base2/base2-free-solar-pro4.ts b/agents/base2/base2-free-solar-pro4.ts deleted file mode 100644 index 14ebc965ab..0000000000 --- a/agents/base2/base2-free-solar-pro4.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { FREEBUFF_SOLAR_PRO_4_MODEL_ID } from '@codebuff/common/constants/freebuff-models' - -import { createBase2 } from './base2' - -const definition = { - ...createBase2('free', { - model: FREEBUFF_SOLAR_PRO_4_MODEL_ID, - }), - id: 'base2-free-solar-pro4', - displayName: 'Buffy the Solar Pro 4 Free Orchestrator', -} - -export default definition diff --git a/agents/base3-free-glm-5-3-flash.ts b/agents/base3-free-glm-5-3-flash.ts deleted file mode 100644 index 0274d2ae7c..0000000000 --- a/agents/base3-free-glm-5-3-flash.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { FREEBUFF_GLM_V53_FLASH_MODEL_ID } from '@codebuff/common/constants/freebuff-models' - -import { createBase3CliRoot } from './base3' - -// No `reasoningOptions`, for the reason spelled out on base3-free-luna: an -// agent-declared reasoning reaches the wire as `body.reasoning`, which makes -// the definition rather than the user the authority on effort. It costs nothing -// here in particular — OpenRouter reports no effort levels for this model, so -// the catalog row declares no ladder and the picker shows no control. -const definition = { - ...createBase3CliRoot({ - model: FREEBUFF_GLM_V53_FLASH_MODEL_ID, - isFreebuff: true, - }), - id: 'base3-free-glm-5-3-flash', - displayName: 'Buffy on GLM 5.3 Flash', -} - -export default definition diff --git a/agents/base3-free-ox-alpha.ts b/agents/base3-free-ox-alpha.ts deleted file mode 100644 index 397385081f..0000000000 --- a/agents/base3-free-ox-alpha.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { FREEBUFF_OX_ALPHA_MODEL_ID } from '@codebuff/common/constants/freebuff-models' - -import { createBase3CliRoot } from './base3' - -/** - * Buffy on Ox Alpha, for CLI and Desktop (2026-08-24). - * - * No `reasoningOptions`, for the reason spelled out on base3-free-luna: an - * agent-declared reasoning reaches the wire as `body.reasoning` and makes the - * agent the authority on effort, which leaves the user's picker choice unable - * to do anything. That matters more here than on most rows -- this endpoint - * reports `reasoning.mandatory: true` with `default_effort: max`, and `max` - * costs ~4.8x the tokens of `high` for no better answer while being the rung - * most likely to spend the whole budget thinking and return - * `finish_reason: "length"` with null content. The catalog row names `high` - * explicitly and the server fills it in; hard-coding anything here would take - * that away. - */ -const definition = { - ...createBase3CliRoot({ - model: FREEBUFF_OX_ALPHA_MODEL_ID, - isFreebuff: true, - }), - id: 'base3-free-ox-alpha', - displayName: 'Buffy on Ox Alpha', -} - -export default definition diff --git a/agents/base3-free-solar-pro4.ts b/agents/base3-free-solar-pro4.ts deleted file mode 100644 index 07ecf58c18..0000000000 --- a/agents/base3-free-solar-pro4.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { FREEBUFF_SOLAR_PRO_4_MODEL_ID } from '@codebuff/common/constants/freebuff-models' - -import { createBase3CliRoot } from './base3' - -const definition = { - ...createBase3CliRoot({ - model: FREEBUFF_SOLAR_PRO_4_MODEL_ID, - isFreebuff: true, - }), - id: 'base3-free-solar-pro4', - displayName: 'Buffy on Solar Pro 4', -} - -export default definition diff --git a/agents/base3.ts b/agents/base3.ts index d2f193f698..f0075de3f0 100644 --- a/agents/base3.ts +++ b/agents/base3.ts @@ -55,8 +55,6 @@ Current date: ${PLACEHOLDER.CURRENT_DATE}. - Don't run destructive or hard-to-undo commands (git push, resets, deploys) unless the user asks for them. ${PLACEHOLDER.KNOWLEDGE_FILES_CONTENTS} - -${PLACEHOLDER.GIT_CHANGES_PROMPT} `, } } @@ -176,6 +174,12 @@ ${ } ${PLACEHOLDER.SYSTEM_INFO_PROMPT} + +# Initial Git Changes + +The following is the state of the git repository at the start of the conversation. Note that it is not updated to reflect any subsequent changes made by you or the user. + +${PLACEHOLDER.GIT_CHANGES_PROMPT} ` } diff --git a/agents/reviewer/code-reviewer-glm-5-3-flash.ts b/agents/reviewer/code-reviewer-glm-5-3-flash.ts deleted file mode 100644 index a7e1c8625b..0000000000 --- a/agents/reviewer/code-reviewer-glm-5-3-flash.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { FREEBUFF_GLM_V53_FLASH_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' - -// Runs the SAME model as the root that spawns it. Not a style choice: the -// chat-completions session gate rejects any request whose model differs from -// the one the session was admitted on, so a cross-model reviewer 403s mid-run. -const definition: SecretAgentDefinition = { - id: 'code-reviewer-glm-5-3-flash', - publisher, - ...createReviewer(FREEBUFF_GLM_V53_FLASH_MODEL_ID), -} - -export default definition diff --git a/agents/reviewer/code-reviewer-ox-alpha.ts b/agents/reviewer/code-reviewer-ox-alpha.ts deleted file mode 100644 index fc77be2a12..0000000000 --- a/agents/reviewer/code-reviewer-ox-alpha.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { FREEBUFF_OX_ALPHA_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-ox-alpha', - publisher, - ...createReviewer(FREEBUFF_OX_ALPHA_MODEL_ID), -} - -export default definition diff --git a/agents/reviewer/code-reviewer-solar-pro4.ts b/agents/reviewer/code-reviewer-solar-pro4.ts deleted file mode 100644 index 9af85f33d1..0000000000 --- a/agents/reviewer/code-reviewer-solar-pro4.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { FREEBUFF_SOLAR_PRO_4_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-solar-pro4', - publisher, - ...createReviewer(FREEBUFF_SOLAR_PRO_4_MODEL_ID), -} - -export default definition diff --git a/bun.lock b/bun.lock index 990ccad9e7..678a7c0683 100644 --- a/bun.lock +++ b/bun.lock @@ -200,11 +200,11 @@ "packages": { "@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.50", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-21PaHfoLmouOXXNINTsZJsMw+wE5oLR2He/1kq/sKokTVKyq7ObGT1LDk6ahwxaz/GoaNaGankMh+EgVcdv2Cw=="], - "@ai-sdk/gateway": ["@ai-sdk/gateway@4.0.70", "", { "dependencies": { "@ai-sdk/provider": "4.0.9", "@ai-sdk/provider-utils": "5.0.34", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-0tzAH2vwXOs/kVktAZRS04dATEQJk1hf1QR+VuVfvo9QmW3UPgcjhhJD9QFgP8HZLxkrEGDImwLIQ7sUfQTIsA=="], + "@ai-sdk/gateway": ["@ai-sdk/gateway@4.0.62", "", { "dependencies": { "@ai-sdk/provider": "4.0.7", "@ai-sdk/provider-utils": "5.0.29", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-zR3pustGWhw5eUZHG+fJZx/V/PBe+LxdDpc5hDFWxozG/3MB/+eY62jn+YiR+9uOH+Hx63e5zJoeKLfZfPktWQ=="], "@ai-sdk/provider": ["@ai-sdk/provider@2.0.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww=="], - "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.36", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2eSw90hn32Je6n2a8Gf4dJ2EoecPJuOCWqwZCw+BkhPq2LOS01HX3s6ljgOm0iIkZiD5aAuMdpOw17rYKQF/Zg=="], + "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.32", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-izgUo50kamJMwoYCXoFwVccuJeyYp0y1+twf0FfpEz7kdQBloZLZbgLYUOUpannLWrV7xYSJR48BQJIQFbFiAQ=="], "@auth/core": ["@auth/core@0.41.3", "", { "dependencies": { "@panva/hkdf": "^1.2.1", "jose": "^6.0.6", "oauth4webapi": "^3.3.0", "preact": "10.24.3", "preact-render-to-string": "6.5.11" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "nodemailer": "^7.0.7 || ^8.0.5" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-sJ3JMHHkXMD3aOjopv7mOBTO1Ocw4b0fAEXJBz6k7YHLpYQI6C40jCUPc5fNvUKxXRXNE1/sRISA15UrwWJBTw=="], @@ -270,57 +270,57 @@ "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], - "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.3.3" }, "os": "darwin", "cpu": "arm64" }, "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA=="], + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.3.2" }, "os": "darwin", "cpu": "arm64" }, "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg=="], - "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.3.3" }, "os": "darwin", "cpu": "x64" }, "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw=="], + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.3.2" }, "os": "darwin", "cpu": "x64" }, "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w=="], - "@img/sharp-freebsd-wasm32": ["@img/sharp-freebsd-wasm32@0.35.4", "", { "dependencies": { "@img/sharp-wasm32": "0.35.4" }, "os": "freebsd" }, "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA=="], + "@img/sharp-freebsd-wasm32": ["@img/sharp-freebsd-wasm32@0.35.3", "", { "dependencies": { "@img/sharp-wasm32": "0.35.3" }, "os": "freebsd" }, "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg=="], - "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.3.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg=="], + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg=="], - "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.3.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw=="], + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw=="], - "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.3.3", "", { "os": "linux", "cpu": "arm" }, "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA=="], + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.3.2", "", { "os": "linux", "cpu": "arm" }, "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ=="], - "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A=="], + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA=="], - "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.3.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w=="], + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.3.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw=="], - "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.3.3", "", { "os": "linux", "cpu": "none" }, "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ=="], + "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.3.2", "", { "os": "linux", "cpu": "none" }, "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w=="], - "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.3.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q=="], + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.3.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ=="], - "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA=="], + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w=="], - "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw=="], + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw=="], - "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw=="], + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ=="], - "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.3.3" }, "os": "linux", "cpu": "arm" }, "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A=="], + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.3.2" }, "os": "linux", "cpu": "arm" }, "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA=="], - "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.3.3" }, "os": "linux", "cpu": "arm64" }, "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ=="], + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.3.2" }, "os": "linux", "cpu": "arm64" }, "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ=="], - "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.3.3" }, "os": "linux", "cpu": "ppc64" }, "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ=="], + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.3.2" }, "os": "linux", "cpu": "ppc64" }, "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA=="], - "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.3.3" }, "os": "linux", "cpu": "none" }, "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA=="], + "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.3.2" }, "os": "linux", "cpu": "none" }, "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ=="], - "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.3.3" }, "os": "linux", "cpu": "s390x" }, "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ=="], + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.3.2" }, "os": "linux", "cpu": "s390x" }, "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw=="], - "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.3.3" }, "os": "linux", "cpu": "x64" }, "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA=="], + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.3.2" }, "os": "linux", "cpu": "x64" }, "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA=="], - "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.3.3" }, "os": "linux", "cpu": "arm64" }, "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA=="], + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" }, "os": "linux", "cpu": "arm64" }, "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w=="], - "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.3.3" }, "os": "linux", "cpu": "x64" }, "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg=="], + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.3.2" }, "os": "linux", "cpu": "x64" }, "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg=="], - "@img/sharp-wasm32": ["@img/sharp-wasm32@0.35.4", "", { "dependencies": { "@emnapi/runtime": "^1.11.3" } }, "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA=="], + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.35.3", "", { "dependencies": { "@emnapi/runtime": "^1.11.1" } }, "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w=="], - "@img/sharp-webcontainers-wasm32": ["@img/sharp-webcontainers-wasm32@0.35.4", "", { "dependencies": { "@img/sharp-wasm32": "0.35.4" }, "cpu": "none" }, "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA=="], + "@img/sharp-webcontainers-wasm32": ["@img/sharp-webcontainers-wasm32@0.35.3", "", { "dependencies": { "@img/sharp-wasm32": "0.35.3" }, "cpu": "none" }, "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q=="], - "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.35.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw=="], + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.35.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w=="], - "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.35.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw=="], + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.35.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw=="], - "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.35.4", "", { "os": "win32", "cpu": "x64" }, "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg=="], + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.35.3", "", { "os": "win32", "cpu": "x64" }, "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA=="], "@jimp/core": ["@jimp/core@1.6.1", "", { "dependencies": { "@jimp/file-ops": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", "file-type": "^21.3.3", "mime": "3" } }, "sha512-+BoKC5G6hkrSy501zcJ2EpfnllP+avPevcBfRcZe/CW+EwEfY6X1EZ8QWyT7NpDIvEEJb1fdJnMMfUnFkxmw9A=="], @@ -384,29 +384,29 @@ "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.6.0", "", {}, "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw=="], + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.30.0", "", { "dependencies": { "@hono/node-server": "^1.19.9 || ^2.0.5", "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-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA=="], - "@next/env": ["@next/env@16.3.4", "", {}, "sha512-cjWZnUUa6jZq2kFaNe/ZyJdZonOZ/QoN0Zka2nz/FLOrfx14pQuM9c5RaSVkWMqgdt4ksgPAMWPyHSs/CyV48Q=="], + "@next/env": ["@next/env@16.3.2", "", {}, "sha512-8k4YoG8cM7LWlkfzGNYCRBbFNlernLiMw4s0btVl+CmmWqn3VpYypA72/5Feb1UWdxe6tHqr5KHP4p4Y4m9luA=="], - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.3.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iBr3I5LZNk5/bgl5//iTgD2tcym14MX0Xo7fD//u9dYAEgGzza1y9oywluPtf74YnOswVdH1908aK9xVz7zQTw=="], + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ib5Llm93YCKoKWDh6ZaHq6QWTuOZ2bRkSnUwMmX8dsRIOkBNL1vVlSiUKSfixPL9SSh9pvukzqajk/klkn5vqg=="], - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.3.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-2dpiSyl2Jw/NrBPaU2MAKGSa+2MR82pJIn4Sm5Rjr+gxAeuh0z158Su3Z2O8zn7UNNq+ej4bToed6RcRN/Lydg=="], + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-qd98fX2+I5nYJDioW2o7nSjoxM5KvWdeDefM80igia4+C/qSIEhH4MhTE+hO/7qKM7W37/Mq+dOWp8UePSyLHw=="], - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+t+U8HZT+fApePCS5h89CSH3datz29MkzyfCn+6fpsZBG/oiEOhINcb9rtkv6sdpToLGFn2e6146NzaKCXkqrA=="], + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-vqsgb6FAOzcrCccsLXiKtAy5t8EzO+uOazuFaSkQxeY0tNONG3vpHYy8pyBafcI5SNFPTeyard6yTr6SzNGo2A=="], - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-mx03GNs1ocQA5JQ4FxDMmIsNkdrZh8cuezKCrId28e5/gIPU/l7Kcy2+vmCCzdjnnmXJy+iOAu+7K0QppO6Urg=="], + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-xIe1eujfHUB2XcxHGddxJyu6TJRPjC5NpIkQYB/32ESkt5VkQyIAjmLRS38c+s6QY+qjtY/4KarVDzXRuD7lZQ=="], - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-YIhGY6fSMfha52bnVxnzc9zaVBzJg+cqQTOD8tXIBSx4fuv0pVMxQTE0PaS59YhnMOiYiG09IMwxJAf/CFm/Dw=="], + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Fe0SA2j8X0kmc3aveuHD7UktO3AE2+mH3LguP60vGbz7u0z+MrDXbeb5iZFYAwR7EzzzXJ2Yk966w9mGTFMqfA=="], - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+eaaX6axpDb0yF1GCpiERe6njplvdC+nks/fKfcHu3XPGRrald8P3/X7yv7QLdjA51knnxwl9pxdIJsg+w1L+Q=="], + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-TFBipb+gyesI/2Ve4zVu7kGltBWN/R466G5/1gtt2lECfc22G1pjkTxu68Q9aFcOaXiRGTQfvDbQQFe7mYgxiQ=="], - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.3.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-0jcXW7Xs/uzICrmgV3MhDYDeRy++1CqnpDIerlPIqYO4bhzB4WNbX/aRnQclustsAyTkFKB0z6rbcjmNg5tR8A=="], + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.3.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-rVtmnNpBYIosDnKD/96dKxFsJnwnn1WRGG/HioSe8XCm2ksSHNrd2R6+hSjvTBxeMNhJ9pYeu/90cWB1nQLuNA=="], - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.3.4", "", { "os": "win32", "cpu": "x64" }, "sha512-vvBzwu1pYQCp92maZCFCIw/XgOTMR5tur9GjakwIo2cmwRTMKajRZZDS9+e4KsUZWKu1E007WUeAFXRRjZeuzw=="], + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.3.2", "", { "os": "win32", "cpu": "x64" }, "sha512-H4Y2o2/JcHu8LtwzD5CXfHhwxwz8gfsx2HXDEw46Mtev5xHnEmB7HNtZtmriw5ReUOjRtcDqo7XSbU01FT9NlA=="], "@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=="], @@ -438,9 +438,9 @@ "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], - "@posthog/core": ["@posthog/core@1.49.2", "", { "dependencies": { "@posthog/types": "^1.407.1" } }, "sha512-AXHDo/4nisUg7OPG1TQNgREK7n+chBQXLQyttp7bDTDsDg2k9lV06TmnpvuNbwJs53q9mP9hjezKEZu4fYadfg=="], + "@posthog/core": ["@posthog/core@1.48.8", "", { "dependencies": { "@posthog/types": "^1.405.1" } }, "sha512-LAOBOjMQrQmgcbZxnubl74l2GQd5OWqxCJJ8mzcWdZRWbfO6Y/A6E3fqWjDtm68hY4LxC/bbopG60cxZW8cfWw=="], - "@posthog/types": ["@posthog/types@1.407.1", "", {}, "sha512-WhbkXPC2rgylXqmxHqv70ffI3k+KxyR6s7DBIfr5NvIqHkxp6v0pk31D/jbz0DNVbzwkLjyll2pxr4FNbJiYzg=="], + "@posthog/types": ["@posthog/types@1.405.1", "", {}, "sha512-JvaR4ChUKUk7qSTG58vKN2Br6es9riFF5mvlu7YGcwbB476vfyTO0o/TBh+zE88QhsfxnE5Tr/jKxI+e1v3Q5A=="], "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], @@ -448,9 +448,9 @@ "@swc/helpers": ["@swc/helpers@0.5.23", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw=="], - "@tanstack/query-core": ["@tanstack/query-core@5.102.8", "", {}, "sha512-ZNjkJ33CqvPNec/6lZBnHqLc3EVGPZ9ySLhYahU9TcuRFdmwXewuj0c4hwSWcGHqEUwcSrKeZ+oGcvPBqXcQcg=="], + "@tanstack/query-core": ["@tanstack/query-core@5.102.1", "", {}, "sha512-bJTsrO2ODWSgg+x1leTsK036GBtmNNjnrF1Vk06J5fg/phYZLQIdXrBe0uf5xTItXYvIW1MtbLhyPw7cPMqUUQ=="], - "@tanstack/react-query": ["@tanstack/react-query@5.102.8", "", { "dependencies": { "@tanstack/query-core": "5.102.8" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-TYBea4OuXWD7MhaSHq069TWbFe7rcwWN6kzT7JF0OKi1K6c1gTv2IzD6A6ExJsCMozdkqBWeuIUZmu4KQg0O5A=="], + "@tanstack/react-query": ["@tanstack/react-query@5.102.1", "", { "dependencies": { "@tanstack/query-core": "5.102.1" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-DKJeRvxjsGUWhUVnVVXSseTS0SKBMLSkrz8dm7i08r93IQQ2gAfUkX1e801gVZ18KSwHiG5yng4tQm5+qSGS0w=="], "@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="], @@ -550,7 +550,7 @@ "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], - "ai": ["ai@7.0.86", "", { "dependencies": { "@ai-sdk/gateway": "4.0.70", "@ai-sdk/provider": "4.0.9", "@ai-sdk/provider-utils": "5.0.34" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-11Hovs3BI98tPJiOuA85Be+ktxbZ2QUIqqLJqfHJ55zz4106pjRkEP9OQ95glyjBXPEtTdr6/z4ISsk6G13rvw=="], + "ai": ["ai@7.0.77", "", { "dependencies": { "@ai-sdk/gateway": "4.0.62", "@ai-sdk/provider": "4.0.7", "@ai-sdk/provider-utils": "5.0.29" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-muLtBSTAUCreR77L16w4AFBiX2gK/RNt84EKp8m03SN9+MfNlC5EGqYYttRjYKV3xe0a33yj1Zawj1EnjejIWw=="], "ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], @@ -598,7 +598,7 @@ "await-to-js": ["await-to-js@3.0.0", "", {}, "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g=="], - "axios": ["axios@1.20.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.6", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg=="], + "axios": ["axios@1.19.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.6", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw=="], "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], @@ -606,7 +606,7 @@ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.11.20", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.11.18", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-1iEmLEYSiE1SeBoAfPo/Mnx3PzfzHUkDK61ASkCpuk3YXugYLH5DYK1SzqV55F8FMI6s0F+/tCP7Polz1QRjxw=="], "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], @@ -636,7 +636,7 @@ "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.30001810", "", {}, "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg=="], + "caniuse-lite": ["caniuse-lite@1.0.30001809", "", {}, "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ=="], "canvas": ["canvas@3.2.3", "", { "dependencies": { "node-addon-api": "^7.0.0", "prebuild-install": "^7.1.3" } }, "sha512-PzE5nJZPz72YUAfo8oTp0u3fqqY7IzlTubneAihqDYAUcBk7ryeCmBbdJBEdaH0bptSOe2VT2Zwcb3UaFyaSWw=="], @@ -762,7 +762,7 @@ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - "eslint": ["eslint@10.9.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.7.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.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-9VaAkDURekixUQJy0oJYl2DcN6oKMfxay7XzaGYAWQwsb6qfKf+x76R2k1L8kb1boc+FyCAaTA9GmiKaaiaF+A=="], + "eslint": ["eslint@10.9.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.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.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-5KeEOJZBfEVA47boFiBsf+6MmmJpffM7qEBg4pLla2e4nlKgdKlqCW0oSLOGsT8Wl5uCGJptLV1bkaiShj90Gw=="], "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=="], @@ -808,7 +808,7 @@ "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], - "express-rate-limit": ["express-rate-limit@8.7.0", "", { "dependencies": { "debug": "^4.4.3", "ip-address": "^10.2.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g=="], + "express-rate-limit": ["express-rate-limit@8.6.2", "", { "dependencies": { "debug": "^4.4.3", "ip-address": "^10.2.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A=="], "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], @@ -826,7 +826,7 @@ "fast-uri": ["fast-uri@3.1.6", "", {}, "sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q=="], - "fastq": ["fastq@1.20.3", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw=="], + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], @@ -914,7 +914,7 @@ "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], - "hono": ["hono@4.13.5", "", {}, "sha512-O6+/eCYRkzzzy0rPWwKLiGBR1nFuUPZynnwjxN1MBA62NNqbT0wQEzQyK2gSO5yDIDB336sXQleAhOHrzlYyKw=="], + "hono": ["hono@4.13.3", "", {}, "sha512-r8AO2mYHoLxSHkgafNeC/BXyb2vWRxD3jem4Ts+ptav8oTG5FIRifAjuJEmZI4bSvvc2ns0GxmIYiZnHqN3mMw=="], "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=="], @@ -942,7 +942,7 @@ "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], - "ip-address": ["ip-address@10.7.0", "", {}, "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA=="], + "ip-address": ["ip-address@10.5.0", "", {}, "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g=="], "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], @@ -1028,7 +1028,7 @@ "jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="], - "js-yaml": ["js-yaml@3.15.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w=="], + "js-yaml": ["js-yaml@3.15.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag=="], "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], @@ -1186,11 +1186,11 @@ "negotiator": ["negotiator@1.1.0", "", { "dependencies": { "content-type": "^2.1.0" } }, "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg=="], - "next": ["next@16.3.4", "", { "dependencies": { "@next/env": "16.3.4", "@swc/helpers": "0.5.23", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.5.23", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.3.4", "@next/swc-darwin-x64": "16.3.4", "@next/swc-linux-arm64-gnu": "16.3.4", "@next/swc-linux-arm64-musl": "16.3.4", "@next/swc-linux-x64-gnu": "16.3.4", "@next/swc-linux-x64-musl": "16.3.4", "@next/swc-win32-arm64-msvc": "16.3.4", "@next/swc-win32-x64-msvc": "16.3.4", "sharp": "^0.35.4" }, "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-/Ztf6CeRH+ejEXUrYtqI4gkS66eFIHuSwqi60RgcpWKodxFZx2/dqVCMKBwILfAHXQ+F1b1vAudgj3mnxqtoIA=="], + "next": ["next@16.3.2", "", { "dependencies": { "@next/env": "16.3.2", "@swc/helpers": "0.5.23", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.5.23", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.3.2", "@next/swc-darwin-x64": "16.3.2", "@next/swc-linux-arm64-gnu": "16.3.2", "@next/swc-linux-arm64-musl": "16.3.2", "@next/swc-linux-x64-gnu": "16.3.2", "@next/swc-linux-x64-musl": "16.3.2", "@next/swc-win32-arm64-msvc": "16.3.2", "@next/swc-win32-x64-msvc": "16.3.2", "sharp": "^0.35.3" }, "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-/ZCaubUy17Lld1SiPWxuPbCk2ihqAxF2QNQaPZeEaEb7t1I58qhsJN187D7AfpapHAqUPXH0f/thtdW9dWgWFg=="], "next-auth": ["next-auth@4.24.15", "", { "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": "^11.1.1" }, "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-NnjYtjrSOAx/TIVFGTX4IfI/9yHnNpi4B7FuLUwuV20v2Zxgr2OGP/YN0ynJuI7y8QOnTBPitfOdEXZrVvhIuA=="], - "node-abi": ["node-abi@3.96.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg=="], + "node-abi": ["node-abi@3.94.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g=="], "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], @@ -1324,7 +1324,7 @@ "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], - "posthog-node": ["posthog-node@5.51.4", "", { "dependencies": { "@posthog/core": "^1.49.1" }, "peerDependencies": { "rxjs": "^7.0.0" }, "optionalPeers": ["rxjs"] }, "sha512-gI6JMBnU3vjDNclUBWonw3y7k8Y0UPIAVO4AQ2zu9eyW+7sY8UQRofx4JIA7IGYLMEbs4gykfogOmXp16+oUdg=="], + "posthog-node": ["posthog-node@5.50.0", "", { "dependencies": { "@posthog/core": "^1.48.8" }, "peerDependencies": { "rxjs": "^7.0.0" }, "optionalPeers": ["rxjs"] }, "sha512-7trcTN4EcPBiYAI1nnD1rk+VB+OcjgN0Mdnp7kOK09PTCSW0LCAUVAYj3FD7u8Ne5zYCnW/ESv6k3VU7sOsL5w=="], "preact": ["preact@10.29.8", "", { "peerDependencies": { "preact-render-to-string": ">=5" }, "optionalPeers": ["preact-render-to-string"] }, "sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q=="], @@ -1352,7 +1352,7 @@ "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - "qs": ["qs@6.16.0", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA=="], + "qs": ["qs@6.15.3", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A=="], "queue-lit": ["queue-lit@1.5.2", "", {}, "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw=="], @@ -1444,7 +1444,7 @@ "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], - "sharp": ["sharp@0.35.4", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.4", "@img/sharp-darwin-x64": "0.35.4", "@img/sharp-freebsd-wasm32": "0.35.4", "@img/sharp-libvips-darwin-arm64": "1.3.3", "@img/sharp-libvips-darwin-x64": "1.3.3", "@img/sharp-libvips-linux-arm": "1.3.3", "@img/sharp-libvips-linux-arm64": "1.3.3", "@img/sharp-libvips-linux-ppc64": "1.3.3", "@img/sharp-libvips-linux-riscv64": "1.3.3", "@img/sharp-libvips-linux-s390x": "1.3.3", "@img/sharp-libvips-linux-x64": "1.3.3", "@img/sharp-libvips-linuxmusl-arm64": "1.3.3", "@img/sharp-libvips-linuxmusl-x64": "1.3.3", "@img/sharp-linux-arm": "0.35.4", "@img/sharp-linux-arm64": "0.35.4", "@img/sharp-linux-ppc64": "0.35.4", "@img/sharp-linux-riscv64": "0.35.4", "@img/sharp-linux-s390x": "0.35.4", "@img/sharp-linux-x64": "0.35.4", "@img/sharp-linuxmusl-arm64": "0.35.4", "@img/sharp-linuxmusl-x64": "0.35.4", "@img/sharp-webcontainers-wasm32": "0.35.4", "@img/sharp-win32-arm64": "0.35.4", "@img/sharp-win32-ia32": "0.35.4", "@img/sharp-win32-x64": "0.35.4" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA=="], + "sharp": ["sharp@0.35.3", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.3", "@img/sharp-darwin-x64": "0.35.3", "@img/sharp-freebsd-wasm32": "0.35.3", "@img/sharp-libvips-darwin-arm64": "1.3.2", "@img/sharp-libvips-darwin-x64": "1.3.2", "@img/sharp-libvips-linux-arm": "1.3.2", "@img/sharp-libvips-linux-arm64": "1.3.2", "@img/sharp-libvips-linux-ppc64": "1.3.2", "@img/sharp-libvips-linux-riscv64": "1.3.2", "@img/sharp-libvips-linux-s390x": "1.3.2", "@img/sharp-libvips-linux-x64": "1.3.2", "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", "@img/sharp-libvips-linuxmusl-x64": "1.3.2", "@img/sharp-linux-arm": "0.35.3", "@img/sharp-linux-arm64": "0.35.3", "@img/sharp-linux-ppc64": "0.35.3", "@img/sharp-linux-riscv64": "0.35.3", "@img/sharp-linux-s390x": "0.35.3", "@img/sharp-linux-x64": "0.35.3", "@img/sharp-linuxmusl-arm64": "0.35.3", "@img/sharp-linuxmusl-x64": "0.35.3", "@img/sharp-webcontainers-wasm32": "0.35.3", "@img/sharp-win32-arm64": "0.35.3", "@img/sharp-win32-ia32": "0.35.3", "@img/sharp-win32-x64": "0.35.3" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], @@ -1514,7 +1514,7 @@ "supports-terminal-graphics": ["supports-terminal-graphics@0.1.0", "", {}, "sha512-+KdfozhS0Fw8y5Sghw8kkZNGT8nWYzJ1EzcoIvVjxhl+26TJTs26y02yfBgvc1jh5AS/c8jcI3xtahhR95KRyQ=="], - "systeminformation": ["systeminformation@5.33.6", "", { "os": "!aix", "bin": { "systeminformation": "lib/cli.js" } }, "sha512-hMOQG/eRUzuopuYGGdl8ntkau0nEC7fOaRoTUg1RSr2GTQIk2VNa76DA0+ApajkGfzmcgAupgIP/vt+jtoe5EA=="], + "systeminformation": ["systeminformation@5.33.1", "", { "os": "!aix", "bin": { "systeminformation": "lib/cli.js" } }, "sha512-DEN6ICHk3Tk0Uf/hrAHh7xlt7iL5CJFBtPZinA0H62DrGG/KPKqq/Nzj6lCXPS4Ay/sf/14zNnk9LpqKzBIc+w=="], "tar-fs": ["tar-fs@2.1.5", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw=="], @@ -1542,7 +1542,7 @@ "ts-pattern": ["ts-pattern@5.9.0", "", {}, "sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg=="], - "tsc-alias": ["tsc-alias@1.9.3", "", { "dependencies": { "chokidar": "^3.5.3", "commander": "^9.0.0", "get-tsconfig": "^4.10.0", "globby": "^11.0.4", "mylas": "^2.1.9", "normalize-path": "^3.0.0", "plimit-lit": "^1.2.6" }, "bin": { "tsc-alias": "dist/bin/index.js" } }, "sha512-GKrkA/K5hwae80rlfJRazukMMMIUsIHRyb75lbEp+qaUP57sYmur2Z05dosZNBspByX3ZrxbLHkMRgLfVuUcYg=="], + "tsc-alias": ["tsc-alias@1.9.2", "", { "dependencies": { "chokidar": "^3.5.3", "commander": "^9.0.0", "get-tsconfig": "^4.10.0", "globby": "^11.0.4", "mylas": "^2.1.9", "normalize-path": "^3.0.0", "plimit-lit": "^1.2.6" }, "bin": { "tsc-alias": "dist/bin/index.js" } }, "sha512-VTWQGMv0xXCEyHDLpmV2DEvGYHMxwsyx87dZeou2ynkM0+WOFdHe+KWiRucavMPUEdQysr7xSu60Y/WY0R4YKA=="], "tsconfig-paths": ["tsconfig-paths@4.2.0", "", { "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg=="], @@ -1648,7 +1648,7 @@ "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], - "zod": ["zod@4.5.4", "", {}, "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA=="], + "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "zod-from-json-schema": ["zod-from-json-schema@0.4.2", "", { "dependencies": { "zod": "^3.25.25" } }, "sha512-U+SIzUUT7P6w1UNAz81Sj0Vko77eQPkZ8LbJeXqQbwLmq1MZlrjB3Gj4LuebqJW25/CzS9WA8SjTgR5lvuv+zA=="], @@ -1662,9 +1662,9 @@ "@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.18", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ=="], - "@ai-sdk/gateway/@ai-sdk/provider": ["@ai-sdk/provider@4.0.9", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XnGXPWiBIfqjsVEud5pOaVneRByJQOu2sYNwlSVJTPCvakdCDkVuYKKfNuStkIpMUYl7JIkBZGBx+B5YfNeVjA=="], + "@ai-sdk/gateway/@ai-sdk/provider": ["@ai-sdk/provider@4.0.7", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6or44XprPzKbr8zkmzosowSE0pxkvJcoojBL+mCZvPUt3kvXp3XSNqeVun9golb1acEfSo6yaEBRT18h2VU+1Q=="], - "@ai-sdk/gateway/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@5.0.34", "", { "dependencies": { "@ai-sdk/provider": "4.0.9", "@standard-schema/spec": "^1.1.0", "@workflow/serde": "4.1.0", "eventsource-parser": "^3.0.8", "undici": "^7.28.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tRBdgRcys/4d8wyQdOdyYScq1AxfMdMd0hIlwolxJKVIbBwXUgClZuQT0VIsz4e7pylY8FE6utYCCZ494UAMJQ=="], + "@ai-sdk/gateway/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@5.0.29", "", { "dependencies": { "@ai-sdk/provider": "4.0.7", "@standard-schema/spec": "^1.1.0", "@workflow/serde": "4.1.0", "eventsource-parser": "^3.0.8", "undici": "^7.28.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7EIbwXiXKGa7EFk6tDZpuZBs6lxhEJpOuHeqrDb3Vd85uYdjwkdRuHnZDVDIIb2+QTSRmyph2NrXcbvuO/KAjQ=="], "@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], @@ -1688,8 +1688,6 @@ "@opentui/react/react-reconciler": ["react-reconciler@0.33.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="], - "@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=="], @@ -1704,9 +1702,9 @@ "accepts/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], - "ai/@ai-sdk/provider": ["@ai-sdk/provider@4.0.9", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XnGXPWiBIfqjsVEud5pOaVneRByJQOu2sYNwlSVJTPCvakdCDkVuYKKfNuStkIpMUYl7JIkBZGBx+B5YfNeVjA=="], + "ai/@ai-sdk/provider": ["@ai-sdk/provider@4.0.7", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6or44XprPzKbr8zkmzosowSE0pxkvJcoojBL+mCZvPUt3kvXp3XSNqeVun9golb1acEfSo6yaEBRT18h2VU+1Q=="], - "ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@5.0.34", "", { "dependencies": { "@ai-sdk/provider": "4.0.9", "@standard-schema/spec": "^1.1.0", "@workflow/serde": "4.1.0", "eventsource-parser": "^3.0.8", "undici": "^7.28.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tRBdgRcys/4d8wyQdOdyYScq1AxfMdMd0hIlwolxJKVIbBwXUgClZuQT0VIsz4e7pylY8FE6utYCCZ494UAMJQ=="], + "ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@5.0.29", "", { "dependencies": { "@ai-sdk/provider": "4.0.7", "@standard-schema/spec": "^1.1.0", "@workflow/serde": "4.1.0", "eventsource-parser": "^3.0.8", "undici": "^7.28.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7EIbwXiXKGa7EFk6tDZpuZBs6lxhEJpOuHeqrDb3Vd85uYdjwkdRuHnZDVDIIb2+QTSRmyph2NrXcbvuO/KAjQ=="], "bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], diff --git a/cli/release/package.json b/cli/release/package.json index 2218ac8870..6182990a49 100644 --- a/cli/release/package.json +++ b/cli/release/package.json @@ -1,6 +1,6 @@ { "name": "codebuff", - "version": "1.0.686", + "version": "1.0.685", "description": "AI coding agent", "license": "MIT", "bin": { diff --git a/cli/src/__tests__/unit/freebuff-reasoning.test.ts b/cli/src/__tests__/unit/freebuff-reasoning.test.ts deleted file mode 100644 index 193fb924ae..0000000000 --- a/cli/src/__tests__/unit/freebuff-reasoning.test.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { describe, expect, test, beforeEach, afterAll } from 'bun:test' -import { mkdtempSync, readFileSync, rmSync } from 'node:fs' -import { tmpdir } from 'node:os' -import { join } from 'node:path' - -import { - FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID, - FREEBUFF_GLM_V53_FLASH_MODEL_ID, - FREEBUFF_MIMO_V25_MODEL_ID, - getFreebuffModelDefaultEffort, - getFreebuffModelEfforts, -} from '@codebuff/common/constants/freebuff-models' - -import type { ReasoningEffort } from '@codebuff/common/constants/reasoning-effort' - -/** - * `/reasoning` is the CLI's counterpart to Desktop's effort picker. Both end up - * writing the same `freebuff_reasoning_effort` metadata key, and the server - * treats it as a REQUEST it re-clamps — so the client's job is only to send a - * rung the selected model actually offers, and to send NOTHING when the user - * has expressed no preference. - * - * That last part is the one a type-check cannot catch: sending the model - * default explicitly type-checks, reads correctly, and silently overrides an - * agent's own declared reasoning while looking like a user decision. - * - * Settings are redirected by HOME rather than by mocking `../utils/settings`. - * `mock.module` is process-global in bun and is NOT scoped to the file that - * calls it: a settings mock here reached the freebuff-model-selector suite that - * runs later in the same process and failed 18 of its tests. A temp HOME also - * exercises the real load/save round trip, which is where the catalog - * validation lives. - */ -const realHome = process.env.HOME -const tempHome = mkdtempSync(join(tmpdir(), 'freebuff-reasoning-')) -process.env.HOME = tempHome -afterAll(() => { - if (realHome === undefined) delete process.env.HOME - else process.env.HOME = realHome - rmSync(tempHome, { recursive: true, force: true }) -}) - -const { handleReasoningCommand } = await import('../../commands/reasoning') -const { - getFreebuffReasoningEffortForModel, - getEffectiveFreebuffReasoningEffort, - getSelectedFreebuffReasoningEffort, - useFreebuffModelStore, -} = await import('../../state/freebuff-model-store') -const { loadFreebuffReasoningEfforts } = await import('../../utils/settings') - -const LADDERED = FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID -const NO_LADDER = FREEBUFF_MIMO_V25_MODEL_ID - -describe('/reasoning', () => { - beforeEach(() => { - useFreebuffModelStore.setState({ - selectedModel: LADDERED, - reasoningEffortByModel: {}, - }) - useFreebuffModelStore.getState().setReasoningEffort(LADDERED, undefined) - useFreebuffModelStore.getState().setReasoningEffort(NO_LADDER, undefined) - }) - - test('the catalog still gives the model under test a ladder', () => { - // Guards the rest of the file: if V4 Flash ever loses its `efforts`, every - // assertion below would pass vacuously against the no-ladder branch. - expect(getFreebuffModelEfforts(LADDERED)).toBeTruthy() - expect(getFreebuffModelEfforts(NO_LADDER)).toBeNull() - }) - - test('with no argument it reports the model default and does not set one', () => { - const { message } = handleReasoningCommand('') - expect(message).toContain(getFreebuffModelDefaultEffort(LADDERED)!) - expect(message).toContain('model default') - // The read path must stay a read: nothing sent until the user picks. - expect(getSelectedFreebuffReasoningEffort()).toBeNull() - }) - - test('a valid rung is set, sent, and survives a reload', () => { - handleReasoningCommand('max') - expect(getSelectedFreebuffReasoningEffort()).toBe('max') - expect(loadFreebuffReasoningEfforts()[LADDERED]).toBe('max') - }) - - test('an invalid rung changes nothing and names the ladder', () => { - handleReasoningCommand('max') - const { message } = handleReasoningCommand('gigantic') - // DeepSeek accepts any string for reasoning_effort and silently ignores - // what it does not recognize, so a bad word must never reach the wire. - expect(getSelectedFreebuffReasoningEffort()).toBe('max') - expect(message).toContain('low') - }) - - test('a rung the model does not offer is refused even though it is a real effort', () => { - // `xhigh` is on the shared ladder but not on DeepSeek V4's. - expect(getFreebuffModelEfforts(LADDERED)).not.toContain('xhigh') - handleReasoningCommand('xhigh') - expect(getSelectedFreebuffReasoningEffort()).toBeNull() - }) - - test('default/reset clears the override rather than storing the default', () => { - handleReasoningCommand('low') - handleReasoningCommand('default') - // Absent, not "low" and not the default value: absence is how the client - // says "no preference", and it is what lets the server apply the catalog - // default without treating the turn as a user choice. - expect(loadFreebuffReasoningEfforts()[LADDERED]).toBeUndefined() - expect(getSelectedFreebuffReasoningEffort()).toBeNull() - // The row still displays what it will run at. - expect(getEffectiveFreebuffReasoningEffort(LADDERED)).toBe( - getFreebuffModelDefaultEffort(LADDERED), - ) - }) - - test('a model with no ladder is told so and nothing is stored', () => { - useFreebuffModelStore.setState({ selectedModel: NO_LADDER }) - const { message } = handleReasoningCommand('high') - expect(message).toContain('no reasoning levels') - expect(loadFreebuffReasoningEfforts()[NO_LADDER]).toBeUndefined() - }) - - test('overrides are per model, so switching model does not carry a rung across', () => { - handleReasoningCommand('max') - useFreebuffModelStore.setState({ selectedModel: NO_LADDER }) - expect(getSelectedFreebuffReasoningEffort()).toBeNull() - useFreebuffModelStore.setState({ selectedModel: LADDERED }) - expect(getSelectedFreebuffReasoningEffort()).toBe('max') - }) - - test('GLM 5.3 Flash is pickable here, at its own ladder and default', () => { - // The row shipped with no ladder at all and the CLI answered "no reasoning - // levels to adjust" for it. Asserted on the CONCRETE model rather than - // through the generic laddered path because the regression to guard is the - // catalog row losing `efforts` again, which the LADDERED constant above - // would not notice. - useFreebuffModelStore.setState({ selectedModel: FREEBUFF_GLM_V53_FLASH_MODEL_ID }) - expect(getFreebuffModelEfforts(FREEBUFF_GLM_V53_FLASH_MODEL_ID)).toEqual([ - 'low', - 'high', - 'max', - ]) - - const before = handleReasoningCommand('') - expect(before.message).toContain('max (model default)') - expect(before.message).toContain('low, high, max') - // Nothing sent until the user actually picks — the model default is the - // server's job, and sending it would look like a decision. - expect(getSelectedFreebuffReasoningEffort()).toBeNull() - - handleReasoningCommand('low') - expect(getSelectedFreebuffReasoningEffort()).toBe('low') - expect( - loadFreebuffReasoningEfforts()[FREEBUFF_GLM_V53_FLASH_MODEL_ID], - ).toBe('low') - - // `xhigh` is on the shared ladder but not this model's, so the CLI refuses - // it locally rather than letting the server clamp it to something the user - // did not choose. - const refused = handleReasoningCommand('xhigh') - expect(refused.message).toContain('is not a reasoning level') - expect(getSelectedFreebuffReasoningEffort()).toBe('low') - }) - - test('a stored rung the model no longer offers is ignored, not clamped', () => { - // Simulates a catalog change landing under a settings file written by an - // older client. Sending it would have the server clamp DOWN to a rung the - // user never picked; sending nothing lands on the model's own default. - useFreebuffModelStore.setState({ - reasoningEffortByModel: { [LADDERED]: 'xhigh' as ReasoningEffort }, - }) - expect(getFreebuffReasoningEffortForModel(LADDERED)).toBeNull() - }) -}) - -/** - * The send path, asserted by reading the source for the same reason the runner's - * effortForwarding test does: an absent metadata field IS how "use the default" - * is expressed, so a dropped value is invisible to every other test. - */ -describe('the CLI turn carries the chosen effort', () => { - const source = readFileSync( - join(import.meta.dir, '..', '..', 'hooks', 'use-send-message.ts'), - 'utf8', - ) - - test('it reaches extraCodebuffMetadata under the name the server reads', () => { - const metadata = source.slice(source.indexOf('extraCodebuffMetadata:')) - expect(metadata).toContain('freebuff_reasoning_effort') - expect(metadata).toContain('freebuffReasoningEffort') - }) -}) diff --git a/cli/src/chat.tsx b/cli/src/chat.tsx index 8a2770b575..88f6662fe8 100644 --- a/cli/src/chat.tsx +++ b/cli/src/chat.tsx @@ -535,7 +535,6 @@ export const Chat = ({ logoutMutation, streamMessageIdRef, addToQueue, - hasQueuedMessages: () => queuedCount > 0, clearMessages, saveToHistory, scrollToLatest, diff --git a/cli/src/commands/__tests__/router-steering.test.ts b/cli/src/commands/__tests__/router-steering.test.ts deleted file mode 100644 index cb15dcf92b..0000000000 --- a/cli/src/commands/__tests__/router-steering.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' - -import { useChatStore } from '../../state/chat-store' -import { - __resetSteeringForTests, - activateSteering, - drainSteeringMessages, -} from '../../utils/steering-buffer' -import { routeUserPrompt } from '../router' - -import type { RouterParams } from '../command-registry' - -const createMockParams = (overrides: Partial = {}): RouterParams => - ({ - agentMode: 'DEFAULT', - inputRef: { current: null }, - inputValue: '', - isChainInProgressRef: { current: false }, - isStreaming: false, - logoutMutation: {} as RouterParams['logoutMutation'], - streamMessageIdRef: { current: null }, - addToQueue: mock(() => {}), - hasQueuedMessages: () => false, - clearMessages: mock(() => {}), - saveToHistory: mock(() => {}), - scrollToLatest: mock(() => {}), - sendMessage: mock(async () => {}), - setCanProcessQueue: mock(() => {}), - setInputFocused: mock(() => {}), - setInputValue: mock(() => {}), - setIsAuthenticated: mock(() => {}), - setMessages: mock(() => {}), - setUser: mock(() => {}), - ...overrides, - }) as RouterParams - -beforeEach(() => { - useChatStore.getState().clearPendingBashMessages() -}) - -afterEach(() => { - __resetSteeringForTests() - useChatStore.getState().clearPendingBashMessages() -}) - -describe('mid-turn routing', () => { - test('plain text steers the active run and echoes a bubble immediately', async () => { - activateSteering('run-1') - const params = createMockParams({ - inputValue: 'actually use zod for validation', - isStreaming: true, - }) - await routeUserPrompt(params) - - expect(params.addToQueue).not.toHaveBeenCalled() - expect(params.sendMessage).not.toHaveBeenCalled() - // Bubble echoed at push time so the submit is visible right away. - expect(params.setMessages).toHaveBeenCalledTimes(1) - const drained = drainSteeringMessages('run-1') - expect(drained.map((entry) => entry.text)).toEqual([ - 'actually use zod for validation', - ]) - expect(drained[0]!.messageId).toStartWith('user-') - }) - - test('falls back to the queue when no run is accepting steering', async () => { - const params = createMockParams({ - inputValue: 'between chained runs', - isStreaming: true, - }) - await routeUserPrompt(params) - - expect(params.addToQueue).toHaveBeenCalledTimes(1) - const [queued] = (params.addToQueue as ReturnType).mock - .calls[0] as [string] - expect(queued).toBe('between chained runs') - }) - - test('queues instead of steering when earlier messages are already queued', async () => { - activateSteering('run-1') - const params = createMockParams({ - inputValue: 'this must not overtake the queue', - isStreaming: true, - hasQueuedMessages: () => true, - }) - await routeUserPrompt(params) - - expect(drainSteeringMessages('run-1')).toEqual([]) - expect(params.addToQueue).toHaveBeenCalledTimes(1) - }) - - test('queues instead of steering while bash output is pending', async () => { - activateSteering('run-1') - useChatStore.getState().addPendingBashMessage({ - command: 'bun test', - output: '3 fail', - } as never) - const params = createMockParams({ - inputValue: 'fix those failures', - isStreaming: true, - }) - await routeUserPrompt(params) - - expect(drainSteeringMessages('run-1')).toEqual([]) - expect(params.addToQueue).toHaveBeenCalledTimes(1) - }) - - test('slash commands never steer', async () => { - activateSteering('run-1') - const params = createMockParams({ - inputValue: '/definitely-not-a-command', - isStreaming: true, - }) - await routeUserPrompt(params) - - expect(drainSteeringMessages('run-1')).toEqual([]) - expect(params.addToQueue).toHaveBeenCalledTimes(1) - }) - - test('idle submits are unaffected and send normally', async () => { - activateSteering('run-1') - const params = createMockParams({ inputValue: 'a fresh task' }) - await routeUserPrompt(params) - - expect(params.sendMessage).toHaveBeenCalledTimes(1) - expect(drainSteeringMessages('run-1')).toEqual([]) - }) -}) diff --git a/cli/src/commands/__tests__/skill-command.test.ts b/cli/src/commands/__tests__/skill-command.test.ts deleted file mode 100644 index 9ff609622a..0000000000 --- a/cli/src/commands/__tests__/skill-command.test.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' - -import { useChatStore } from '../../state/chat-store' -import { - __resetSkillRegistryForTests, - __setSkillsForTests, -} from '../../utils/skill-registry' -import { findCommand } from '../command-registry' -import { buildSkillPrompt } from '../prompt-builders' -import { routeUserPrompt } from '../router' - -import type { RouterParams } from '../command-registry' -import type { SkillDefinition } from '@codebuff/common/types/skill' - -const TEST_SKILL: SkillDefinition = { - name: 'release-notes', - description: 'Draft release notes from recent commits', - content: - '---\nname: release-notes\ndescription: Draft release notes\n---\n\nDo the thing.', - filePath: '/tmp/skills/release-notes/SKILL.md', -} - -const createMockParams = (overrides: Partial = {}): RouterParams => - ({ - agentMode: 'DEFAULT', - inputRef: { current: null }, - inputValue: '', - isChainInProgressRef: { current: false }, - isStreaming: false, - logoutMutation: {} as RouterParams['logoutMutation'], - streamMessageIdRef: { current: null }, - addToQueue: mock(() => {}), - clearMessages: mock(() => {}), - saveToHistory: mock(() => {}), - scrollToLatest: mock(() => {}), - sendMessage: mock(async () => {}), - setCanProcessQueue: mock(() => {}), - setInputFocused: mock(() => {}), - setInputValue: mock(() => {}), - setIsAuthenticated: mock(() => {}), - setMessages: mock(() => {}), - setUser: mock(() => {}), - ...overrides, - }) as RouterParams - -const resetChatStore = () => { - useChatStore.getState().setInputMode('default') - useChatStore.getState().setPendingSkillName(null) -} - -beforeEach(() => { - __setSkillsForTests({ [TEST_SKILL.name]: TEST_SKILL }) - resetChatStore() -}) - -afterEach(() => { - __resetSkillRegistryForTests() - resetChatStore() -}) - -describe('/skill: command', () => { - test('bare invocation enters skill input mode instead of sending', async () => { - const command = findCommand('skill:release-notes') - expect(command).toBeDefined() - - const params = createMockParams({ inputValue: '/skill:release-notes' }) - await command!.handler(params, '') - - expect(useChatStore.getState().inputMode).toBe('skill') - expect(useChatStore.getState().pendingSkillName).toBe('release-notes') - expect(params.sendMessage).not.toHaveBeenCalled() - expect(params.addToQueue).not.toHaveBeenCalled() - }) - - test('invocation with trailing text sends immediately', async () => { - const command = findCommand('skill:release-notes') - const params = createMockParams({ - inputValue: '/skill:release-notes for v2.1 only', - }) - await command!.handler(params, 'for v2.1 only') - - expect(useChatStore.getState().inputMode).toBe('default') - expect(params.sendMessage).toHaveBeenCalledTimes(1) - const [{ content }] = (params.sendMessage as ReturnType).mock - .calls[0] as [{ content: string }] - expect(content).toBe(buildSkillPrompt(TEST_SKILL, 'for v2.1 only')) - expect(content).toContain('') - expect(content).toContain('User request: for v2.1 only') - }) -}) - -describe('skill input mode submit', () => { - const enterSkillMode = () => { - useChatStore.getState().setInputMode('skill') - useChatStore.getState().setPendingSkillName(TEST_SKILL.name) - } - - test('submit with text sends the skill plus the user request', async () => { - enterSkillMode() - const params = createMockParams({ inputValue: 'focus on the API changes' }) - await routeUserPrompt(params) - - expect(useChatStore.getState().inputMode).toBe('default') - expect(useChatStore.getState().pendingSkillName).toBeNull() - expect(params.sendMessage).toHaveBeenCalledTimes(1) - const [{ content }] = (params.sendMessage as ReturnType).mock - .calls[0] as [{ content: string }] - expect(content).toBe( - buildSkillPrompt(TEST_SKILL, 'focus on the API changes'), - ) - }) - - test('empty submit runs the skill without a user request', async () => { - enterSkillMode() - const params = createMockParams({ inputValue: '' }) - await routeUserPrompt(params) - - expect(params.sendMessage).toHaveBeenCalledTimes(1) - const [{ content }] = (params.sendMessage as ReturnType).mock - .calls[0] as [{ content: string }] - expect(content).toBe(buildSkillPrompt(TEST_SKILL, '')) - expect(content).not.toContain('User request:') - }) - - test('submit while a turn is running queues instead of sending', async () => { - enterSkillMode() - const params = createMockParams({ - inputValue: 'and be brief', - isStreaming: true, - }) - await routeUserPrompt(params) - - expect(params.sendMessage).not.toHaveBeenCalled() - expect(params.addToQueue).toHaveBeenCalledTimes(1) - const [queued] = (params.addToQueue as ReturnType).mock - .calls[0] as [string] - expect(queued).toBe(buildSkillPrompt(TEST_SKILL, 'and be brief')) - }) - - test('a skill deleted mid-session reports instead of sending nothing', async () => { - enterSkillMode() - __resetSkillRegistryForTests() - const params = createMockParams({ inputValue: 'anything' }) - await routeUserPrompt(params) - - expect(params.sendMessage).not.toHaveBeenCalled() - expect(params.setMessages).toHaveBeenCalled() - expect(useChatStore.getState().inputMode).toBe('default') - }) - - test('leaving skill mode clears the pending skill', () => { - enterSkillMode() - useChatStore.getState().setInputMode('default') - expect(useChatStore.getState().pendingSkillName).toBeNull() - }) -}) diff --git a/cli/src/commands/command-registry.ts b/cli/src/commands/command-registry.ts index b308c7bb91..a0a6f291a7 100644 --- a/cli/src/commands/command-registry.ts +++ b/cli/src/commands/command-registry.ts @@ -9,8 +9,7 @@ import { collectProcessDiagnostics, formatProcessDiagnostics, } from './process-diagnostics' -import { buildInterviewPrompt, buildPlanPrompt, buildReviewPromptFromArgs, buildSkillPrompt } from './prompt-builders' -import { handleReasoningCommand } from './reasoning' +import { buildInterviewPrompt, buildPlanPrompt, buildReviewPromptFromArgs } from './prompt-builders' import { runBashCommand } from './router' import { handleUsageCommand } from './usage' import { returnToFreebuffLanding } from '../hooks/use-freebuff-session' @@ -44,9 +43,6 @@ export type RouterParams = { logoutMutation: UseMutationResult streamMessageIdRef: React.MutableRefObject addToQueue: (message: string, attachments?: PendingAttachment[]) => void - /** Whether the message queue currently holds anything. Steering checks it - * so a mid-turn submit can't overtake earlier queued submissions. */ - hasQueuedMessages?: () => boolean clearMessages: () => void saveToHistory: (message: string) => void scrollToLatest: () => void @@ -187,11 +183,6 @@ const FREEBUFF_ONLY_COMMANDS = new Set([ 'plan', 'end-session', 'dashboard', - // Freebuff-only because the ladder it reads is the FREEBUFF catalog's, and - // the metadata it sets is honored only for free-mode traffic - // (isFreebuffOriginatedRequest). On Codebuff the command would take a value - // and silently drop it. - 'reasoning', ]) const ALL_COMMANDS: CommandDefinition[] = [ @@ -626,24 +617,6 @@ const ALL_COMMANDS: CommandDefinition[] = [ clearInput(params) }, }), - // /reasoning (freebuff-only) — read or set the thinking level for the - // selected model. Takes effect on the NEXT message: the effort rides - // codebuff_metadata on each request, so nothing about the live session has to - // be restarted for a change to land. - defineCommandWithArgs({ - name: 'reasoning', - aliases: ['effort', 'think'], - handler: (params, args) => { - const { message } = handleReasoningCommand(args) - params.setMessages((prev) => [ - ...prev, - getUserMessage(params.inputValue.trim()), - getSystemMessage(message), - ]) - params.saveToHistory(params.inputValue.trim()) - clearInput(params) - }, - }), // /end-session (freebuff-only) — end the active session early and drop back // to the model picker. The hook flips status to 'none', which unmounts // and mounts , where the user picks a model @@ -718,50 +691,36 @@ function createSkillCommand(skillName: string): CommandDefinition { params.saveToHistory(trimmed) params.setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false }) - // Bare invocation: like /interview, drop into an input mode so the - // user can add instructions before the skill is sent. Enter with an - // empty composer still runs the skill as-is (the router's skill-mode - // branch), so a no-args run costs one extra keystroke, not a feature. - if (!args.trim()) { - useChatStore.getState().enterSkillMode(skill.name) + // Build the message content with skill context and optional user args + const skillContext = ` +${skill.content} +` + + const userPrompt = `I invoke the following skill:\n\n${skillContext}\n\n` + + (args.trim() + ? `User request: ${args.trim()}` + : '') + + // Check streaming/queue state + if ( + params.isStreaming || + params.streamMessageIdRef.current || + params.isChainInProgressRef.current + ) { + const pendingAttachments = capturePendingAttachments() + params.addToQueue(userPrompt, pendingAttachments) params.setInputFocused(true) params.inputRef.current?.focus() return } - dispatchSkillPrompt(params, skill, args) + params.sendMessage({ + content: userPrompt, + agentMode: params.agentMode, + }) + setTimeout(() => { + params.scrollToLatest() + }, 0) }, }) } - -/** - * Send (or queue, mid-turn) a user-invoked skill prompt. Shared by the - * /skill: args form and the skill input mode's submit (router), so the - * two entry paths for the same feature cannot drift. - */ -export function dispatchSkillPrompt( - params: RouterParams, - skill: { name: string; content: string }, - input: string, -): void { - const userPrompt = buildSkillPrompt(skill, input) - - if ( - params.isStreaming || - params.streamMessageIdRef.current || - params.isChainInProgressRef.current - ) { - params.addToQueue(userPrompt, capturePendingAttachments()) - params.setInputFocused(true) - params.inputRef.current?.focus() - return - } - - params.sendMessage({ - content: userPrompt, - agentMode: params.agentMode, - }) - setTimeout(() => { - params.scrollToLatest() - }, 0) -} diff --git a/cli/src/commands/prompt-builders.ts b/cli/src/commands/prompt-builders.ts index 2435238212..4dc9979778 100644 --- a/cli/src/commands/prompt-builders.ts +++ b/cli/src/commands/prompt-builders.ts @@ -39,26 +39,6 @@ export function buildInterviewPrompt(input: string): string { return `${INTERVIEW_BASE_PROMPT}\n\n${trimmedInput}` } -/** - * Build the prompt for a user-invoked skill. Shared by the /skill: - * command (when it carries trailing text) and the skill input mode's second - * submit, so both entry paths produce byte-identical prompts. - * - * `content` is the whole SKILL.md (frontmatter included) — same as the - * agent-runtime's own skill tool output. - */ -export function buildSkillPrompt( - skill: { name: string; content: string }, - input: string, -): string { - const skillContext = `\n${skill.content}\n` - const trimmedInput = input.trim() - return ( - `I invoke the following skill:\n\n${skillContext}\n\n` + - (trimmedInput ? `User request: ${trimmedInput}` : '') - ) -} - /** * Review scope presets for the review screen. */ diff --git a/cli/src/commands/reasoning.ts b/cli/src/commands/reasoning.ts deleted file mode 100644 index f3fe0af775..0000000000 --- a/cli/src/commands/reasoning.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { - getFreebuffModel, - getFreebuffModelDefaultEffort, - getFreebuffModelEfforts, -} from '@codebuff/common/constants/freebuff-models' -import { isReasoningEffort } from '@codebuff/common/constants/reasoning-effort' - -import { - getFreebuffReasoningEffortForModel, - getSelectedFreebuffModel, - useFreebuffModelStore, -} from '../state/freebuff-model-store' - -import type { ReasoningEffort } from '@codebuff/common/constants/reasoning-effort' - -/** Words that mean "stop overriding" rather than naming a rung. `default` is - * the obvious one; `auto` and `reset` are what people type instead. */ -const CLEAR_WORDS = new Set(['default', 'auto', 'reset', 'clear', 'none']) - -function displayName(model: string): string { - return getFreebuffModel(model)?.displayName ?? model -} - -/** - * `/reasoning [level]` — read or set how hard the selected model thinks. - * - * Effort is a REQUEST, not a command: the server re-clamps whatever we send - * against the model that actually runs the turn (resolveFreebuffReasoningEffort), - * which is not always the one selected here — a limited-tier user's premium - * pick is coerced, and a saturated turn can be rerouted mid-flight. So this - * validates against the local catalog for a good error message, and does not - * pretend the answer is final. - * - * Returns the message to post; the caller owns chat state. - */ -export function handleReasoningCommand(args: string): { message: string } { - const model = getSelectedFreebuffModel() - const label = displayName(model) - const efforts = getFreebuffModelEfforts(model) - - if (!efforts) { - return { - message: `${label} has no reasoning levels to adjust — it runs at the provider's own setting. Switch models with /end-session to pick one that does.`, - } - } - - const modelDefault = getFreebuffModelDefaultEffort(model) - const override = getFreebuffReasoningEffortForModel(model) - const ladder = efforts.join(', ') - - const requested = args.trim().toLowerCase() - if (!requested) { - const current = override ?? modelDefault - const suffix = override ? '' : ' (model default)' - return { - message: [ - `Reasoning for ${label}: ${current}${suffix}`, - `Available: ${ladder}`, - `Set it with /reasoning , or /reasoning default to go back to ${modelDefault}.`, - ].join('\n'), - } - } - - if (CLEAR_WORDS.has(requested)) { - useFreebuffModelStore.getState().setReasoningEffort(model, undefined) - return { - message: `Reasoning for ${label} back to the model default (${modelDefault}).`, - } - } - - if (!isReasoningEffort(requested) || !efforts.includes(requested)) { - return { - message: `"${args.trim()}" is not a reasoning level for ${label}. Available: ${ladder}.`, - } - } - - const effort: ReasoningEffort = requested - useFreebuffModelStore.getState().setReasoningEffort(model, effort) - return { - message: `Reasoning for ${label} set to ${effort}. Applies from your next message.`, - } -} diff --git a/cli/src/commands/router.ts b/cli/src/commands/router.ts index 7453034ebe..d9b08aa766 100644 --- a/cli/src/commands/router.ts +++ b/cli/src/commands/router.ts @@ -3,7 +3,6 @@ import { runTerminalCommand } from '@codebuff/sdk' import { - dispatchSkillPrompt, findCommand, type RouterParams, type CommandResult, @@ -26,8 +25,6 @@ import { IS_FREEBUFF } from '../utils/constants' import { getSystemProcessEnv } from '../utils/env' import { terminalCommandBroker } from '../utils/terminal-command-broker' import { getSystemMessage, getUserMessage } from '../utils/message-history' -import { getSkillByName } from '../utils/skill-registry' -import { pushSteeringMessage } from '../utils/steering-buffer' import { capturePendingAttachments, hasProcessingFiles, @@ -261,7 +258,6 @@ export async function routeUserPrompt( isStreaming, streamMessageIdRef, addToQueue, - hasQueuedMessages, saveToHistory, scrollToLatest, sendMessage, @@ -276,10 +272,9 @@ export async function routeUserPrompt( const pendingImages = pendingAttachments.filter((a) => a.kind === 'image') const trimmed = inputValue.trim() - // Allow empty messages if there are pending attachments (images or text). - // Skill mode also accepts an empty submit: it means "run the skill as-is". + // Allow empty messages if there are pending attachments (images or text) const hasAttachments = pendingAttachments.length > 0 - if (!trimmed && !hasAttachments && inputMode !== 'skill') return + if (!trimmed && !hasAttachments) return // DAU signal: one un-sampled event per user-submitted prompt. The CLI's // distinct id resolves to the canonical codebuff user id (anonymous id is @@ -348,38 +343,6 @@ export async function routeUserPrompt( return } - // Handle skill mode input: the user picked a skill (bare /skill:) - // and is now adding instructions. Empty input runs the skill without any. - if (inputMode === 'skill') { - const skillName = useChatStore.getState().pendingSkillName - const skill = skillName ? getSkillByName(skillName) : undefined - - if (!skill) { - // Mode without a resolvable skill (state got out of sync): explain, - // and leave the user's typed text in the composer rather than - // destroying it — only the mode is reset. - setInputMode('default') - setInputFocused(true) - inputRef.current?.focus() - setMessages((prev) => [ - ...prev, - getSystemMessage(`Skill not found: ${skillName ?? '(unknown)'}`), - ]) - return - } - - setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false }) - setInputMode('default') - setInputFocused(true) - inputRef.current?.focus() - - if (trimmed) { - saveToHistory(trimmed) - } - dispatchSkillPrompt(params, skill, trimmed) - return - } - // Handle review mode input if (inputMode === 'review') { if (!trimmed) return @@ -467,35 +430,6 @@ export async function routeUserPrompt( streamMessageIdRef.current || isChainInProgressRef.current ) { - // Steer the running turn when possible: plain text is handed to the - // active run and injected at its next step boundary, so the user can - // redirect the agent without waiting the turn out. Falls back to the - // queue for anything the steering hook can't carry faithfully: - // attachments (strings only), a slash command (queued today so it can - // error/execute after the turn), pending `!` bash output (only - // prepareUserMessage folds it into the message that referenced it), a - // non-empty queue (steering would deliver this text ahead of earlier - // submissions), and the window where no run is accepting steering. - const canSteer = - !hasAttachments && - !isSlashCommand(trimmed) && - useChatStore.getState().pendingBashMessages.length === 0 && - !hasQueuedMessages?.() - if (canSteer) { - // Echo the bubble now, so the submit is visible immediately, and hand - // its id to the buffer: if the run ends before draining this entry, - // use-send-message retracts the bubble and requeues the text (which - // mints its own bubble at dequeue) — no invisible message, no dupe. - const steeredMessage = getUserMessage(trimmed) - if ( - pushSteeringMessage({ messageId: steeredMessage.id, text: trimmed }) - ) { - setMessages((prev) => [...prev, steeredMessage]) - setInputFocused(true) - inputRef.current?.focus() - return - } - } const pendingAttachmentsForQueue = capturePendingAttachments() // Pass a copy of pending attachments to the queue addToQueue(trimmed, pendingAttachmentsForQueue) diff --git a/cli/src/components/__tests__/ad-banner.test.tsx b/cli/src/components/__tests__/ad-banner.test.tsx index 8faf9df43b..449580e371 100644 --- a/cli/src/components/__tests__/ad-banner.test.tsx +++ b/cli/src/components/__tests__/ad-banner.test.tsx @@ -9,7 +9,6 @@ import { getAdDisplayLabel, getCardAdLayout, getInlineAdLayout, - orderedRequestedAds, } from '../ad-banner' import { initializeThemeStore } from '../../hooks/use-theme' @@ -17,58 +16,6 @@ beforeAll(() => { initializeThemeStore() }) -describe('requested waiting-room ads', () => { - test('mount order follows the canonical request and excludes duplicates/unrequested ads', () => { - const ads = [ - { - placementId: 'waiting-room-2', - impUrl: 'two', - adText: '', - title: '', - cta: '', - url: '', - favicon: '', - clickUrl: '', - }, - { - placementId: 'waiting-room-1', - impUrl: 'one', - adText: '', - title: '', - cta: '', - url: '', - favicon: '', - clickUrl: '', - }, - { - placementId: 'waiting-room-1', - impUrl: 'duplicate', - adText: '', - title: '', - cta: '', - url: '', - favicon: '', - clickUrl: '', - }, - { - placementId: 'waiting-room-4', - impUrl: 'hidden', - adText: '', - title: '', - cta: '', - url: '', - favicon: '', - clickUrl: '', - }, - ] - expect( - orderedRequestedAds(ads, ['waiting-room-1', 'waiting-room-2']).map( - (ad) => ad.impUrl, - ), - ).toEqual(['one', 'two']) - }) -}) - describe('card ad layout', () => { const ad = { adText: @@ -224,31 +171,6 @@ describe('card ad render', () => { expect(await renderCard({})).toContain('Ad') }) - test('reports presentation only after the card mounts', async () => { - const presented: string[] = [] - const setup = await createTestRenderer({ - width: 78, - height: AD_CARD_HEIGHT, - }) - const root = createRoot(setup.renderer) - const mountedAd = { ...ad, provider: 'first_party' as const } - - flushSync(() => { - root.render( - presented.push(presentedAd.impUrl)} - />, - ) - }) - await setup.renderOnce() - expect(presented).toEqual(['imp-1']) - - flushSync(() => root.unmount()) - setup.renderer.destroy() - }) - test('does not print the headline twice when there is no CTA', async () => { const frame = await renderCard({ cta: '' }) diff --git a/cli/src/components/__tests__/deepseek-quota-row.test.tsx b/cli/src/components/__tests__/deepseek-quota-row.test.tsx index c99e61c200..5f699cbbf4 100644 --- a/cli/src/components/__tests__/deepseek-quota-row.test.tsx +++ b/cli/src/components/__tests__/deepseek-quota-row.test.tsx @@ -77,14 +77,6 @@ describe('a section holding two pools', () => { ).toBe('DeepSeek: 1 of 1 used') }) - test('an admission-counted chip says starts', () => { - const counted = { - ...quotas[FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID], - countsAdmissions: true as const, - } - expect(formatFreebuffRowQuota(counted)).toBe('DeepSeek: 1 of 1 starts') - }) - test('nothing is singled out when every row shares a pool', () => { const onePool = { [FREEBUFF_GPT_5_6_LUNA_MODEL_ID]: quotas[FREEBUFF_GPT_5_6_LUNA_MODEL_ID]!, diff --git a/cli/src/components/__tests__/freebuff-model-selector.test.tsx b/cli/src/components/__tests__/freebuff-model-selector.test.tsx index c4d0a60cf4..51696157bd 100644 --- a/cli/src/components/__tests__/freebuff-model-selector.test.tsx +++ b/cli/src/components/__tests__/freebuff-model-selector.test.tsx @@ -10,8 +10,7 @@ import { FALLBACK_FREEBUFF_MODEL_ID, FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID, FREEBUFF_MIMO_V25_MODEL_ID, - FREEBUFF_GLM_V53_FLASH_MODEL_ID, - FREEBUFF_SOLAR_PRO_4_MODEL_ID, + FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID, FREEBUFF_FABLE_5_MODEL_ID, FREEBUFF_GLM_V52_MODEL_ID, FREEBUFF_GPT_5_6_LUNA_MODEL_ID, @@ -161,25 +160,19 @@ describe('FreebuffModelSelector tier layout', () => { status: 'none', accessTier: 'full', }) - // The saved pick has to be a PREMIUM row that is NOT the recommended hero: - // premium or the tier headers it is being ordered against don't apply to - // it, non-hero or the landing picker opens collapsed and there are no tier - // headers at all. The hero is GPT-5.6 Luna since 2026-08-24, which leaves - // exactly one other premium row — Solar Pro 4 today. - // - // The occupants keep leaving downward: V4 Flash left - // FREEBUFF_PREMIUM_MODEL_IDS on 2026-08-24, V4 Pro was withdrawn on 08-26, - // GLM 5.3 Flash was un-premiumed on 08-28 and moved into UNLIMITED — below - // the header this asserts it sits above. Read the list, not this comment. + // The saved pick has to be something OTHER than the recommended hero, or + // the landing picker opens collapsed and there are no tier headers to + // order. The hero is DeepSeek V4 Flash again since 2026-08-20, so Luna is + // the premium row that exercises "saved model stays focused" here. useFreebuffModelStore .getState() - .setSelectedModel(FREEBUFF_SOLAR_PRO_4_MODEL_ID) + .setSelectedModel(FREEBUFF_GPT_5_6_LUNA_MODEL_ID) const setup = await renderSelector() const frame = setup.captureCharFrame() const premiumHeaderIndex = frame.indexOf('PREMIUM') - const recommendedModelIndex = frame.indexOf('GPT-5.6 Luna') - const selectedModelIndex = frame.indexOf('Solar Pro 4') + const recommendedModelIndex = frame.indexOf('DeepSeek V4 Flash') + const selectedModelIndex = frame.indexOf('GPT-5.6 Luna') const unlimitedHeaderIndex = frame.indexOf('UNLIMITED') expect(premiumHeaderIndex).toBeGreaterThanOrEqual(0) @@ -189,8 +182,8 @@ describe('FreebuffModelSelector tier layout', () => { // 2026-08-20 and left the picker entirely. expect(unlimitedHeaderIndex).toBeGreaterThan(selectedModelIndex) // The cursor sits on the SAVED pick, not on the recommendation. - expect(frame).toContain('› Solar Pro 4') - expect(frame).not.toContain('› GPT-5.6 Luna') + expect(frame).toContain('› GPT-5.6 Luna') + expect(frame).not.toContain('› MiniMax M3') }) /** @@ -303,25 +296,23 @@ describe('FreebuffModelSelector tier layout', () => { }) test('collapses to the unlimited hero when the premium default is spent', async () => { - // A returning user sitting on a spent PREMIUM row opens the picker already - // on a row `pick` silently refuses. Both the selection AND the cursor have - // to leave it, or Enter does nothing with no explanation — and the picker - // has to collapse onto the replacement, or it opens on greyed, unusable - // premium rows with the recommendation below them. - // - // KEYED ON A PREMIUM ROW (Luna), NOT ON THE DEFAULT. It used to key on - // DEFAULT_FREEBUFF_MODEL_ID, which was right for as long as every default - // was premium — 2026-08-12 to 08-30. The default is now unmetered, so - // exhausting "its pool" exhausts nothing and the step-down under test never - // fires. Keying on the row that actually HAS a pool keeps this covering the - // behaviour rather than passing vacuously. + // The default selection has been premium since 2026-08-12, so a returning + // user who has spent their pool opens the picker already sitting on a row + // `pick` silently refuses. Both the selection AND the cursor have to leave + // it, or Enter does nothing with no explanation — and the picker has to + // collapse onto the replacement, or it opens on three greyed, unusable + // premium rows with the recommendation fourth. const resetAt = new Date(FIXED_NOW_MS + 60_000).toISOString() useFreebuffSessionStore.getState().setSession({ status: 'none', accessTier: 'full', + // Keyed on the CURRENT default rather than on a named model: the default + // moved from Flash to V4 Pro on 2026-08-21, and this fixture has to + // exhaust the pool of whichever row the picker will actually open on, or + // the step-down under test never triggers. rateLimitsByModel: { - [FREEBUFF_GPT_5_6_LUNA_MODEL_ID]: { - model: FREEBUFF_GPT_5_6_LUNA_MODEL_ID, + [DEFAULT_FREEBUFF_MODEL_ID]: { + model: DEFAULT_FREEBUFF_MODEL_ID, limit: 6, period: 'pacific_day', resetTimeZone: 'America/Los_Angeles', @@ -331,23 +322,17 @@ describe('FreebuffModelSelector tier layout', () => { }, }, }) - useFreebuffModelStore - .getState() - .setSelectedModel(FREEBUFF_GPT_5_6_LUNA_MODEL_ID) + useFreebuffModelStore.getState().setSelectedModel(DEFAULT_FREEBUFF_MODEL_ID) const setup = await renderSelector() await Promise.resolve() await setup.renderOnce() await setup.renderOnce() - // Lands on the RECOMMENDATION, which is now unmetered — so unlike every - // version of this test since 2026-08-12 the destination is not the - // fallback. The user is moved off the row they cannot use and onto the one - // the picker leads with, rather than being demoted two steps. - expect(getSelectedFreebuffModel()).toBe(DEFAULT_FREEBUFF_MODEL_ID) + expect(getSelectedFreebuffModel()).toBe(FALLBACK_FREEBUFF_MODEL_ID) const frame = setup.captureCharFrame() // `›` is the cursor: it has to be on the row Enter now commits. - expect(frame).toContain('› GLM 5.3 Flash') + expect(frame).toContain('› MiMo 2.5') // …and that row is the whole screen, exactly as for a user who is already // on the recommendation. The spent rows live behind the toggle. expect(frame).toContain('See all') @@ -378,11 +363,8 @@ describe('FreebuffModelSelector tier layout', () => { await setup.renderOnce() await setup.renderOnce() - // Repaired onto the recommendation. Was the fallback while the default was - // premium; an unmetered default is always joinable, so an invalid selection - // now lands on the row the picker leads with. - expect(getSelectedFreebuffModel()).toBe(DEFAULT_FREEBUFF_MODEL_ID) - expect(setup.captureCharFrame()).toContain('› GLM 5.3 Flash') + expect(getSelectedFreebuffModel()).toBe(FALLBACK_FREEBUFF_MODEL_ID) + expect(setup.captureCharFrame()).toContain('› MiMo 2.5') }) test('shows every limited-tier model when the access tier arrives after mount', async () => { @@ -392,7 +374,7 @@ describe('FreebuffModelSelector tier layout', () => { }) useFreebuffModelStore .getState() - .setSelectedModel(FREEBUFF_GLM_V53_FLASH_MODEL_ID) + .setSelectedModel(FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID) const setup = await renderSelector() flushSync(() => { @@ -465,19 +447,11 @@ describe('FreebuffModelSelector tier layout', () => { }) test('sizes and centres a row around its per-row quota chip', async () => { - // The chip is drawn on a row that answers to a DIFFERENT pool than its - // section header, and it was missing from BOTH the centering math and the - // height estimate — visible only once a user had spent a Luna session, - // until the server began sending unused pool rows and it became every - // full-access picker. - // - // WHICH row wears it is arithmetic, not semantic: getFreebuffSectionQuotas - // gives the header to the pool MOST rows share and breaks ties toward the - // earlier row. The occupant has moved with every premium departure — Flash - // out on 2026-08-24, V4 Pro withdrawn 08-26, GLM 5.3 Flash un-premiumed - // 08-28. The invariant under test — a second line the width and height math - // must both know about — is unchanged; only the row it lands on moves, so - // this drives it from the CURRENT premium list rather than naming a row. + // The chip is drawn on a row whose pool is stricter than its section's + // (Luna's one-a-day ceiling), and it was missing from BOTH the centering + // math and the height estimate — visible only once a user had spent a Luna + // session, until the server began sending unused pool rows and it became + // every full-access picker. const resetAt = new Date(FIXED_NOW_MS + 60_000).toISOString() const pool = ( model: string, @@ -498,65 +472,49 @@ describe('FreebuffModelSelector tier layout', () => { useFreebuffSessionStore.getState().setSession({ status: 'none', accessTier: 'full', - // Flash sends no pool row at all since 2026-08-24: it is unmetered. rateLimitsByModel: { - [FREEBUFF_GPT_5_6_LUNA_MODEL_ID]: pool( - FREEBUFF_GPT_5_6_LUNA_MODEL_ID, + [FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID]: pool( + FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID, 'premium', 'Premium', 4, ), - // A row answering to a pool the section header does NOT speak for, so - // it carries its own chip. SYNTHESISED rather than read from - // FREEBUFF_PER_MODEL_SESSION_CAPS, which is empty since 2026-08-28 — - // this test is about the width and height math around a second line, - // not about which model happens to be capped this week, and tying it to - // a real cap is what made it break every time one moved. - [FREEBUFF_SOLAR_PRO_4_MODEL_ID]: pool( - FREEBUFF_SOLAR_PRO_4_MODEL_ID, - 'solar_trial', - 'Solar Pro 4', + [FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID]: pool( + FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID, + 'premium', + 'Premium', + 4, + ), + [FREEBUFF_GPT_5_6_LUNA_MODEL_ID]: pool( + FREEBUFF_GPT_5_6_LUNA_MODEL_ID, + 'luna', + 'Luna', 2, ), }, }) useFreebuffModelStore .getState() - // NOT the hero, so the picker opens expanded and the chip under test is - // drawn at all. Luna took the hero slot on 2026-08-24; selecting it here - // collapses the list to a single row and the chip disappears. V4 Flash - // also supplies the warning-ONLY second line asserted below, which the - // chip row cannot: every row carrying a pool row here also carries a - // chip. - .setSelectedModel(FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID) + .setSelectedModel(FREEBUFF_GPT_5_6_LUNA_MODEL_ID) const frame = (await renderSelector()).captureCharFrame() // Gutters inside the card borders, which is what "centred" means here and // what a length the math didn't know about throws off. Asserted for the // ordinary warning line too, so this pins the invariant rather than the // one string that broke it. - const gutters = (line: string) => { + const gutters = (needle: string) => { + const line = frame.split('\n').find((l) => l.includes(needle))! const inner = line.slice(line.indexOf('│') + 1, line.lastIndexOf('│')) return [ inner.length - inner.trimStart().length, inner.length - inner.trimEnd().length, ] } - const lines = frame.split('\n') - // The second line carrying a per-row chip. Anchored on the chip TEXT, so a - // chip that stops being drawn fails here rather than quietly re-measuring - // some warning-only line instead. A per-row label is longer than the shared - // one, which is the case the width math has to survive. - const chipLine = lines.find((l) => l.includes('Solar Pro 4: 0 of 2 used')) - // Flash carries the training warning with nothing after it — the shape the - // width math already handled, which is the "ordinary warning line" above. - const warningOnlyLine = lines.find( - (l) => l.includes('May use data for AI training') && !l.includes('used'), - ) - expect(chipLine).toBeDefined() - expect(warningOnlyLine).toBeDefined() - for (const line of [chipLine!, warningOnlyLine!]) { - const [left, right] = gutters(line) + for (const needle of [ + 'Luna: 0 of 2 used', + 'May use data for AI training', + ]) { + const [left, right] = gutters(needle) expect(Math.abs(left - right)).toBeLessThanOrEqual(1) } // A row the height estimate does not know has a second line costs the list @@ -572,14 +530,11 @@ describe('FreebuffModelSelector tier layout', () => { status: 'none', accessTier: 'full', }) - // A row that isn't the hero, so the picker opens expanded and the PREMIUM - // header is actually drawn. Flash since 2026-08-24 -- Luna took the hero - // slot, so selecting Luna here would collapse the list. The assertion is - // the ABSENCE of numbers on that header, so the fact that Flash itself - // stopped being premium that same day changes nothing here. + // A premium row that isn't the hero, so the picker opens expanded and the + // PREMIUM header is actually drawn. useFreebuffModelStore .getState() - .setSelectedModel(FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID) + .setSelectedModel(FREEBUFF_GPT_5_6_LUNA_MODEL_ID) const frame = (await renderSelector()).captureCharFrame() // The section still groups the rows; only the invented numbers are gone. @@ -609,26 +564,10 @@ describe('FreebuffModelSelector tier layout', () => { // The reserved cue gutter used to sit between the last badge and the right // border, padding the card out by ~17 columns of empty space. What remains // is ordinary slack from the widest row in the set. - // - // So this bound tracks the WIDEST ROW, not the hero's own content, and it - // moves whenever any row in the set grows. It went 10 -> 14 when GLM 5.3 - // Flash gained a reasoning ladder, which widens its row two different ways: - // a model with a pinned `reasoningEffort` shows ` · Reasoning: `, and - // a model the user has picked a rung for shows ` · Reasoning: *` - // whether or not one is pinned (see reasoningSuffixFor). GLM 5.3 Flash has - // no pinned effort — it runs at the provider's own setting — but an earlier - // test in this file leaves a saved pick in the store, so the starred form is - // what is actually being measured here. That is the card sizing itself to - // its content, which is the behaviour under test. - // - // Kept well under 17 deliberately — the number has to stay small enough to - // fail if the reserved gutter ever comes back, which is the only thing this - // assertion is really guarding. Widen it again only for a real content - // change, and check WHICH row got wider before you do. const gapToBorder = heroRow.length - 1 - (heroRow.indexOf('NEW') + 'NEW'.length) expect(heroRow.endsWith('│')).toBe(true) - expect(gapToBorder).toBeLessThan(14) + expect(gapToBorder).toBeLessThan(10) }) }) @@ -741,77 +680,3 @@ describe('FreebuffModelSelector limited-model offer', () => { expect(isFreebuffModelId(getSelectedFreebuffModel())).toBe(true) }) }) - -describe('FreebuffModelSelector plan line', () => { - const PLAN_SESSION = { - status: 'none', - accessTier: 'full', - subscription: { - tierId: 'starter', - tiers: [ - { - id: 'starter', - displayName: 'Starter', - priceUsd: 8, - firstPeriodPriceUsd: 2.5, - dailySessions: 2, - fiveDaySessions: 6, - monthlySessions: 50, - monthlySpendLimitUsd: 40, - dailyPremiumSessions: 2, - disclaimers: [], - current: true, - upgrade: false, - downgrade: false, - }, - ], - usage: { - dayUsed: 1.3, - dayLimit: 2, - fiveDayUsed: 3, - fiveDayLimit: 6, - monthUsed: 11, - monthLimit: 50, - dayPremiumUsed: 1, - dayPremiumLimit: 2, - dayResetAt: new Date(FIXED_NOW_MS + 3 * 3600_000).toISOString(), - periodEndsAt: new Date( - FIXED_NOW_MS + 20 * 24 * 3600_000, - ).toISOString(), - monthSpendUsd: 3.21, - monthSpendLimitUsd: 40, - }, - }, - } as never - - test('a subscriber sees their plan windows under the catalog', async () => { - useFreebuffSessionStore.getState().setSession(PLAN_SESSION) - const frame = (await renderSelector()).captureCharFrame() - expect(frame).toContain('STARTER PLAN') - expect(frame).toContain('today 1.3 of 2') - expect(frame).toContain('5-day 3 of 6') - expect(frame).toContain('month 11 of 50') - }) - - test('a blocking limit names itself and its reset', async () => { - useFreebuffSessionStore.getState().setSession({ - ...(PLAN_SESSION as Record), - subscription: { - ...(PLAN_SESSION as { subscription: Record }) - .subscription, - blockedBy: 'daily', - }, - } as never) - const frame = (await renderSelector()).captureCharFrame() - expect(frame).toContain("today's plan sessions are used") - expect(frame).toContain('resets in 3h') - }) - - test('no plan means no plan line', async () => { - useFreebuffSessionStore - .getState() - .setSession({ status: 'none', accessTier: 'full' } as never) - const frame = (await renderSelector()).captureCharFrame() - expect(frame).not.toContain('PLAN ·') - }) -}) diff --git a/cli/src/components/__tests__/freebuff-offer-invariants.test.ts b/cli/src/components/__tests__/freebuff-offer-invariants.test.ts index 536c46698d..01ff6a1191 100644 --- a/cli/src/components/__tests__/freebuff-offer-invariants.test.ts +++ b/cli/src/components/__tests__/freebuff-offer-invariants.test.ts @@ -9,35 +9,12 @@ import { describe, expect, test } from 'bun:test' import { getFreebuffRootAgentIdForModel } from '@codebuff/common/constants/free-agents' import { FREEBUFF_GLM_V52_MODEL_ID, - FREEBUFF_GPT_5_6_LUNA_MODEL_ID, - LIMITED_FREEBUFF_MODEL_ID, + resolveFreebuffModelForAccessTier, } from '@codebuff/common/constants/freebuff-models' import { freebuffOfferViolations } from '@codebuff/common/testing/freebuff-offer-invariants' -import { - resolveFreebuffModelPickForSession, - resolveFreebuffModelSelectionForSession, -} from '../../hooks/use-freebuff-session' import { freebuffCliOfferedModelIds } from '../freebuff-model-selector' -import type { FreebuffAccessTier } from '@codebuff/common/constants/freebuff-models' -import type { FreebuffSessionResponse } from '../../types/freebuff-session' - -function cliAcceptsModel( - model: string, - accessTier: FreebuffAccessTier, - hasPaidSubscription = false, -): boolean { - const session: FreebuffSessionResponse = { - status: 'none', - accessTier, - ...(hasPaidSubscription - ? { subscription: { tierId: 'starter', tiers: [] } } - : {}), - } - return resolveFreebuffModelPickForSession(model, session) === model -} - describe('freebuff rows the CLI offers', () => { for (const accessTier of ['full', 'limited'] as const) { test(`are all usable on the ${accessTier} tier`, () => { @@ -48,7 +25,8 @@ describe('freebuff rows the CLI offers', () => { offered: freebuffCliOfferedModelIds(accessTier), // the CLI's own resolver, which every session start runs the selection through: a model // it coerces away is one the user picked and never got - accepts: (model) => cliAcceptsModel(model, accessTier), + accepts: (model) => + resolveFreebuffModelForAccessTier(model, accessTier) === model, rootAgentIdFor: getFreebuffRootAgentIdForModel, catalog: 'supported', }), @@ -56,62 +34,10 @@ describe('freebuff rows the CLI offers', () => { }) } - // A paid plan reaches limited regions, so a limited-region subscriber's grid gains the models - // their plan meters. Its own surface: the CLI's own resolver has to keep the pick too, or the - // user picks the model they bought and the session starts on MiMo. - test('are all usable on the limited tier for a subscriber', () => { - expect( - freebuffOfferViolations({ - surface: 'cli picker + referral banner (limited, subscriber)', - accessTier: 'limited', - hasPaidSubscription: true, - offered: freebuffCliOfferedModelIds('limited', true), - accepts: (model) => cliAcceptsModel(model, 'limited', true), - rootAgentIdFor: getFreebuffRootAgentIdForModel, - catalog: 'supported', - }), - ).toEqual([]) - }) - - // The plan widens what may be PICKED, never what the free pools give. - test('the limited grid keeps every free row for a subscriber', () => { - const free = freebuffCliOfferedModelIds('limited') - const paid = freebuffCliOfferedModelIds('limited', true) - for (const id of free) expect(paid).toContain(id) - expect(paid.length).toBeGreaterThan(free.length) - }) - - test('a limited subscriber startup keeps their saved plan model selected', () => { - const paidSession: FreebuffSessionResponse = { - status: 'none', - accessTier: 'limited', - subscription: { tierId: 'starter', tiers: [] }, - } - const unpaidSession: FreebuffSessionResponse = { - status: 'none', - accessTier: 'limited', - } - - expect( - resolveFreebuffModelSelectionForSession( - FREEBUFF_GPT_5_6_LUNA_MODEL_ID, - paidSession, - ), - ).toBe(FREEBUFF_GPT_5_6_LUNA_MODEL_ID) - expect( - resolveFreebuffModelSelectionForSession( - FREEBUFF_GPT_5_6_LUNA_MODEL_ID, - unpaidSession, - ), - ).toBe(LIMITED_FREEBUFF_MODEL_ID) - }) - test('the earned reward is offered on BOTH tiers, and the grid never shows it', () => { // Limited access included: a bounty grant is redeemable there, so the row has to be // reachable there. The banner still only renders it against a live balance. - expect(freebuffCliOfferedModelIds('full')).toContain( - FREEBUFF_GLM_V52_MODEL_ID, - ) + expect(freebuffCliOfferedModelIds('full')).toContain(FREEBUFF_GLM_V52_MODEL_ID) expect(freebuffCliOfferedModelIds('limited')).toContain( FREEBUFF_GLM_V52_MODEL_ID, ) diff --git a/cli/src/components/__tests__/status-bar.test.tsx b/cli/src/components/__tests__/status-bar.test.tsx index 2db6c9afd5..b0af467ef3 100644 --- a/cli/src/components/__tests__/status-bar.test.tsx +++ b/cli/src/components/__tests__/status-bar.test.tsx @@ -1,18 +1,12 @@ import { beforeAll, describe, expect, test } from 'bun:test' -import { FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID } from '@codebuff/common/constants/freebuff-model-ids' import { createTestRenderer } from '@opentui/core/testing' import { createRoot, flushSync } from '@opentui/react' import React from 'react' import { StatusBar } from '../status-bar' import { initializeThemeStore } from '../../hooks/use-theme' -import { useChatStore } from '../../state/chat-store' -import { IS_FREEBUFF } from '../../utils/constants' import { getStatusIndicatorState } from '../../utils/status-indicator-state' -import type { FreebuffSessionResponse } from '../../types/freebuff-session' -import type { RunState } from '@codebuff/sdk' - beforeAll(() => { initializeThemeStore() }) @@ -47,60 +41,4 @@ describe('StatusBar', () => { setup.renderer.destroy() } }) - - // The idle session line (and therefore the context readout) only renders in - // freebuff builds — useFreebuffSessionProgress returns null otherwise. - test.skipIf(!IS_FREEBUFF)( - 'renders context usage next to the unlimited label', - async () => { - const now = Date.now() - const session = { - status: 'active', - accessTier: 'full', - instanceId: 'test-instance', - model: FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID, - admittedAt: new Date(now - 60_000).toISOString(), - expiresAt: new Date(now + 3_600_000).toISOString(), - remainingMs: 3_600_000, - } as FreebuffSessionResponse - useChatStore.getState().setRunState({ - sessionState: { - mainAgentState: { contextTokenCount: 142_310 }, - }, - } as RunState) - - const statusIndicatorState = getStatusIndicatorState({ - statusMessage: null, - streamStatus: 'idle', - nextCtrlCWillExit: false, - isConnected: true, - }) - // Wide frame: the right-hand flex column takes half the row, and the - // left label truncates rather than wraps. - const setup = await createTestRenderer({ width: 140, height: 3 }) - const root = createRoot(setup.renderer) - flushSync(() => { - root.render( - {}} - statusIndicatorState={statusIndicatorState} - freebuffSession={session} - />, - ) - }) - - try { - await setup.renderOnce() - const frame = setup.captureCharFrame() - // 142,310 of DeepSeek V4 Flash's 1,048,576-token window → 14%. - expect(frame).toContain('unlimited · 142.3K (14%)') - } finally { - flushSync(() => root.unmount()) - setup.renderer.destroy() - useChatStore.getState().setRunState(null) - } - }, - ) }) diff --git a/cli/src/components/ad-banner.tsx b/cli/src/components/ad-banner.tsx index b2b73dafdb..e523575eb8 100644 --- a/cli/src/components/ad-banner.tsx +++ b/cli/src/components/ad-banner.tsx @@ -9,7 +9,6 @@ import { truncateToLines, truncateToWidth, } from '@codebuff/common/ads/inline-ad-layout' -import { visibleWaitingRoomPlacementIds } from '@codebuff/common/ads/waiting-room-placements' import { safeOpen } from '../utils/open-url' import React, { useState, useMemo, useEffect } from 'react' @@ -22,7 +21,6 @@ import type { AdResponse } from '../hooks/use-gravity-ad' interface ChoiceAdBannerProps { ads: AdResponse[] - placementIds?: readonly string[] onClick?: (ad: AdResponse) => void onImpression?: (ad: AdResponse) => void } @@ -33,6 +31,7 @@ interface ChoiceAdBannerProps { // subtracts this from the model picker's height budget. export const AD_CARD_HEIGHT = 5 export const INLINE_AD_CARD_HEIGHT = 4 // border-top + header row + detail row + border-bottom +const MIN_CARD_WIDTH = 60 // Minimum width per ad card to remain readable // Layout lives in `common` so the advertiser campaign builder's creative // preview fits copy exactly the way this renderer does. Re-exported here @@ -332,7 +331,6 @@ export const SingleAdBanner: React.FC<{ */ export const ChoiceAdBanner: React.FC = ({ ads, - placementIds, onClick, onImpression, }) => { @@ -342,14 +340,11 @@ export const ChoiceAdBanner: React.FC = ({ const colAvail = terminalWidth - 2 // Only show as many ads as fit with a healthy minimum width; hide the rest - const maxVisible = - placementIds?.length ?? visibleWaitingRoomPlacementIds(terminalWidth).length - const visibleAds = useMemo(() => { - const requested = placementIds?.length - ? orderedRequestedAds(ads, placementIds) - : ads - return requested.slice(0, maxVisible) - }, [ads, maxVisible, placementIds]) + 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), @@ -384,14 +379,3 @@ export const ChoiceAdBanner: React.FC = ({ ) } - -/** Preserve canonical request order and never mount a duplicate slot response. */ -export function orderedRequestedAds( - ads: AdResponse[], - placementIds: readonly string[], -): AdResponse[] { - return placementIds.flatMap((placementId) => { - const ad = ads.find((candidate) => candidate.placementId === placementId) - return ad ? [ad] : [] - }) -} diff --git a/cli/src/components/chat-input-bar.tsx b/cli/src/components/chat-input-bar.tsx index bd69227021..8f2aadf578 100644 --- a/cli/src/components/chat-input-bar.tsx +++ b/cli/src/components/chat-input-bar.tsx @@ -123,25 +123,8 @@ export const ChatInputBar = ({ }: ChatInputBarProps) => { const inputMode = useChatStore((state) => state.inputMode) const setInputMode = useChatStore((state) => state.setInputMode) - const pendingSkillName = useChatStore((state) => state.pendingSkillName) - - const baseModeConfig = getInputModeConfig(inputMode) - // Skill mode names the pending skill in the banner so the user can see - // what their text will be attached to. Skill names run up to 64 chars; - // keep the banner narrow enough to leave room for typing. - const skillLabel = - inputMode === 'skill' && pendingSkillName - ? pendingSkillName.length > 24 - ? `${pendingSkillName.slice(0, 23)}…` - : pendingSkillName - : null - const modeConfig = skillLabel - ? { - ...baseModeConfig, - label: skillLabel, - widthAdjustment: skillLabel.length + 3, - } - : baseModeConfig + + const modeConfig = getInputModeConfig(inputMode) const askUserState = useChatStore((state) => state.askUserState) const hasAnyPreview = hasSuggestionMenu diff --git a/cli/src/components/freebuff-landing-screen.tsx b/cli/src/components/freebuff-landing-screen.tsx index 815487a121..0c00720cce 100644 --- a/cli/src/components/freebuff-landing-screen.tsx +++ b/cli/src/components/freebuff-landing-screen.tsx @@ -4,7 +4,6 @@ import React, { useCallback, useEffect, useRef, useState } from 'react' import { Button } from './button' import { ChoiceAdBanner, AD_CARD_HEIGHT } from './ad-banner' -import { visibleWaitingRoomPlacementIds } from '@codebuff/common/ads/waiting-room-placements' import { FreebuffModelSelector } from './freebuff-model-selector' import { ShimmerText } from './shimmer-text' import { @@ -406,7 +405,6 @@ export const FreebuffLandingScreen: React.FC = ({ // forceStart bypasses the "wait for first user message" gate inside the hook, // which would otherwise block ads here since no conversation exists yet. // The server tries Gravity first, then falls back to ZeroClick and Carbon. - const waitingRoomPlacementIds = visibleWaitingRoomPlacementIds(terminalWidth) const { ads, recordClick, recordImpression } = useGravityAd({ enabled: true, forceStart: true, @@ -414,7 +412,6 @@ export const FreebuffLandingScreen: React.FC = ({ // Legacy wire name for this surface — the ads API maps it to placements, // so it must not change with the component rename. surface: 'waiting_room', - placementIds: waitingRoomPlacementIds, }) useFreebuffCtrlCExit() @@ -874,7 +871,6 @@ export const FreebuffLandingScreen: React.FC = ({ {ads ? ( diff --git a/cli/src/components/freebuff-model-selector.tsx b/cli/src/components/freebuff-model-selector.tsx index db1bb9ee74..4f6818a065 100644 --- a/cli/src/components/freebuff-model-selector.tsx +++ b/cli/src/components/freebuff-model-selector.tsx @@ -33,14 +33,8 @@ import { getRateLimitsByModel, getGlmPromo, getReferralInfo, - getSubscriptionInfo, } from '@codebuff/common/types/freebuff-session' -import { - formatPlanWindows, - freebuffPlanSummary, -} from '@codebuff/common/util/freebuff-plan-summary' - import { startFreebuffSession } from '../hooks/use-freebuff-session' import { useNow } from '../hooks/use-now' import { useFreebuffModelStore } from '../state/freebuff-model-store' @@ -167,14 +161,8 @@ interface FreebuffModelSelectorProps { * model, so it reaches the user through FreebuffReferralBanner instead. */ function gridModels( accessTier: FreebuffAccessTier, - /** Live paid plan. A plan reaches limited regions, so a subscriber there is - * offered the rows their plan meters instead of MiMo alone — the server - * admits them (see `hasPaidSubscription` on - * isFreebuffSessionModelAllowedForAccessTier), and a picker that hid them - * would sell a plan whose models never appear. */ - hasPaidSubscription = false, ): readonly FreebuffModelOption[] { - return getFreebuffModelsForAccessTier(accessTier, hasPaidSubscription).filter( + return getFreebuffModelsForAccessTier(accessTier).filter( (m) => !isFreebuffGlmV52ModelId(m.id), ) } @@ -190,13 +178,8 @@ function gridModels( * renders the action when the server reports sessions left — and the tier never was. */ export function freebuffCliOfferedModelIds( accessTier: FreebuffAccessTier, - /** See gridModels. */ - hasPaidSubscription = false, ): readonly string[] { - return [ - ...gridModels(accessTier, hasPaidSubscription).map((m) => m.id), - FREEBUFF_GLM_V52_MODEL_ID, - ] + return [...gridModels(accessTier).map((m) => m.id), FREEBUFF_GLM_V52_MODEL_ID] } export const FreebuffModelSelector: React.FC = ({ @@ -213,13 +196,6 @@ export const FreebuffModelSelector: React.FC = ({ const { contentMaxWidth } = useTerminalDimensions() const selectedModel = useFreebuffModelStore((s) => s.selectedModel) const setSelectedModel = useFreebuffModelStore((s) => s.setSelectedModel) - // Subscribed, not read imperatively: `/reasoning` can change a row's effort - // while the picker is unmounted, and the width maths below memoizes on this - // value. Reading the store outside React would leave the memo stale and - // truncate the row it just widened. - const reasoningEffortByModel = useFreebuffModelStore( - (s) => s.reasoningEffortByModel, - ) const session = useFreebuffSessionStore((s) => s.session) const accessTier = (session && 'accessTier' in session ? session.accessTier : undefined) ?? @@ -235,20 +211,7 @@ export const FreebuffModelSelector: React.FC = ({ const [pending, setPending] = useState(null) const [hoveredId, setHoveredId] = useState(null) - // `subscription.tierId` is non-null exactly when the server resolved an - // ENTITLING plan row, so the picker widens on the server's own verdict rather - // than on anything it decides for itself. - const subscriptionInfo = getSubscriptionInfo(session) - const hasPaidSubscription = Boolean(subscriptionInfo?.tierId) - // The paid plan's own windows, rendered as a single muted line below the - // catalog — the CLI counterpart of the web dropdown's plan panel. The same - // shared summary drives Desktop and the web usage page, so all three name - // the same binding limit and the same reset. - const planSummary = freebuffPlanSummary(subscriptionInfo) - const availableModels = useMemo( - () => gridModels(accessTier, hasPaidSubscription), - [accessTier, hasPaidSubscription], - ) + const availableModels = useMemo(() => gridModels(accessTier), [accessTier]) // Capacity-limited models the SERVER decided to offer on this response. The // client has no catalog of its own for these on purpose: when the wave's pool // empties (or the offer is switched off) the payload stops arriving and every @@ -617,32 +580,6 @@ export const FreebuffModelSelector: React.FC = ({ setSelectedModel, ]) - // What the row advertises as this model's reasoning: the user's `/reasoning` - // pick when they made one, otherwise the effort the server pins from the - // catalog. ONE function for both the width maths and the render — they were - // separate strings before the picker gained an override, and a row whose - // suffix outgrows what the width maths budgeted for is a truncated row. - // - // A model with a LADDER but no pinned `reasoningEffort` (Fable 5) still shows - // nothing until the user picks: its default is the provider's own, and - // spending row width to restate it pushed the "see all models" toggle off a - // short terminal. The suffix appears the moment it carries information the - // user did not already have. - const reasoningSuffixFor = useCallback( - (model: FreebuffModelOption): string => { - const chosen = reasoningEffortByModel[model.id] - if (chosen && model.efforts?.includes(chosen)) { - // The '*' marks a rung the USER chose, so a pick is distinguishable - // from the catalog default without a second line. - return ` · Reasoning: ${chosen}*` - } - return model.reasoningEffort - ? ` · Reasoning: ${model.reasoningEffort}` - : '' - }, - [reasoningEffortByModel], - ) - const BUTTON_CHROME = 4 // 2 border + 2 padding const NAME_GAP = 2 // spaces between name column and details column @@ -684,14 +621,9 @@ export const FreebuffModelSelector: React.FC = ({ m.multimodal ? 9 : 0 // Same treatment for the " · Reasoning: high" effort suffix. const reasoningSuffixLen = (m: FreebuffModelOption) => - reasoningSuffixFor(m).length + m.reasoningEffort ? 14 + m.reasoningEffort.length : 0 // Same treatment for the " · NEW" badge (6 chars). const newSuffixLen = 6 -// Ox Alpha reached the CLI on 2026-08-24 as an experimental row. The badge is -// the only promise we can keep about a model an anonymous host can reprice, -// rename or withdraw without notice, so it has to survive the width maths the -// same way NEW does -- an unaccounted suffix truncates the row it labels. -const testSuffixLen = ' · TEST'.length // Line 1, in each mode. const columnLabelLen = (m: FreebuffModelOption) => @@ -701,8 +633,7 @@ const testSuffixLen = ' · TEST'.length m.tagline.length + reasoningSuffixLen(m) + multimodalSuffixLen(m) + - (m.isNew ? newSuffixLen : 0) + - (m.experimental ? testSuffixLen : 0) + (m.isNew ? newSuffixLen : 0) const compactLabelLen = (m: FreebuffModelOption) => 2 + m.displayName.length + @@ -710,8 +641,7 @@ const testSuffixLen = ' · TEST'.length m.tagline.length + reasoningSuffixLen(m) + multimodalSuffixLen(m) + - (m.isNew ? newSuffixLen : 0) + - (m.experimental ? testSuffixLen : 0) + (m.isNew ? newSuffixLen : 0) // Line 2, or 0 for a row with no details. Centered in the card rather // than indented under line 1's details column — the notice is a footnote @@ -758,7 +688,6 @@ const testSuffixLen = ' · TEST'.length availableModels, offerModels, contentMaxWidth, - reasoningSuffixFor, rowDetailsText, supersededNoticeFor, ]) @@ -791,15 +720,6 @@ const testSuffixLen = ' · TEST'.length y += rowHeight(m) }) }) - // The plan summary contributes real rows like everything else here: left - // out of the estimate, the first frame's viewport ends exactly one row - // short per line — the plan line steals the toggle's row and the blocked - // row is clipped outright, which is precisely the row a blocked user - // needs. - if (planSummary) { - y += SECTION_GAP + 1 - if (planSummary.blocked) y += 1 - } if (canCollapse) { y += TOGGLE_MARGIN y += 1 @@ -812,7 +732,6 @@ const testSuffixLen = ' · TEST'.length canCollapse, showStandaloneRecommended, supersededNoticeFor, - planSummary, ]) // When a referral exists, start at the parent's full allowance until the @@ -997,7 +916,11 @@ const testSuffixLen = ' · TEST'.length // see pixels. const imagesSuffix = model.multimodal ? ' · Images' : '' - const reasoningSuffix = reasoningSuffixFor(model) + // The effort the server runs this model at (the same catalog field the + // completions layer sends) — see FreebuffModelOption.reasoningEffort. + const reasoningSuffix = model.reasoningEffort + ? ` · Reasoning: ${model.reasoningEffort}` + : '' return (