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 (