# WindsurfAPI 记账本 v2 · 精细账 — 2026-04-09 ~ 04-22(78 commits) > 数据源:`git log` 逐条核验(78 条 hash 全部 `git cat-file`/`git log --no-walk` 验证存在); > 每条账目基于 `git show --stat` + commit body + 关键 commit 的 diff 级深挖(深挖 ≥15 条,问题链 9 条)。 > 关联 issue/PR 编号沿用 v1 采集报告(#1-#26 中本片内出现 #2/#3/#5/#8/#9/#12/#15/#18、PR #1/PR #13)。 ## 一、逐条 commit 账(按时间序) | hash | date | 聚类 | 干了什么(含关键 diff 细节) | 关联 issue/PR | |---|---|---|---|---| | 395311f | 2026-04-09 18:41 | 项目奠基 | 首个 commit:16 文件 2243 行。59 模型目录(kimi-k2 从第一天就是 `enumValue:0 + modelUid:'MODEL_KIMI_K2'` 的错误形态,埋下 12 天后才爆的雷);多账号轮询池 + accounts.json 持久化;SSE 流式 + 非流式双路径;原生 `node:http` 自写路由(server.js 202 行),package.json 无 dependencies 字段——零 npm 运行时依赖定调;`src/proto.js` 手写 schema-less wire codec(varint/fixed64/len-delim),`src/grpc.js` 手写 gRPC 帧层;认证走本地 language_server 二进制(langserver.js spawn);`buildCascadeConfig` 从这天起 `planner_mode=1(DEFAULT)`(沿用参考实现),工具循环的种子 | — | | 547bf9d | 2026-04-09 19:29 | Dashboard | 零依赖 SPA 后台 /dashboard:6 面板(overview/账号 CRUD/代理配置/SSE 实时日志/按模型+账号的请求统计/封禁检测),DASHBOARD_PASSWORD 可选保护;新增 src/dashboard/api.js 220 行、logger.js 59 行、proxy-config.js 76 行、stats.js 105 行 | — | | e5f38e4 | 2026-04-09 19:35 | 文档/站点 | 默认端口 3001→3003(src/config.js 一行);README 全量 API 文档 + 部署指南;docs/index.html 深色主题 GitHub Pages 介绍站 | — | | ad6242e | 2026-04-09 19:43 | 文档/中文化 | README 217 行 + GitHub Pages 437 行全面改繁体中文,补部署教学/模型说明/环境变量/重启流程 | — | | c680126 | 2026-04-09 20:30 | Dashboard/安全 | Dashboard 认证 API_KEY 回退:未设 DASHBOARD_PASSWORD 时自动用 API_KEY 保护后台,堵住「设了密钥但后台裸奔」的口子(auth.js 8 行) | — | | ef21ff7 | 2026-04-09 20:57 | 账号健康 | 排除 gRPC 连接错误对账号错误计数的影响:ECONNREFUSED/ECONNRESET/invalid_argument/unmarshal 等连接层错误不再计入账号错误数,避免 LS 重启时账号被误停用(chat.js 5 行)——早期就有的「隔离传输错误与账号错误」意识 | — | | 3a45f56 | 2026-04-10 14:20 | LS 池架构 | 语言服务器池架构:每个代理(含无代理 default)各起一个独立 LS 实例,spawn 带 HTTPS_PROXY + 独立 data 目录——根治「单 LS 切账号时 workspace 初始化流被 upstream cancel 误判 expired」与「per-account 代理对聊天无效」;probeAccount() canary 聊天推断订阅层级(pro/free/expired/unknown)写 accounts.json,每 6h 重探;chat.js 多账号重试排除 model_not_available 账号、rate_limit 冷却 1h;index.js 平滑关闭延长到 30s 保流式;langserver.js 重写 303 行 | — | | 2a4d599 | 2026-04-10 17:26 | 延迟优化 | Cascade 工作区 init 由 per-request 改 per-LS 一次性预热(省 InitializePanelState+AddTrackedWorkspace+UpdateWorkspaceTrust 三个 gRPC 往返,300-900ms/请求);新 src/cache.js(SHA256 key + LRU + TTL 本地响应缓存);按订阅层级 RPM 滑动窗口限流(pro=60/free=10/unknown=20)+ 队列等待代替直接 503;SSE 15s 心跳修 120s 断流;流式终止 chunk 补 usage 修 new-api 输出 token=0;getApiKey 改层级加权选择(空闲比例高优先);dashboard 修 setInterval 泄漏(每次触发新建 timer 不清旧的,指数级累积)→ 抽 App.poll() 每 key 单 timer + tab 隐藏暂停 | — | | ff60ef7 | 2026-04-10 17:44 | 对话复用 | 实验性 cascade_id 复用(fingerprint/apiKey/lsPort 三约束命中才复用,只送最新 user 消息,后端保上下文),默认关,runtime-config.json 持久化;顺手修 cascadeChat 把 assistant 消息整组丢弃的 bug(新建 cascade 时把完整 u/a 历史以带标签 transcript 打包送出)——多轮上下文修复 | — | | 957e5e0 | 2026-04-10 18:15 | 额度/日志/品牌 | windsurf-api.js 手写 Connect-RPC JSON 客户端打 server.codeium.com/GetUserStatus(走代理隧道,兼容新旧两种配额契约);auth.js refreshCredits/refreshAllCredits,启动即拉 + 每 15 分钟刷新;dashboard 账号表「余额」列(plan+进度条+悬浮详情);logger.js 结构化日志 JSONL 按天轮转(app-*.jsonl / error-*.jsonl);bydwgx1337 品牌化(启动 banner、/health provider、dashboard 标题) | — | | 05e8519 | 2026-04-12 06:31 | Cascade 协议对抗 | **深挖**:buildCascadeConfig 的 `planner_mode` 从沿用参考实现的 1(DEFAULT) 改 3(NO_TOOL)——DEFAULT 让 Cascade IDE-agent 循环保持 hot,即使 CascadeToolConfig 未设也会反射性 edit_file `/tmp/windsurf-workspace/...`,产出 stall_warm 假成功、「file already exists」冲突、路径泄露三症状;改后压测 15/15 成功(之前 13/15)、壁钟 99s→35s、零路径泄露零冲突。client.js 连带:cascadeChat 的 stall 检测从「只看 responseText 增长」扩到「文本/thinking/新 step/新 tool call 任一增长」,timeout 15s→25s,textLen<300 的 stall 抛 retryable 错误让 chat.js 换账号;另加 panel-state-not-found 时 warmupCascade(force) 重跑(新 sessionId)+ StartCascade/SendMessage 各重试一次的链路 | — | | 17b8c27 | 2026-04-12 06:44 | 用量/工具仿真 | **深挖**:真实 token usage 替换 chars/4 估算——windsurf.js 新增 buildGetGeneratorMetadataRequest/parseGeneratorMetadata 解析 GetCascadeTrajectoryGeneratorMetadata RPC 的 CortexStepGeneratorMetadata.chat_model.usage(field 4);client.js 在 poll 结束后单独发一次该 RPC 聚合 usage,网络失败降级 per-step metadata,再降级 chars/4(三级降级链);chat.js 抽 buildUsageBody() 统一流式/非流式;实测 opus "Count to 5" prompt=2302 vs 原估 ~12(误差 190 倍)。新 src/sanitize.js(出站剥 /tmp/windsurf-workspace 路径,纵深防御)+ src/handlers/tool-emulation.js(OpenAI tools[] → `{...}` 文本协议,Cascade 无 per-request 工具定义槽位) | — | | 4abd3f6 | 2026-04-12 12:53 | 工具仿真/账号管控 | **深挖**:并行 tool_call 修复(提示词允许一次发多个 ``,解析器已支持,修只返回首个的问题);tool_call 响应 content 空白置 null 符合 OpenAI 规范;prompt_tokens 改 inputTokens+cacheRead+cacheWrite 总输入(修 NewAPI 只显示 3 token);模型身份注入(实验性,prepend 系统提示词让模型自称请求的模型);dashboard 账号级模型禁用/启用编辑器(模态+分组勾选);auth.js per-account blockedModels;**同时把 `import { isExperimentalEnabled }` 重复加了一次(chat.js 第 25 行,文件里 17b8c27 已有一份)——3 分钟后自己踩爆启动崩溃** | — | | 89d18d1 | 2026-04-12 12:56 | 自伤修复 | 删掉 chat.js 里重复的 `import { isExperimentalEnabled }`(1 行)——4abd3f6 引入的重复 import 导致启动崩溃,自己引入自己修 | — | | 2c993b9 | 2026-04-12 13:36 | 工具仿真 | **深挖**:tool emulation 从「注入用户消息 preamble」改为「proto 层系统 prompt 注入」——利用 CascadeConversationalPlannerConfig 的 `tool_calling_section`(field 10)+ `additional_instructions_section`(field 12)两个 SectionOverrideConfig,OVERRIDE 工具定义 + APPEND 强化指令;原因:NO_TOOL 模式的内建系统 prompt 会告诉模型「没有工具可用」,直接压过用户消息层注入(症状:输出 token 大量为 0 或极低、完全不调 ``)。chat.js 里 normalizeMessagesForCascade 改传空 tools(只做 role:tool/tool_calls 消息重写不再注入 preamble),toolPreamble 沿 cascadeChat → buildSendCascadeMessageRequest 传入 proto 层 | — | | 3e9ef78 | 2026-04-14 20:35 | 文档 | README 加中文未经授权再分发警告(2 行) | — | | 8fb9926 | 2026-04-15 18:20 | 文档 | README 小改(商用禁令措辞补 3 行) | — | | 7f339b5 | 2026-04-15 20:33 | 限流/授权 | per-model 限流:模型级 429 不再整体封账号,只对该模型冷却 5 分钟(isRateLimitedForModel 双查全局+per-model);READEME 正式商用禁止声明;tool-emulation.js 同步 +163 行 | — | | aaf757e | 2026-04-16 15:18 | 模型目录/云 API | 59→82+ 模型目录(GPT-5.4 Mini、Claude 1M context、GPT-5.2 Codex、SWE-1.6、GLM-5.1、Minimax M2.5 等,credit 倍率来自线上 GetCascadeModelConfigs);windsurf-api.js 加 getCascadeModelConfigs() 动态目录 + checkMessageRateLimit() 预检;auth.js 启动时 merge 云端模型配置;windsurf-login.js 登录流程返回 Firebase refreshToken + refreshFirebaseToken()/reRegisterWithCodeium() 免重登续期 | — | | d1078ca | 2026-04-16 15:32 | 代理/限流/token | 云 API 调用(getUserStatus/getCascadeModelConfigs/checkMessageRateLimit)在 per-account 代理 407 或 CONNECT 失败时回退直连(修过期/无认证住宅代理上额度刷新挂掉);CheckUserMessageRateLimit 作可选预检(preflightRateLimit 实验开关,默认关);Firebase token 每 50 分钟周期刷新重注册保 API key 存活;dashboard 加手动 rate-limit 检查与 refresh-token 端点 | — | | b721201 | 2026-04-17 15:28 | 模型目录 | **深挖**:ALL_MODEL_KEYS 是模块加载时快照,mergeCloudModels 后加的模型(opus-4-7 家族)进不了快照 → getTierModels('pro') 返回过期列表 → 预检 403 model_not_entitled。修法:`pro: ALL_MODEL_KEYS` 改 getter `get pro() { return Object.keys(MODELS); }` 每次调用重枚举(models.js 一行)——与 PR #1 完全同题同方案,一分钟后撞车 | PR #1(撞车) | | 18d25f3 | 2026-04-18 17:53 | 文档 | README 加 2 行(Star/Follow 条件声明补丁) | — | | 7411d4c | 2026-04-18 17:54 | 文档 | README 一行(商用声明措辞)——这是 4ff1baf merge 的第一父,即 main 分支线 | — | | 4f9a767 | 2026-04-20 20:16 | 早期 issue 波 | **深挖**:#3 Firebase 登录失败给 OAuth 指引;#5 长 prompt 超时——cold stall 检测改为随输入长度自适应(30s-90s);PR #1 内容并入(pro tier getter);README 全文重写口语化 + FAQ + setup.sh 一键初始化(54 行:建目录/设权限/生成配置);dashboard 登录面板 OAuth 提示;.gitignore 补 codeium_ext/ codeium.zip *.db bugsy/ windsurf-grpc/ | #2 #3 #5 PR #1 | | 4ff1baf | 2026-04-20 20:16 | 合并 | Merge PR #1(dd373156/fix-opus-4-7-tier-access)。**深挖**:merge 的第二父就是 b721201 本身——即 PR #1 的 head 是 dwgx 自己的 commit,同题同方案撞车的实锤,最终以 merge 形式收编 | PR #1 | | 796b0eb | 2026-04-20 20:22 | 文档/中文化 | README 全部改简体中文(152 行,繁体→简体) | — | | e8c2272 | 2026-04-20 20:28 | Claude Code 兼容 | usage 对象补 output_tokens/input_tokens 别名(修 Claude Code 读 null 崩溃)、completion_tokens_details 全量补;流式 finish_reason chunk 带 usage(部分客户端在空 choices 汇总 chunk 之前就读);dashboard 默认 token 登录方式 + 全登录类型显眼挂 show-auth-token 链接 | #2 | | ec2e49e | 2026-04-20 20:39 | OAuth | dashboard 浏览器侧 Firebase Auth SDK(CDN)+ Google/GitHub 弹窗登录:signInWithPopup → idToken → Codeium register → 自动入池;后端 /dashboard/api/oauth-login 收 idToken;免掉 OAuth 用户手动去 windsurf.com/show-auth-token 的弯路 | #3 | | c3a4c82 | 2026-04-20 20:42 | /v1/messages | 新 src/handlers/messages.js 240 行:Anthropic↔OpenAI 双向翻译,POST /v1/messages 全流式(message_start/content_block_delta/message_stop),支持 system/tools/thinking 块,复用账号池/级联/限流;Claude Code 设 ANTHROPIC_BASE_URL 即连——回应 #2 的关键 | #2 | | 90b88b6 | 2026-04-20 20:49 | /v1/messages | **深挖**:/v1/messages 流式从「整包缓冲再回放」改「ServerResponse shim 实时翻译」——OpenAI SSE 字节流边到边转 Anthropic SSE(canned 输入验证 11 事件齐全),Claude Code 首 token 从 30-90s 变即时;客户端断连正确传播到 cascade abort(原来被过早 writableEnded=true 吞掉);inputChars 自适应冷滞改 contentToString 处理数组格式 content(原来数组一律数 0,Anthropic/vision 请求永不触发自适应超时);dashboard 去掉 loadAccounts/renderLoginHistory 的 `?.()`(吞掉缺失方法) | #2 | | 9fad3ac | 2026-04-20 21:03 | Legacy 协议 | **深挖**:legacy RawGetChatMessage 的 assistant 消息编码错误——原来把内容当 raw string 写进 ChatMessage.intent(field 5,类型是 ChatMessageIntent),wire type 恰好都是 length-delimited,单轮短回复侥幸过解析,真多轮对话必炸 "invalid_argument: unmarshal ... invalid wire-format data";对照 exa.chat_pb.proto 确认 assistant 文本应放 ChatMessage.action(field 6)的 ChatMessageAction{ChatMessageActionGeneric{string text=1}}。VPS 实测多轮 gpt-4o-mini 3+3=6 恢复。影响面:所有 enumValue-only 模型(gpt-4o/4o-mini/4.1/o3/deepseek/grok/qwen/gemini-2.5-flash/glm-4.7)多轮全部静默损坏 | — | | 876c0a0 | 2026-04-20 21:24 | Dashboard/配置 | 身份 prompt 定制化:runtime-config.js 新增 DEFAULT_IDENTITY_PROMPTS + get/set/reset,chat.js 从硬编码 PROVIDER_IDENTITY 改读配置(10 家 provider);dashboard 统计分析加时间范围(6h/24h/72h)+ per-account 分解 + p50/p95 延迟(stats.js per-model 200 条环形缓冲);登录代理加「测试代理」按钮(HTTP CONNECT → TLS → GET api.ipify.org 显示 egress IP) | — | | 55d9e6d | 2026-04-20 21:34 | 日志 | chat 日志行加 turns 与 chars——诊断下游 relay 丢上下文的(log 显示 turns=1 就是 relay 丢历史);顺带记录「账号轮换本身不丢上下文:每请求在 client.js 重打包完整历史」 | — | | d02efc3 | 2026-04-20 21:44 | Legacy 协议 | **深挖**:legacy RawGetChatMessage 拒绝 role:tool 与 assistant.tool_calls(upstream proto unmarshaler 只认 user/assistant/system),复现 [user,assistant+tool_calls,tool,user] → invalid_argument。修法:buildRawGetChatMessageRequest 编码前把工具历史降级为纯文本——role=tool 转 user 包 `[tool result for ]: `,assistant.tool_calls 拼进 assistant 文本 `[called tool X with Y]`。语义保真(模型仍看到工具历史),只是叙述化。Cascade 路径不受影响(SendUserCascadeMessage 收完整工具协议)。VPS 三场景验证通过 | — | | c5d38bb | 2026-04-20 22:36 | 双语文档/token 持久化 | **深挖**:token 持久化三处洞——windsurf-login/oauth-login 在 addAccountByKey 已 saveAccounts 之后才写 refreshToken(重启即丢,自动续期静默失效);/accounts/:id/refresh-token 算了 keyChanged 却从不写回内存账号(手动轮换是 no-op 直到重启)。统一收口到新 setAccountTokens(id,{apiKey,refreshToken,idToken}) 持久化。另修 messages.js createCaptureRes.once() 别名成 on() 的假单发(实现真 once+off);README 双语重写(ASCII 架构图×2)+ README.en 英文全译 + update.sh(pm2 stop/delete/kill-port/start + health check,避开 pm2 restart 僵尸进程陷阱);dashboard i18n EN/中开关(24 元素 data-i18n) | — | | fe4ddb1 | 2026-04-20 23:32 | 修复 #8 | **深挖**:CascadeConversationalPlannerConfig 有 4 个模型字段——plan_model_deprecated(1, enum)、requested_model_deprecated(15, ModelOrAlias)、plan_model_uid(34, string)、requested_model_uid(35, string),原来只填 35。对 InitializeCascadePanelState 返回 "user status is nil" 的新账号/未探测账号,upstream 校验器读的是 deprecated 字段 → 400 "neither PlanModel nor RequestedModel specified"。修法:modelUid 存在时 34+35 双写;enum 存在时 15+1 双写(1 是 plan_model_deprecated 直接 enum);两者皆无则 throw。老账号 + 新账号双兼容 | #8 | | b7937b0 | 2026-04-20 23:39 | 修复 #5 | **深挖**:#5(alen978) 两个真 bug:① 速率限制账号不自动切换——maxAttempts 硬编码 3,>3 账号池前 3 个被限流就永远轮不到健康账号;改 `min(10, active_account_count)`,流式/非流式双路径。② Cascade planner 30s 无输出就 stall——90s 自适应上限仍不够长,上限提到 maxWait(180s),per-char 因子收紧为 30s+5s/1500 chars(原 2000)。另加 dashboard 自更新:POST /dashboard/api/self-update(git pull + 800ms 后 detach 跑 update.sh 让 HTTP 响应先 flush)、GET self-update/check(前后 commit + behind 标志)、非 git 部署(SFTP/zip)友好降级 | #5 | | d8c31be | 2026-04-20 23:53 | 身份注入/版本 | **深挖**:17/17 smoke 确认身份注入只作用于 cascadeMessages、没作用于 legacy RawGetChatMessage 用的 raw messages 数组(gpt-4o-mini 问 "who are you?" 无视注入)——messages 从 const 改 let,system turn 两个数组都 prepend;验证 gpt-4o-mini 自报 OpenAI、gemini-2.5-flash 自报 Google。启动时 server.js 从 package.json + git rev-parse 构建 VERSION_INFO(version/commit/commitMessage/commitDate/branch),GET /health 返回;package.json 1.0.0→1.3.0 对齐 dashboard;sidebar footer + Overview 展示版本 | — | | e3a1d48 | 2026-04-20 23:56 | self-update 链 | **深挖**:US VPS(SFTP 部署)实测发现 `git pull --ff-only` 因工作树脏拒绝(部署脚本改过文件没提交),且 ok=false 时前端拿到空 error(错误在 runShell rejection 里)。修法:后端先 `git status --porcelain -uno`(忽略 untracked 的 accounts.json/stats.json/runtime-config.json),脏则返回 {ok:false, dirty:true, dirtyFiles:[...], error:友好文案} 不拉取;前端弹确认框列改动文件,可选 forceReset:true 二次 POST → `git fetch` + `git reset --hard origin/` 再走正常 pull | — | | b09abf8 | 2026-04-20 23:58 | self-update 链 | **深挖**:/self-update 的 spawn 用 `require('node:child_process')` 在 type:module 下静默失败——pull 成功但重启从未触发,PM2 继续跑旧内存代码,/health 永远报旧 commit。修法:`await import('node:child_process')`(ESM 兼容)+ spawn 显式 cwd: process.cwd() + PM2_NAME 环境变量 + 成败日志 | — | | 6d734d9 | 2026-04-21 00:01 | self-update 链 | **深挖**:继续翻车——spawn('bash', ['update.sh']) detached 在 SSH 手动跑没问题、Node 进程内静默失败(疑似 env/PATH 传播),且 update.sh 硬编码 PORT=3003 与 US VPS 的 8996 不符。干脆换方案:800ms 后 process.exit(0),靠 PM2 autorestart:true 捕获退出并用 pm2 start 时捕获的命令行/env 重执行(保留 --cwd),新进程从更新后的 git HEAD 重算 VERSION_INFO。实测 /health 5 秒后报新 commit | — | | 6680906 | 2026-04-21 00:07 | 修复 #8 followup | **深挖**:motto1 报 Pro 14 天试用被标 free 硬封锁。根因链:#8 的 proto bug → probe 的 claude canary 全失败 → inferTier 只见 gpt-4o-mini/gemini-2.5-flash 工作 → 标 free → 预检 403。proto 修了但老账号 stale 标签还在。两个缓解:① refreshCredits 的 planName regex 放宽(accept trial/individual/premium/paid)下次刷新自动升;② 手动 tier 覆盖——点账号表 tier 徽章弹模态选 pro/free/unknown,setAccountTier 写 tierManual=true 防自动 probe 覆盖,PATCH /dashboard/api/accounts/:id 接 tier。两击解除老账号卡死,不用删号重加 | #8 | | 3045ddb | 2026-04-21 00:25 | 修复 #3 | OAuth 注册账号用邮箱密码登录时 Firebase 回 EMAIL_NOT_FOUND/INVALID_PASSWORD/INVALID_LOGIN_CREDENTIALS,原来只有最后一种带 OAuth 提示,前两种是繁体「信箱不存在/密碼錯誤」且无指引。修法:三种失败统一 OAuth/Token 指引、消息全改简体、isAuthFail 标记穿透 /windsurf-login 响应、失败时渲染 Google/GitHub/Token 三按钮一键切换正确流程 | #3 | | 8f1b50e | 2026-04-21 10:06 | 权威层探测 | **深挖**:GetUserStatus RPC 取代 4 次 canary 聊天推断层级——一个 RPC 拿 teams_tier/plan_name/cascade_allowed_models_config(per-model credit 倍率)/trial 结束时间/prompt+flow credit 用量。field number 对照从 LS 二进制提取的 FileDescriptorProto(scripts/ls-protos/,61 proto 文件 2900+ 类型)逐字段验证。auth.js fetchUserStatus 把快照写进 account,probeAccount 先走 RPC(层级以 RPC 为准),canary 降级为 enum-only 纯 UID 模型专用(claude 4.6 等,白名单按 enum 键),canary 清单 4→6(补 gemini 3.0 flash + claude 4.5 sonnet);models.js 加 getModelKeysByEnum() 反向查表;升级流:free→pro 在每 4h 的探测量自动拾取,清 stale not_entitled 标记;dashboard tier 格显示 plan/trial 天数子行 + 详细 tooltip | — | | b16dd8d | 2026-04-21 10:14 | /v1/messages | /v1/messages 接受 x-api-key(Anthropic 客户端默认头);messages 空数组提前 400(server.js 12 行) | #2 | | c014ce9 | 2026-04-21 10:47 | Dashboard/日志 | /logs/stream 的 EventSource 设不了 X-Dashboard-Password 头 → 改 `?pwd=` query 传密码,checkAuth 头缺失时回退 URL.searchParams.pwd;/favicon.ico 回 204 不再落进带认证的 catch-all(消浏览器控制台假 401) | — | | e06e725 | 2026-04-21 10:50 | 日志 | legacy getCreditUsage 与新 client.getUserStatus 的日志文案区分(windsurf-api.js 1 行),排查时不再混淆两条路径 | — | | 7c62147 | 2026-04-21 11:12 | 修复 #5 续 | **深挖**:alen978 跟进两条——① gpt-5.4-high 报 model_not_entitled:binary 里有 gpt-5-4-high string UID(dumped rawDesc 确认),目录补 gpt-5.4-high + gpt-5.4-none 正确 UID;② ~1MB PDF 输入时 LS 约 0.7s 崩溃(TCP ECONNRESET → "pending stream has been canceled")——LS 崩溃这边拦不住,但能识别症状:nonStreamResponse catch 里命中该错误时改回 413 + 可操作提示(分块/先摘要 PDF/减历史轮数),不再给看不懂的 502 | #5 | | ef55e7a | 2026-04-21 12:18 | 模型目录 | **深挖**:96/114 模型 smoke 只测出这一个 catalog bug——kimi-k2 返回 200 但 content:null,因为把 proto enum 名 'MODEL_KIMI_K2' 当 modelUid string 发出去。binary 里 enum=323 + string UID 'kimi-k2-instruct-0905',双字段都设上(models.js 一行:`enumValue: 323, modelUid: 'kimi-k2-instruct-0905'`) | — | | 19604de | 2026-04-21 12:19 | 模型目录 | **深挖**(一分钟返工):kimi-k2-instruct-0905 这个 string UID 在 cascade server 没注册 → 改回 enum-only legacy RawGetChatMessage 路径(`enumValue: 323` 去掉 modelUid) | — | | 40c3cb7 | 2026-04-21 12:24 | 模型目录 | **深挖**(五分钟后三修):enum-only 又撞 'failed_precondition'——server 对 kimi-k2 强制 cascade flow。最终态:`enumValue: 323 + modelUid: 'MODEL_KIMI_K2'` 双字段(复用 #8 双字段模式,enum 供 legacy、UID 供 cascade,新旧账号都通) | — | | efcb713 | 2026-04-21 16:13 | 模型别名 | Anthropic/OpenAI 官方带日期全名别名(claude-opus-4-5-20251101、gpt-4o-2024-11-20 等)走 _lookup 解析——Cursor/Claude Code/官方 SDK 发规范名不再 model not found,免自定义映射层;覆盖 claude 3.5/3.7/4/4.1/4.5 sonnet+opus 的 -latest/-0 后缀与 gpt-4o/4.1/5/5-pro 系 dated 形态(models.js +36 行) | #9 | | 5430b61 | 2026-04-21 16:21 | LS 安装 | install-ls.sh 一键装 language_server_linux_x64(拉 Exafunction/codeium 最新 release,x64+arm64,可本地路径或 --url 覆盖);langserver.js 缺 binary 时给出指向 install-ls.sh 的可读错误(原来裸 ENOENT) | #18 | | 1355e43 | 2026-04-21 17:33 | 安全审计 | **深挖**:内部审计 5 critical + 3 medium。S1 chat.js:487 ReferenceError——_msgChars 是外层作用域变量,nonStreamResponse 的 catch 里访问即 ReferenceError,超大 payload 413 路径实际从没触发过,永远裸 500;改从 messages 实时重算(兼容 string/数组 content)。S2+S4 账号管理路由在 validateApiKey 之前、泄漏全量 apiKey → 挪到 API key 门后(/auth/accounts 管理端专用,但保留 dashboard 复制所需全量 key)。S3 proto.js varint 编解码 >2^31 静默损坏(JS 位运算截断成 uint32,Date.now() 量级 request_id 被悄悄改值)→ 双路径 28 bit 后切 BigInt,Python 解码器 round-trip 验证。S5 saveAccounts 序列化补 tierManual(否则重启丢手动层级,probe 重踩)+ _saveInFlight 互斥锁防并发写交错(reportSuccess/markRateLimited/updateCapability 竞态)。M1 index.js VERSION 与 package.json 三处漂移(1.2.0/1.3.0/1.2.0)改 JSON 读同步。M2 两处预检 markRateLimited 传错参数(传 acct.id 但函数按 apiKey 查 → 静默 no-op)改传 acct.apiKey + 5 分钟。M6 parseFields 截断 fixed64/fixed32/len-delim 改 throw 不静默返垃圾。L4 流式 finish_reason chunk 不再带 usage(只终态空 choices chunk 带,防客户端双计费) | — | | cae78f9 | 2026-04-21 17:35 | 模型目录 | **深挖**:qwen-3-coder(enum 325)报 'unknown model UID MODEL_QWEN_3_CODER_480B_INSTRUCT'——LS 从 enum 名自动派生 UID 字符串但 cascade server 不认识;从 binary dump 钉正确 string uid 'qwen-3-coder-480b',顺带加 qwen-3-coder-fast(enum 327,uid 'qwen3-coder-fast') | — | | cbfcf1b | 2026-04-21 17:36 | 模型目录 | **深挖**(一分钟返工):qwen-3-coder(325)和 'qwen-3-coder-480b' 都被 cascade server 以 'unknown model UID' 拒绝——enum 在 binary 里有但后端 catalog 没注册;drop coder 保 fast(换 327) | — | | 0599798 | 2026-04-21 17:36 | 模型目录 | **深挖**(一分钟再返工):qwen-3-coder-fast 也没有路由——server 缺 327 的 routing;全撤(models.js 4 行),qwen-3-coder/fast 双双从目录移除,等 upstream 注册 | — | | f058a5e | 2026-04-21 17:50 | 卫生 | 补 LICENSE(MIT,21 行标准文本)——README 自称 MIT 但 GitHub license detection 需要真实 LICENSE 文件 | — | | 5c8ba74 | 2026-04-21 18:50 | 安全审计/合 PR | **深挖**:合 PR #13(colin1112a 15 bug 报告)其中 8 项:auth.js saveAccounts 原子写(tmp+rename,配已有 _saveInFlight);client.js rawGetChatMessage done flag 防 onEnd/onError 双回调;connect.js StreamingFrameParser 16MB 帧上限;server.js readBody 10MB(413);grpc.js HTTP/2 session 池(一端口一 session)+ settle-once guards + closeSessionForPort;langserver.js _pending map 合并并发 ensureLs + proxyKey 非 [A-Za-z0-9_] 全清 + LS exit 关 session;dashboard self-update git branch regex 校验;index.html 补 PR 漏掉的 refreshCredits/resetErrors/封禁面板/proxy 表格 onclick 的 esc(),loadLogs 改 fetch+ReadableStream+X-Dashboard-Password(?pwd= 兜底保留)。明确不采纳:getAccountList 去 apiKey(会坏 dashboard 复制)、/auth 路由开口(维持只读);chat.js/_msgChars/proto varint 三项判「d020525 已有更好实现」不重复收 | PR #13 | | 13c72a0 | 2026-04-21 19:08 | 安全审计 | **深挖**:Antigravity 审计 6 个真 bug + escJsAttr。BUG-03 代理密码泄露:getProxyConfigMasked() 返回 hasPassword 布尔替代明文,mergePassword 支持「缺字段=保留」「空串=清除」,GET /proxy 与 PUT /proxy/global 全走 masked。BUG-05 grpcUnary 多帧响应截断:stripGrpcFrame 只取第一帧 → extractGrpcFrames + Buffer.concat(空帧回退旧行为)。BUG-06 流式部分响应后失败会注入 "[Error: ...]" 文本:hadSuccess=true 时只发 finish_reason:stop + 日志,不污染已发文本。BUG-07 SIGTERM 时 saveAccounts 可能丢:提 _serializeAccounts + saveAccountsSync(),shutdown/drain 两条路径同步 flush。BUG-08 非 default port 盲分配 restart 竞争:ensureLs 循环 isPortInUse 找空闲。BUG-14 Anthropic SSE 心跳被 translator 吞:createCaptureRes 接 realRes,write() 检测 SSE 注释 `: ping` 直通。XSS:esc() 的 `'` 在 HTML 属性解码后仍能 break out of onclick JS 字符串 → 新 escJsAttr()(\' \x3c \x3e \x26 \r \n \u2028 \u2029),15 处 onclick dynamic value 全换。Playwright 冒烟:9 面板激活/0 console error/恶意 account id XSS 未触发/代理密码 GET 只回 hasPassword | PR #13 | | 2edbf38 | 2026-04-21 19:20 | 致谢 | dashboard 侧栏「关于→致谢」面板:dd373156(PR #1 修 Pro 层模型合并)+ colin1112a(PR #13 审 15 bug)双卡片(头像/PR 链接/合并日期/改动说明,CONTRIBUTORS 数组手动维护),README 双语文档加 Contributors 段 | PR #1 PR #13 | | 60af392 | 2026-04-21 19:23 | 致谢反转 | 移除 dd373156 的 credits(README ×2 + dashboard ×1)——owner 自述「PR #1 是还不熟 GitHub 流程时误开被自动 merge,非主动审核认可的贡献」 | PR #1 | | 3760e50 | 2026-04-21 19:29 | 致谢反转 | Revert "credits: 移除 dd373156"(revert f501f96)——6 分钟后恢复致谢,对外部贡献致谢的决策在 9 分钟内横跳两次 | PR #1 | | 60e6f15 | 2026-04-21 19:42 | 安全审计补漏 | **深挖**:补 PR #13 + Antigravity 遗漏 3 项。① chat.js streamResponse 的「流式重试时重新分配 parser/sanitizer 防脏状态」(PR #13 原要求、合并时漏掉):toolParser/pathStreamText/pathStreamThinking 创建移进 for 循环,attempt>0 且 !hadSuccess 时重建,防前次失败的半读 `` 片段或 hold-back path 污染下次重试流。② windsurf-login.js 删未用 authHeader 本地变量(真 Proxy-Authorization 走 headers 对象)。③ langserver.js BUG-17:getLsFor 不再悄悄回退 default LS——回退会让请求走错 egress IP,Codeium 看到来源不匹配直接会话过期 → 账号误判 expired;调用方必须先 ensureLs(proxy)(chat.js 已遵守,移除无副作用) | PR #13 | | 74e3977 | 2026-04-21 19:53 | self-update 链 | **深挖**:self-update 全面 execFile 化——删 runShell(exec→shell),改 runGit(args[]) 用 execFile('git', args) 不走 shell;6 处 git 命令全迁(status/fetch/reset/pull/rev-parse/log);branch regex 校验保留为防御层,但即便 regex 被拿掉 execFile 也把参数当数据,metacharacter 注入失效(对应 Antigravity BUG-02)。README 加 opus-4.7 升级指引:/v1/models 看不到 4.7 的最常见原因是 Exafunction/codeium 公开 release 停在 v2.12.5(2026-01),binary 本身不含 4.7,列了 macOS/Linux/Windows 桌面端自带 LS binary 位置 + install-ls.sh 本地路径升级命令 | #15 | | b86589d | 2026-04-21 20:17 | 社区卫生 | 社区可用性改造:README 软化商用声明为 Star/Follow 条件(消 MIT 与声明冲突);docs/index.html 同步 59→107 模型 + Anthropic 端点 + 部署改 install-ls.sh;SECURITY.md 加私下披露渠道(防 0day 公开曝光);.github/ issue/PR 模板 + CI(node --check 语法检查);CONTRIBUTING.md 流程与风格;package.json 补 license/repository/keywords/homepage;.gitignore 排除 CLAUDE.md/audit/*.png 等内部文档 | — | | b1a75fe | 2026-04-21 20:27 | self-update 链 | update.sh 兜底:filter-branch 重写历史后 `git pull --ff-only` 拒绝 non-fast-forward → 检测 ff-only 失败后 `reset --hard origin/master`;仅 tracked 文件本地改动会丢,.env/accounts.json/logs/runtime-config 全 gitignored 不受影响(update.sh +12 行) | — | | 44a7f31 | 2026-04-21 20:31 | 卫生 | 删模板死链:CONTRIBUTING/PR 模板引用的 CLAUDE.md 已移出仓库(会 404)、config.yml 里未启用的 Discussions 入口(3 文件,5+/8-) | — | | 88be128 | 2026-04-21 20:46 | 首页重构 | docs/index.html 全重构(1442+/393-):Claude 纸色 #FAF9F5 + 珊瑚 #CC785C accent + Windsurf 青 #00C8B4;衬线大标题(Iowan/Palatino fallback);Hero 打字机循环 4 模型 scene + Windsurf 浪动光标;OpenAI vs Anthropic 双协议 curl 并排高亮;可筛选 80+ 模型矩阵(provider/思考型/免费 tag 过滤,hover credit 倍率);SVG offset-path 架构流动图;Claude Code/Cursor/Cline/OpenAI SDK 接入 snippet grid;6 条 FAQ details;count-up/reveal/code copy | — | | a6376f8 | 2026-04-21 21:01 | 修复 #15 | **深挖**:Anduin9527 报 Claude Code 选 Opus 4.7(1M) 403 model_not_entitled。根因:Claude Code 发 Anthropic 官方名 claude-opus-4-7(无 reasoning 后缀),Windsurf 云端暴露的是 claude-opus-4-7-medium;mergeCloudModels 把 -medium 放进 MODELS 但 _lookup 没有 bare 名别名 → resolveModel miss → 403。VPS 实测佐证(/v1/models 仅 -medium;直接 POST -medium 正常路由撞 5 分钟 RPM 冷却;bare 名复现 403)。修法:ANTHROPIC_DATED 加 4 条 alias(claude-opus-4-7/4.7/4-7-latest/4.7-thinking 全解析到 -medium),等 Windsurf 吐 -high 再分流 | #15 | | e3271de | 2026-04-21 22:48 | 修复 #18 | **深挖**:Loki Wang 复现的 #18——启动只 WARN 让用户自己跑 install-ls.sh,用户无视直接发请求 → LS port not ready 崩溃来报 bug。改启动自动检测 binary,缺则自动跑 install-ls.sh:Windows 跳过(LS 只有 Linux 版);脚本不存在跳过;装失败不阻塞启动保留 WARN;LS_INSTALL_PATH env 传 binaryPath 尊重自定义路径(src/index.js +27 行)。副作用:首次启动多 10-30s 下载 | #18 | | fad32d3 | 2026-04-21 22:55 | 图片上传 | **深挖**:feat #12——Cascade SendUserCascadeMessageRequest.images(field 6)repeated ImageData{base64_data=1 string, mime_type=2 string} 编码。新 src/image.js(83 行):extractImages() 同时处理 OpenAI image_url 与 Anthropic image block;parseDataUrl() 拆 data:image/png;base64,...;fetchImageUrl() https fetch + 5MB 上限 + mime 白名单 + redirect 跟进。windsurf.js buildSendCascadeMessageRequest 加 images 参数(field 6 编码);client.js cascadeChat 从最后一条 user message 提取;messages.js Anthropic→OpenAI 保留 image blocks(原来丢弃)。Legacy RawGetChatMessage 无 images 字段只走 Cascade。VPS probe:LS 吃 field 6 不 crash、日志确认双格式触发 | #12 | | 1cf98ce | 2026-04-21 23:03 | 图片编码实验 | ImageData.base64_data 改用 raw bytes 编码试解 upstream internal error(windsurf.js 3+/2-) | #12 | | 740ad6d | 2026-04-21 23:04 | 图片编码回退 | **深挖**(一分钟回退):raw bytes 触发 LS "string field contains invalid UTF-8"——base64 string 才是 proto 正确编码(field 1 是 string 类型);结论:上游 internal error 是 Windsurf 云端尚未实现 images 处理,代码保留等 LS 更新后自动生效 | #12 | | 1f98fff | 2026-04-21 23:19 | 图片换方向 | **深挖**:换思路——images 存在时 buildCascadeConfig 的 planner_mode 从 NO_TOOL(3) 切回 DEFAULT(1)(forceDefault: !!images?.length),因为 NO_TOOL 会禁用 Windsurf 后端 vision pipeline(windsurf.js 8 行:`writeVarintField(4, forceDefault ? 1 : 3)`) | #12 | | ee3c413 | 2026-04-21 23:37 | 图片安全 | **深挖**:image.js 安全加固 7 项——CRIT-1 fetchImageUrl 私有地址过滤(127.x/10.x/172.16-31.x/192.168.x/169.254.x/localhost/::1)+ redirect 目标二次 validateImageUrl 防跳 IMDS;CRIT-2 重定向深度 MAX_REDIRECTS=3 防栈溢出;HIGH-1 reject/resolve 竞态 settled 守卫防 size 超限 double resolve;HIGH-2 base64 路径 MAX_BASE64_LEN 防超大 data URL DoS;HIGH-3 MODELS 静态定义 claude-opus-4-7-medium 防 mergeCloudModels 未跑时 500;MED-1 URL 协议严格 https/http 拒 file://;MED-2 parseDataUrl 先 strip 空白再匹配(兼容换行大 base64)(image.js 54 行 + models.js 1 行) | #12 | | 98b308c | 2026-04-22 00:13 | 修复 #9 | **深挖**:Cursor 客户端白名单拦截含 claude 的模型名(请求根本到不了后端)→ 加不含品牌词前缀别名:opus-4.6→claude-opus-4.6、sonnet-4.6→claude-sonnet-4.6、opus-4.7→claude-opus-4-7-medium、ws-opus/ws-sonnet(最安全零品牌词),CURSOR_ALIASES 全表(models.js +32 行) | #9 | | f9678ae | 2026-04-22 00:29 | 安全审计 round 3 | **深挖**:审计 round 3 十项(9 文件 58+/16-)。P1:langserver.js execSync("mkdir -p") → mkdirSync + catch log.warn(消 shell 拼接);dashboard self-update 面板 4 处 commit hash 加 esc()(hash 里可藏 HTML);.env.example 加 DASHBOARD_PASSWORD 变量 + 警告(空密码+空 API_KEY = 后台完全裸奔)。P2:proto.js decodeVarint 截断改 throw(不再静默返回垃圾值,配合 round 1 的 BigInt);dashboard /test-proxy 加 SSRF 私有地址过滤(PRIVATE_PROXY_HOST regex 拒 127.x/10.x/172.16-31/192.168/169.254/0.x/localhost/0.0.0.0)。P3:auth.js Firebase token refresh timer 无条件启动(原来仅启动时有 refreshToken 快照才起,新加的账号永不续期);grpc.js pendingBuf 加 100MB 上限防 OOM;README 双语 Cursor 别名用法表 | — | ## 二、问题链清单(完整生命周期) ### 问题 1:Cascade 内建工具循环(planner_mode=DEFAULT 引发的三症状) - 引入:395311f(2026-04-09 18:41)`buildCascadeConfig` 从首个 commit 就沿用参考实现的 `planner_mode=1(DEFAULT)`,让 Cascade 的 IDE-agent 循环保持 hot——即使 CascadeToolConfig 未设,也会反射性 `edit_file /tmp/windsurf-workspace/...` - 暴露:04-10~12 压测连续不稳(压测 13/15 失败)、`stall_warm` 假成功、「file already exists」冲突、`/tmp/windsurf-workspace` 路径泄露三症状齐现 - 修复:05e8519(2026-04-12 06:31)`planner_mode` 改 NO_TOOL(3)。压测 15/15 成功、壁钟 99s→35s、零路径泄露零冲突;stall 检测扩 4 信号(文本/thinking/新 step/新 tool call)+ timeout 15s→25s + textLen<300 抛 retryable 换账号 - 返工/复发:2c993b9(04-12 13:36)发现 NO_TOOL 的内建系统 prompt 会告诉模型「没有工具可用」,压过用户消息层注入 → 输出 token 大量为 0 → 改用 proto 层 field 10/12 SectionOverrideConfig 系统 prompt 级注入工具定义。**复发**:1f98fff(04-21 23:19)发现 NO_TOOL 会禁用 vision pipeline——图片请求时条件性切回 DEFAULT(1),形成「无图 NO_TOOL / 有图 DEFAULT」双模式 - 现状:**已根治**(NO_TOOL 为默认,图片场景显式切 DEFAULT 是有文档的例外,非回归) ### 问题 2:kimi-k2 模型目录(12 天潜伏 + 6 分钟三连改) - 引入:395311f(2026-04-09 18:41)59 模型目录里 kimi-k2 就是 `enumValue: 0, modelUid: 'MODEL_KIMI_K2'`——把 proto enum 名当 string UID 用;aaf757e(04-16 15:18)扩到 82 模型时原样保留未修正 - 暴露:ef55e7a(2026-04-21 12:18)96/114 模型 smoke 测试只抓到这一个 catalog bug:kimi-k2 返回 200 但 content:null - 修复 1:ef55e7a(12:18)改 `enumValue: 323 + modelUid: 'kimi-k2-instruct-0905'`(binary dump 确认的正确 string UID) - 返工:19604de(12:19,1 分钟后)string UID 在 cascade server 未注册 → 回退 enum-only(323) legacy 路径 - 返工:40c3cb7(12:24,5 分钟后)enum-only 又撞 failed_precondition(server 强制 cascade flow)→ 最终 `enumValue: 323 + modelUid: 'MODEL_KIMI_K2'` 双字段(enum 走 legacy、UID 走 cascade,复用 #8 双字段模式) - 现状:**已根治**。注脚:96/114 smoke 只测出 1 个 catalog bug,说明其余 95 个模型白测了 12 天 ### 问题 3:qwen-3-coder 模型上架(13 分钟三次提交全撤) - 引入:cae78f9(2026-04-21 17:35)加 qwen-3-coder(enum 325)+ 钉 string uid 'qwen-3-coder-480b',顺带加 qwen-3-coder-fast(enum 327)——上游 catalog 未核实就上架 - 暴露:同一 commit 内 'unknown model UID MODEL_QWEN_3_CODER_480B_INSTRUCT'——LS 从 enum 名自动派生 UID 字符串,但 cascade server 不认识 - 返工:cbfcf1b(17:36,1 分钟后)'qwen-3-coder-480b' 也被拒(enum 在 binary 里有但后端 catalog 没注册)→ drop coder 保 fast - 返工:0599798(17:36,再 1 分钟)fast 也没有路由 → 全撤,两个模型双双移出目录 - 现状:**已撤除**(遗留项:等 Windsurf 上游注册 325/327 后再上架;教训:模型目录变更需先对线上 catalog 验证,与 kimi-k2 同为「没核实上游就动手」) ### 问题 4:图片上传编码(raw bytes ↔ base64 一分钟回退 + 换方向) - 引入:fad32d3(2026-04-21 22:55)#12 图片上传——base64 string 编码(field 1 是 string 类型),VPS 验证 LS 吃 field 6 不 crash - 暴露:上游返回 internal error(Windsurf 云端未实现 images 处理) - 修复尝试 1:1cf98ce(23:03)ImageData.base64_data 改 raw bytes 编码 - 回退:740ad6d(23:04,1 分钟后)raw bytes 触发 LS "string field contains invalid UTF-8"——base64 才是 proto 正确编码,internal error 实为云端未实现;commit 消息明说「代码保留等 Windsurf 更新 LS 后自动生效」 - 换方向:1f98fff(23:19,15 分钟后)planner_mode 有图时切 DEFAULT(1)——NO_TOOL 禁用 vision pipeline - 安全收尾:ee3c413(23:37)image.js SSRF 过滤/重定向深度/size guard/opus-4-7 静态定义 7 项 - 现状:**遗留**(上游未实现 images;本地编码路径已就绪并加固,双模式 planner 已落地,等 Windsurf 云端开启) ### 问题 5:PR #1 撞车 + credits 致谢反转(owner 对 GitHub 流程不熟的连锁) - 引入:b721201(2026-04-17 15:28 +0800)修复 pro tier 快照问题——`pro: ALL_MODEL_KEYS` 改 getter(mergeCloudModels 后加模型 403 的根因修复) - 暴露/撞车:PR #1(dd373156,04-17 16:29 +0900 创建,即 b721201 一分钟后)同题同方案——merge 4ff1baf(04-20 20:16)的第二父就是 b721201 本身:PR head 就是 dwgx 自己的 commit,「同题同方案」实为同一 commit 走了一遍 PR 流程 - 致谢:2edbf38(04-21 19:20)致谢面板 + README Contributors 段(dd373156 PR #1 + colin1112a PR #13) - 反转:60af392(04-21 19:23)移除 dd373156 credits——owner 自述「PR #1 是不熟 GitHub 流程时误开被自动 merge,非主动认可的贡献」 - 再反转:3760e50(04-21 19:29,6 分钟后)Revert 恢复致谢 - 现状:**遗留**(对外部贡献致谢的决策 9 分钟内横跳两次后停在「恢复致谢」;PR #1 的贡献性质仍是悬案,不排除后续再动) ### 问题 6:重复 import 启动崩溃(自伤,3 分钟闭环) - 引入:4abd3f6(2026-04-12 12:53)在 chat.js 又加了一次 `import { isExperimentalEnabled }`(17b8c27 06:44 已有一份)——大 commit 里混入重复 import - 暴露:启动直接崩溃 - 修复:89d18d1(2026-04-12 12:56,3 分钟后)删掉重复的那一行(1 行删除),commit 消息自认「修复 isExperimentalEnabled 重复 import 导致启动崩溃」 - 现状:**已根治**(本片最小自伤事件,自己引入自己修,未波及线上——同日上午的 05e8519/17b8c27 均在更早) ### 问题 7:proto.js varint >2^31 静默截断(request_id 被悄悄改值) - 引入:395311f(2026-04-09 18:41)手写 schema-less wire codec,varint 编解码用 JS 位运算(截断到 uint32) - 暴露:1355e43(2026-04-21 17:33)内部审计 S3 发现 Date.now() 量级 request_id 被静默截断成垃圾值 - 修复:1355e43——encode + decode 双路径 28 bit 后切 BigInt,Python 解码器 round-trip 验证正确 - 加固:f9678ae(2026-04-22 00:29)decodeVarint 截断从「静默返回垃圾值」改 throw——同族风险彻底封死 - 现状:**已根治**(BigInt 双路径 + 截断即抛) ### 问题 8:#8 模型字段缺失 → Pro trial 误标 free(两波修复 + 老数据救援) - 引入:395311f(04-09)起 buildCascadeConfig 只填 requested_model_uid(field 35);后续演进中「只有 field 35」对 fresh/无 tier 账号不成立 - 暴露:#8(2026-04-20,motto1)新账号 400 "neither PlanModel nor RequestedModel specified" - 修复 1:fe4ddb1(04-20 23:32)四字段全填——35+34(uid)+ 15+1(enum deprecated),双写覆盖新旧校验器 - 复发/余波:#8 followup(6680906,04-21 00:07)trial 误标——proto bug 时 canary 全失败 → inferTier 只见 free 模型工作 → 标 free → 预检 403 硬封锁;proto 修了但 accounts.json 里 stale 标签还在 - 修复 2:6680906 双缓解——planName regex 放宽(trial/individual/premium/paid 自动升)+ 手动 tier 覆盖(PATCH accounts/:id + tierManual=true 防 probe 踩掉),两击解卡不用删号 - 现状:**已根治**(新账号四字段不再 400;老账号有手动/自动两条升级路径;tierManual 持久化由 1355e43 S5 补上防重启丢失) ### 问题 9:legacy RawGetChatMessage 协议编码缺陷(两处独立炸点) - 引入:395311f(04-09)assistant 内容错误写进 ChatMessage.intent(field 5,类型 ChatMessageIntent)——wire type 巧合相同,单轮短回复侥幸过解析 - 暴露:9fad3ac(2026-04-20 21:03)真多轮对话必炸 "invalid_argument: cannot parse invalid wire-format data"(影响所有 enumValue-only 模型:gpt-4o/4o-mini/4.1/o3/deepseek/grok/qwen/gemini-2.5-flash/glm-4.7) - 修复 1:9fad3ac assistant 文本改走 ChatMessage.action(field 6)`ChatMessageAction{ChatMessageActionGeneric{string text=1}}`,对照 exa.chat_pb.proto 逐层验证,VPS 多轮 3+3=6 恢复 - 二次暴露:d02efc3(04-20 21:44)[user, assistant+tool_calls, tool, user] 仍炸——role:tool 与 assistant.tool_calls 不被 legacy 端点接受 - 修复 2:d02efc3 编码前把工具历史降级为叙述文本(role=tool → `[tool result for ]` 包装进 user;tool_calls → 拼进 assistant 文本),Cascade 路径不受影响 - 现状:**已根治**(legacy 端点只剩纯文本语义,工具历史语义由叙述化保真;Claude Code 风格请求 VPS 三场景验证通过)