diff --git a/.gitignore b/.gitignore index ad23d07463..5da1f76a39 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 @@ -76,9 +70,3 @@ 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..a631ef36df 100644 --- a/README.md +++ b/README.md @@ -34,13 +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 | +| **GPT-5.6 Luna** | Full access | The default in full mode; strong all-around with native images | | **DeepSeek V4 Flash 07/31** | Full access | Fast coding and tool use; pauses during peak hours | | **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 | +| **GLM 5.3 Flash** | Full access | Deepest reasoning; 2 sessions 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. +Most models draw on your normal daily sessions rather than a separate limit. GLM 5.3 Flash is the exception, capped at 2 sessions a day while we measure what it costs at scale. MiMo 2.5 stays unmetered and costs 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. diff --git a/README.zh-CN.md b/README.zh-CN.md index 8965932aec..bccd52ee84 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -34,13 +34,12 @@ Freebuff 提供经过筛选的模型目录。常规模型选择器目前包括 | 模型 | 访问范围 | 适用场景 | | --------------------------- | -------------- | ------------------------------------------------ | -| **GLM 5.3 Flash** | 完整访问 | 完整模式下的默认模型;推理最深入,且不消耗会话 | -| **GPT-5.6 Luna** | 完整访问 | 综合能力强,原生支持图像 | +| **GPT-5.6 Luna** | 完整访问 | 完整模式下的默认模型;综合能力强,原生支持图像 | | **DeepSeek V4 Flash 07/31** | 完整访问 | 快速编程和工具调用;高峰时段暂停 | | **MiMo 2.5** | 完整和受限访问 | 受限模式的默认模型;均衡性能并支持图像 | -| **Solar Pro 4** | 完整访问 | 限时试用;52.4 万上下文,仅支持文本 | +| **GLM 5.3 Flash** | 完整访问 | 推理最深入;每天 2 次会话 | -大多数模型使用你的常规每日会话,而不再各自设限。GLM 5.3 Flash 和 MiMo 2.5 保持无限使用,完全不消耗会话。模型仍可能由量化(Q8_0)版本提供服务。 +大多数模型使用你的常规每日会话,而不再各自设限。GLM 5.3 Flash 是例外:在我们评估其规模化成本期间,每天限用 2 次会话。MiMo 2.5 保持无限使用,完全不消耗会话。模型仍可能由量化(Q8_0)版本提供服务。 DeepSeek V4 Pro 已从模型目录中下线,由 GLM 5.3 Flash 接替其深度推理的位置。 diff --git a/agents/__tests__/base3.test.ts b/agents/__tests__/base3.test.ts index f4870bcce1..cfbd9acd6e 100644 --- a/agents/__tests__/base3.test.ts +++ b/agents/__tests__/base3.test.ts @@ -17,7 +17,6 @@ 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' /** @@ -51,14 +50,13 @@ const CLI_ROOTS = [ 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) + // 13 since GLM 5.3 Flash shipped on 2026-08-26. The count is asserted so a + // root added without the flags below cannot slip in unnoticed. + expect(CLI_ROOTS.length).toBe(13) for (const agent of CLI_ROOTS) { // Windowed reads + the 100-entry glob cap + search-first tool wording. expect(agent.windowedFileReads).toBe(true) diff --git a/agents/base-chat.ts b/agents/base-chat.ts index 0ce94245ac..b90f1c1d62 100644 --- a/agents/base-chat.ts +++ b/agents/base-chat.ts @@ -68,8 +68,6 @@ End every response by calling the suggest_followups tool with exactly 3 followup '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-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-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/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..011afe310a 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.67", "", { "dependencies": { "@ai-sdk/provider": "4.0.8", "@ai-sdk/provider-utils": "5.0.32", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LtyxLkg7dZ2iz8Ouh1806BJbA+q+FKc/mXUCl4v/wdNNIGtbfk80dNtlhqjhqOZa4dnfc3caVafRL7ocxzoegA=="], "@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.35", "", { "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-/5z8tRGuYXwFy0ID+WtiWiECJzH5x/rI/g/3H8x3GQvE4i4etnZfKWAiU23VZEymInh+4l7uMNQJyaNN/54QFw=="], "@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=="], @@ -242,15 +242,19 @@ "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], - "@eslint/config-array": ["@eslint/config-array@0.23.5", "", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="], + "@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="], - "@eslint/config-helpers": ["@eslint/config-helpers@0.7.0", "", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw=="], + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], - "@eslint/core": ["@eslint/core@1.2.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="], + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], - "@eslint/object-schema": ["@eslint/object-schema@3.0.5", "", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="], + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.6", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.3.0", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA=="], - "@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.2", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A=="], + "@eslint/js": ["@eslint/js@9.39.5", "", {}, "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], @@ -390,23 +394,23 @@ "@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.3", "", {}, "sha512-U2eYQRwXj+dsqxV79zFqExDdatnNY/ZWc2nsJU1p/OgT7fd3dXwlF6OjYaFQCfMoeTA19PWq+wVmYgimVA+V+g=="], - "@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.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-8Hiv32QJPwdV6KYJ8meR9SBA061tQqnIKTJDocvOXlEQqib0xMFpzArosuffFUUc0sslbh7QQ8a3Yey1QV8EIw=="], - "@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.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-A1lgKgwVchRYmSe467zdwhxT9040dd8lH+o65sL5Jet8fjB4kegw/rDyPIpYVRb6jAqwXFOJpjIXJLxQKLiE3A=="], - "@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.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-bf0FIssMFueU2dm7vQEWWxk0c8UjKTdW0yzuh0sQsD8pf1+KCLDdaqhYZNMYGmXwEOiHAUzgBKudovIlcvvBjg=="], - "@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.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-W7viwCk9JY/cAkdz/A273rd5bb3RgT/IHwR7Upv90tunjBWNtAAhGhoecHh+teRNRSinuAFmE+l7fwZ4YKkrXg=="], - "@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.3", "", { "os": "linux", "cpu": "x64" }, "sha512-0W46zw1N3ODpI6n0GeivHvvob1pooozgZVqy65k0mh4/7vr+FbY9+WpHzNVXjHipJf/A3FDheBG19H1s5A25rA=="], - "@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.3", "", { "os": "linux", "cpu": "x64" }, "sha512-H4mBso8ZTMBPtdT0PN0pBx2ayTvQuTuvS6qT13d77yVFJXAPCxkyIhLTmdMaGTJs0krQYI/qpzdHijCeihXhbg=="], - "@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.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-cTMUJpcEGmeywofCUfhR+rSsoE33+rVPnPEYNTNdLNlsOeEg/vktOsKUSTb28vUGqD2jkm4Zaskcwn7OCI6FQg=="], - "@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.3", "", { "os": "win32", "cpu": "x64" }, "sha512-2VR4cTBzHXaBjnGsuH6GyJjENzQOmHeAh11uY1iUhjm3j5dEUrVJuUj+VL78jaGi/Dik8xS76zEj18BsFhlVZQ=="], "@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 +442,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.49.1", "", { "dependencies": { "@posthog/types": "^1.407.0" } }, "sha512-jdZh85tG56OXLH881CVwBZyiXCPPaZasfYeWwm9kVUvxC/Rb+lz7wYN9GuqSEmNPJgVnK4v8wS0bCaFc3OmVEA=="], - "@posthog/types": ["@posthog/types@1.407.1", "", {}, "sha512-WhbkXPC2rgylXqmxHqv70ffI3k+KxyR6s7DBIfr5NvIqHkxp6v0pk31D/jbz0DNVbzwkLjyll2pxr4FNbJiYzg=="], + "@posthog/types": ["@posthog/types@1.407.0", "", {}, "sha512-7J/aFVi7JWFt/ekGVsMFvkTcADoE9MTNf1N9cpBSMUQ/SPLiBjbg386Fzk5OlgWG/u5OemBy4wlgD7YebqeppQ=="], "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], @@ -474,8 +478,6 @@ "@types/diff": ["@types/diff@8.0.0", "", { "dependencies": { "diff": "*" } }, "sha512-o7jqJM04gfaYrdCecCVMbZhNdG6T1MHg/oQoRFdERLV+4d+V7FijhiEAbFu0Usww84Yijk9yH58U4Jk4HbtzZw=="], - "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], - "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], "@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="], @@ -550,7 +552,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.83", "", { "dependencies": { "@ai-sdk/gateway": "4.0.67", "@ai-sdk/provider": "4.0.8", "@ai-sdk/provider-utils": "5.0.32" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bg7+SopUwqA7DeQ2O8I9qELyQTHCeeI/0RuNUlT/gGz+LqWrIl5vbYRQv3eMBEnUsVFaM33n6SA8Vqe9gi8L1w=="], "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=="], @@ -560,7 +562,7 @@ "ansi-regex": ["ansi-regex@6.3.0", "", {}, "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ=="], - "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "any-base": ["any-base@1.1.0", "", {}, "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="], @@ -636,6 +638,8 @@ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + "caniuse-lite": ["caniuse-lite@1.0.30001810", "", {}, "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg=="], "canvas": ["canvas@3.2.3", "", { "dependencies": { "node-addon-api": "^7.0.0", "prebuild-install": "^7.1.3" } }, "sha512-PzE5nJZPz72YUAfo8oTp0u3fqqY7IzlTubneAihqDYAUcBk7ryeCmBbdJBEdaH0bptSOe2VT2Zwcb3UaFyaSWw=="], @@ -762,7 +766,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@9.39.5", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.6", "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw=="], "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=="], @@ -774,11 +778,11 @@ "eslint-plugin-unused-imports": ["eslint-plugin-unused-imports@4.4.1", "", { "peerDependencies": { "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", "eslint": "^10.0.0 || ^9.0.0 || ^8.0.0" }, "optionalPeers": ["@typescript-eslint/eslint-plugin"] }, "sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ=="], - "eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], "eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - "espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="], + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], @@ -808,7 +812,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 +830,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.2", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-UpGiiODyCGprM8EPP6JodP6jC9Rws6TCuiDOD+nn0CJhR8guI3g/ozo4ugL0vJ+Yz1UtJuuRPqvQuybVOF1VQA=="], "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], @@ -890,6 +894,8 @@ "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], "globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="], @@ -904,6 +910,8 @@ "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], "has-proto": ["has-proto@1.2.0", "", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="], @@ -934,6 +942,8 @@ "immer": ["immer@10.2.0", "", {}, "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw=="], + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], @@ -942,7 +952,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=="], @@ -1052,6 +1062,8 @@ "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "log-update": ["log-update@8.0.0", "", { "dependencies": { "ansi-escapes": "^7.3.0", "cli-cursor": "^5.0.0", "slice-ansi": "^9.0.0", "string-width": "^8.2.0", "strip-ansi": "^7.2.0", "wrap-ansi": "^10.0.0" } }, "sha512-lddSgOt3bPASrylL54ZSpy8nBHns+vBVSoILlVOx+dei300pnLRN958rj/EdlVLKuWlSESU3qdnDZdAI7FXYGg=="], "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], @@ -1186,11 +1198,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.3", "", { "dependencies": { "@next/env": "16.3.3", "@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.3", "@next/swc-darwin-x64": "16.3.3", "@next/swc-linux-arm64-gnu": "16.3.3", "@next/swc-linux-arm64-musl": "16.3.3", "@next/swc-linux-x64-gnu": "16.3.3", "@next/swc-linux-x64-musl": "16.3.3", "@next/swc-win32-arm64-msvc": "16.3.3", "@next/swc-win32-x64-msvc": "16.3.3", "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-tuRTx1nQ/yVw83cwJBo9F+njGUgMn3UHQycreWHB8XsStvvAh1AthbI8/4IpKnFaF58F+iSiHejYOlMQ/eq83g=="], "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.95.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-T9iGctuocf0qIWFFOTxPzjT5q0SILqaBYXt272tlBHvTKC5+3JnkMirLxNJNkXHtFyBjU2Jx+NL4Zipr0B/c6Q=="], "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], @@ -1254,6 +1266,8 @@ "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + "parse-bmfont-ascii": ["parse-bmfont-ascii@1.0.6", "", {}, "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA=="], "parse-bmfont-binary": ["parse-bmfont-binary@1.0.6", "", {}, "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA=="], @@ -1352,7 +1366,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=="], @@ -1398,6 +1412,8 @@ "resolve": ["resolve@2.0.0-next.7", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.2", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ=="], + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], @@ -1502,7 +1518,7 @@ "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], - "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], "stripe": ["stripe@16.12.0", "", { "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" } }, "sha512-H7eFVLDxeTNNSn4JTRfL2//LzCbDrMSZ+2q1c7CanVWgK2qIW5TwS+0V7N9KcKZZNpYh/uCqK0PyZh/2UsaAtQ=="], @@ -1510,11 +1526,13 @@ "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "@babel/core": "*", "babel-plugin-macros": "*", "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" }, "optionalPeers": ["@babel/core", "babel-plugin-macros"] }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], "supports-terminal-graphics": ["supports-terminal-graphics@0.1.0", "", {}, "sha512-+KdfozhS0Fw8y5Sghw8kkZNGT8nWYzJ1EzcoIvVjxhl+26TJTs26y02yfBgvc1jh5AS/c8jcI3xtahhR95KRyQ=="], - "systeminformation": ["systeminformation@5.33.6", "", { "os": "!aix", "bin": { "systeminformation": "lib/cli.js" } }, "sha512-hMOQG/eRUzuopuYGGdl8ntkau0nEC7fOaRoTUg1RSr2GTQIk2VNa76DA0+ApajkGfzmcgAupgIP/vt+jtoe5EA=="], + "systeminformation": ["systeminformation@5.33.5", "", { "os": "!aix", "bin": { "systeminformation": "lib/cli.js" } }, "sha512-0v8l1CwFOAjfkv6ynpMrv3YGjH0M7PWCpZwusr8J1TEoQFPK7WXO6gbeAiandaWoh7vbMdnFtDqVotJVnLJtIg=="], "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 +1560,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 +1666,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 +1680,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.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-aWO7iwhFUGf347tCwNGggggfmZigaSu7TF739IZSrWWABUp7zkb4Cr3fMqvBe5EIS7ABJJu3Cadn0g/zs1G0QQ=="], - "@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.32", "", { "dependencies": { "@ai-sdk/provider": "4.0.8", "@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-MZUhlINn6FzKIWuX3T36h+yM9d7bG+yatH+kC99ZCe0DHxXfP73KwaoLiLcZDPQDamFyO3umPPBLJieZJyG4DQ=="], "@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], @@ -1680,7 +1698,11 @@ "@codebuff/sdk/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], - "@eslint/config-array/minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], + "@eslint/eslintrc/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], + + "@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "@eslint/eslintrc/js-yaml": ["js-yaml@4.3.2", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA=="], "@opentui/core/diff": ["diff@9.0.0", "", {}, "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw=="], @@ -1688,8 +1710,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 +1724,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.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-aWO7iwhFUGf347tCwNGggggfmZigaSu7TF739IZSrWWABUp7zkb4Cr3fMqvBe5EIS7ABJJu3Cadn0g/zs1G0QQ=="], - "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.32", "", { "dependencies": { "@ai-sdk/provider": "4.0.8", "@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-MZUhlINn6FzKIWuX3T36h+yM9d7bG+yatH+kC99ZCe0DHxXfP73KwaoLiLcZDPQDamFyO3umPPBLJieZJyG4DQ=="], "bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], @@ -1724,11 +1744,11 @@ "eslint/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], - "eslint/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "eslint/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "eslint/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], - "eslint/minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], + "eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], @@ -1740,7 +1760,7 @@ "eslint-plugin-import/tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="], - "espree/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "espree/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], "execa/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], @@ -1770,12 +1790,16 @@ "plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], + "rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + "react-devtools-core/ws": ["ws@7.5.13", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA=="], "react-reconciler/scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], "send/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + "slice-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], "ts-node/diff": ["diff@4.0.4", "", {}, "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ=="], @@ -1790,6 +1814,8 @@ "typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@7.18.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw=="], + "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "wrap-ansi/string-width": ["string-width@8.2.2", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg=="], "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -1798,7 +1824,9 @@ "@codebuff/evals/pino/process-warning": ["process-warning@5.1.0", "", {}, "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw=="], - "@eslint/config-array/minimatch/brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="], + "@eslint/eslintrc/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "@eslint/eslintrc/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], @@ -1812,14 +1840,10 @@ "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "eslint-plugin-import/tsconfig-paths/json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], "eslint/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - "eslint/minimatch/brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="], - "express/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], "p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], @@ -1848,12 +1872,8 @@ "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "@eslint/config-array/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.1.4", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg=="], - "eslint/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@7.18.0", "", {}, "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ=="], "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^1.3.0" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA=="], 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..b41ff956e7 100644 --- a/cli/src/chat.tsx +++ b/cli/src/chat.tsx @@ -14,7 +14,14 @@ import { useShallow } from 'zustand/react/shallow' import { getAdsEnabled } from './commands/ads' import { routeUserPrompt, addBashMessageToHistory } from './commands/router' +import { MissionTodosTracker } from './components/mission-todos-tracker' import { SingleAdBanner } from './components/ad-banner' +import { + buildMissionContinuation, + loadMission, + refreshMissionCompletion, +} from './missions/mission-store' +import { getMissionAutopilotAction } from './missions/mission-autopilot' import { ChatInputBar } from './components/chat-input-bar' import { ChatHeader } from './components/chat-header' import { FreebuffActiveSessionSummary } from './components/freebuff-active-session-summary' @@ -52,7 +59,7 @@ import { usePublishMutation } from './hooks/use-publish-mutation' import { useSuggestionEngine } from './hooks/use-suggestion-engine' import { useUsageMonitor } from './hooks/use-usage-monitor' import { WEBSITE_URL } from './login/constants' -import { getProjectRoot } from './project-files' +import { getMissionScopeId, getProjectRoot } from './project-files' import { useChatHistoryStore } from './state/chat-history-store' import { useChatStore } from './state/chat-store' import { useQueuePanelStore } from './state/queue-panel-store' @@ -535,7 +542,6 @@ export const Chat = ({ logoutMutation, streamMessageIdRef, addToQueue, - hasQueuedMessages: () => queuedCount > 0, clearMessages, saveToHistory, scrollToLatest, @@ -571,6 +577,13 @@ export const Chat = ({ }, ) + const missionAutopilotPendingRef = useRef(false) + let missionAutopilotActive = false + try { + missionAutopilotActive = + loadMission(getProjectRoot(), getMissionScopeId())?.status === 'active' + } catch {} + // Retire onboarding suggested prompts once the user submits anything // (typed or clicked), persisting so they don't return on future launches. useEffect(() => { @@ -1487,6 +1500,52 @@ export const Chat = ({ IS_FREEBUFF && freebuffSession?.status === 'active' const isFreebuffSessionOver = IS_FREEBUFF && freebuffSession?.status === 'ended' + + // A mission is a persistent worker, not a single assistant turn. Whenever + // its chat becomes idle, re-check the authoritative plan and start the next + // turn. The per-chat mission file prevents parallel terminals from sharing + // or completing each other's work. + useEffect(() => { + let root: string + let scopeId: string + try { + root = getProjectRoot() + scopeId = getMissionScopeId() + } catch { + return + } + const mission = refreshMissionCompletion(root, scopeId) + const action = getMissionAutopilotAction({ + active: mission?.status === 'active', + idle: + !isStreaming && + !isWaitingForResponse && + !isChainInProgressRef.current && + !askUserState && + !reviewMode, + sessionOver: isFreebuffSessionOver, + }) + if (action !== 'continue' || !mission || missionAutopilotPendingRef.current) return + + missionAutopilotPendingRef.current = true + const timer = setTimeout(() => { + const current = refreshMissionCompletion(root, scopeId) + if (!current || current.status !== 'active') { + missionAutopilotPendingRef.current = false + return + } + onSubmitPrompt(buildMissionContinuation(root, current, scopeId), agentMode) + .catch((error) => logger.error({ error }, '[mission-autopilot] Failed to continue mission')) + .finally(() => { + missionAutopilotPendingRef.current = false + }) + }, 1500) + return () => { + clearTimeout(timer) + missionAutopilotPendingRef.current = false + } + }, [messages.length, isStreaming, isWaitingForResponse, isFreebuffSessionOver, askUserState, reviewMode, agentMode, onSubmitPrompt]) + const shouldShowStatusLine = !feedbackMode && (hasStatusIndicatorContent || @@ -1629,6 +1688,8 @@ export const Chat = ({ /> )} + + {reviewMode ? ( // Review and ask_user take precedence over the session-ended banner: // during the grace window the agent may still be asking to run tools @@ -1650,9 +1711,10 @@ export const Chat = ({ width={separatorWidth} maxVisibleRows={isCompactHeight ? 4 : 8} /> - ) : isFreebuffSessionOver && !askUserState ? ( + ) : isFreebuffSessionOver && !askUserState && !isStreaming && !isWaitingForResponse ? ( ) : ( <> 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/mission.ts b/cli/src/commands/mission.ts new file mode 100644 index 0000000000..fb02806521 --- /dev/null +++ b/cli/src/commands/mission.ts @@ -0,0 +1,74 @@ +import { getMissionScopeId, getProjectRoot } from '../project-files' +import { + buildMissionPrompt, + cancelMission, + completeMission, + createMission, + formatMissionStatus, + loadMission, +} from '../missions/mission-store' + +function root(): string { + return getProjectRoot() || process.cwd() +} + +function scope(): string | undefined { + try { + return getMissionScopeId() + } catch { + return undefined + } +} + +export type MissionCommandResult = + | { kind: 'message'; message: string } + | { kind: 'start'; prompt: string } + +export function runMissionCommand(args: string): MissionCommandResult { + const trimmed = args.trim() + let [verb, ...rest] = trimmed ? trimmed.split(/\s+/) : ['status'] + + if (verb.endsWith(',')) { + verb = verb.slice(0, -1) + } + + const value = rest.join(' ').trim() + + if (verb === 'status') { + return { kind: 'message', message: formatMissionStatus(loadMission(root(), scope())) } + } + if (verb === 'start') { + if (!value) { + return { kind: 'message', message: 'Usage: /mission start ' } + } + const mission = createMission(root(), value, scope()) + return { kind: 'start', prompt: buildMissionPrompt(root(), mission, scope()) } + } + if (verb === 'complete') { + const evidence = value ? value.split('|').map((item) => item.trim()) : [] + try { + const mission = completeMission(root(), evidence, scope()) + return { kind: 'message', message: formatMissionStatus(mission) } + } catch (error) { + return { + kind: 'message', + message: error instanceof Error ? error.message : String(error), + } + } + } + if (verb === 'cancel') { + try { + const mission = cancelMission(root(), scope()) + return { kind: 'message', message: formatMissionStatus(mission) } + } catch (error) { + return { + kind: 'message', + message: error instanceof Error ? error.message : String(error), + } + } + } + return { + kind: 'message', + message: 'Uso: /mission [status|start |complete [evidência]|cancel]', + } +} 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..e6a93eb19f 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, @@ -14,6 +13,10 @@ import { } from './router-utils' import { buildInterviewPrompt, buildPlanPrompt, buildReviewPrompt } from './prompt-builders' import { getProjectRoot } from '../project-files' +import { + buildMissionContinuation, + loadMission, +} from '../missions/mission-store' import { useChatStore } from '../state/chat-store' import { useFreebuffSessionStore } from '../state/freebuff-session-store' import { trackEvent } from '../utils/analytics' @@ -26,8 +29,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 +262,6 @@ export async function routeUserPrompt( isStreaming, streamMessageIdRef, addToQueue, - hasQueuedMessages, saveToHistory, scrollToLatest, sendMessage, @@ -276,10 +276,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 +347,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 +434,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) @@ -523,7 +461,13 @@ export async function routeUserPrompt( return } - sendMessage({ content: trimmed, agentMode }) + const projectRoot = getProjectRoot() || process.cwd() + const mission = loadMission(projectRoot) + const content = + mission?.status === 'active' + ? trimmed + buildMissionContinuation(projectRoot, mission) + : trimmed + sendMessage({ content, agentMode }) setTimeout(() => { scrollToLatest() diff --git a/cli/src/components/__tests__/freebuff-model-selector.test.tsx b/cli/src/components/__tests__/freebuff-model-selector.test.tsx index c4d0a60cf4..e824311286 100644 --- a/cli/src/components/__tests__/freebuff-model-selector.test.tsx +++ b/cli/src/components/__tests__/freebuff-model-selector.test.tsx @@ -11,7 +11,6 @@ import { FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID, FREEBUFF_MIMO_V25_MODEL_ID, FREEBUFF_GLM_V53_FLASH_MODEL_ID, - FREEBUFF_SOLAR_PRO_4_MODEL_ID, FREEBUFF_FABLE_5_MODEL_ID, FREEBUFF_GLM_V52_MODEL_ID, FREEBUFF_GPT_5_6_LUNA_MODEL_ID, @@ -165,21 +164,19 @@ describe('FreebuffModelSelector tier layout', () => { // 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. + // GLM 5.3 Flash as the only row that is both. Flash filled this slot until + // 2026-08-24, when it left FREEBUFF_PREMIUM_MODEL_IDS and moved down into + // UNLIMITED -- below the header this asserts it sits above; V4 Pro until + // its withdrawal on 2026-08-26. useFreebuffModelStore .getState() - .setSelectedModel(FREEBUFF_SOLAR_PRO_4_MODEL_ID) + .setSelectedModel(FREEBUFF_GLM_V53_FLASH_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 selectedModelIndex = frame.indexOf('GLM 5.3 Flash') const unlimitedHeaderIndex = frame.indexOf('UNLIMITED') expect(premiumHeaderIndex).toBeGreaterThanOrEqual(0) @@ -189,7 +186,7 @@ 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).toContain('› GLM 5.3 Flash') expect(frame).not.toContain('› GPT-5.6 Luna') }) @@ -303,25 +300,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 +326,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 +367,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 () => { @@ -473,11 +459,12 @@ describe('FreebuffModelSelector tier layout', () => { // // 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. + // earlier row. With Flash in the section that was 2-1 for the shared + // premium pool, so Luna's one-a-day ceiling wore the chip. Flash left that + // pool on 2026-08-24, leaving Luna and V4 Pro tied 1-1, so the header now + // speaks for Luna and it is V4 PRO that carries its own count. The + // invariant under test — a second line the width and height math must + // both know about — is unchanged; only the row it lands on moved. const resetAt = new Date(FIXED_NOW_MS + 60_000).toISOString() const pool = ( model: string, @@ -506,16 +493,10 @@ describe('FreebuffModelSelector tier layout', () => { '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_GLM_V53_FLASH_MODEL_ID]: pool( + FREEBUFF_GLM_V53_FLASH_MODEL_ID, + 'glm_v53_flash', + 'GLM 5.3 Flash', 2, ), }, @@ -524,10 +505,9 @@ describe('FreebuffModelSelector tier layout', () => { .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. + // collapses the list to a single row and the chip disappears. 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) const frame = (await renderSelector()).captureCharFrame() @@ -543,11 +523,12 @@ describe('FreebuffModelSelector tier layout', () => { ] } 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')) + // GLM 5.3 Flash's second line carries its per-model ceiling 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. Deliberately the + // PER-MODEL pool rather than the shared one: its label is the longest the + // caps table can produce, which is the case the width math has to survive. + const chipLine = lines.find((l) => l.includes('GLM 5.3 Flash: 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( @@ -609,26 +590,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 +706,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/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-model-selector.tsx b/cli/src/components/freebuff-model-selector.tsx index db1bb9ee74..b17a78b187 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,7 +621,7 @@ 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 @@ -758,7 +695,6 @@ const testSuffixLen = ' · TEST'.length availableModels, offerModels, contentMaxWidth, - reasoningSuffixFor, rowDetailsText, supersededNoticeFor, ]) @@ -791,15 +727,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 +739,6 @@ const testSuffixLen = ' · TEST'.length canCollapse, showStandaloneRecommended, supersededNoticeFor, - planSummary, ]) // When a referral exists, start at the parent's full allowance until the @@ -997,7 +923,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 (