Releases: dwgx/WindsurfAPI
v2.0.11
v2.0.11 — 实验性 sketch dashboard 皮肤 (cookie 切换)
新增:仪表盘可切换皮肤。默认仍是现代风(不动),加了一个手绘草稿风的实验性 UI 作为 opt-in 替代。
切换方式 (How to switch)
- 设置入口:仪表盘"实验性功能"面板新增"界面风格 / Console skin"下拉选择。
- 后端:cookie
dashboard_skin=sketch命中即返回 sketch HTML,否则返回默认 HTML。Vary: Cookie防止中间层串味。 - Sketch 皮肤里也有一个回切按钮(侧栏脚部)和一个面板里的下拉。
The dashboard now has a switchable skin. Default modern UI is unchanged; a hand-drawn sketch UI is added as an opt-in alternative.
- Toggle: experimental panel → "界面风格 / Console skin" dropdown.
- Backend: cookie
dashboard_skin=sketchselects the sketch HTML; otherwise default.Vary: Cookieprevents intermediary cache poisoning. - Sketch skin also exposes a back-to-default button in its sidebar footer and a dropdown inside its experimental panel.
Sketch 皮肤逻辑修复 (audit 出来的 critical drift)
第三方贡献的 sketch UI 跟生产后端有几处 API contract drift,独立审计 (codex) 拉出来后这次都补齐了:
- logs SSE:从
EventSource ?token=改成fetch + X-Dashboard-Password头,payload 字段名{ts, msg}对齐生产;之前的写法对正式后端是 401 / 字段全空。 - experimental routes:
PATCH→PUT,POST /clear-pool→DELETE /conversation-pool;读flags.cascadeConversationReuse而不是顶层。之前所有切换静默失败。 - system-prompts editor:整段从默认 UI 移植过来 (
loadSystemPrompts/saveSystemPrompt/resetSystemPrompt),sketch 现在能跟默认 UI 一样改重置 prompt 模板。 - /stats 字段名 drift:
r.hourly / r.models / r.accounts→r.hourlyBuckets / r.modelCounts / r.accountCounts(后端 shape 早就是这样)。之前 sketch 的 stats 永远是空。 - /bans 端点不存在:sketch 改成跟默认 UI 一样从
/accounts派生 ban 列表。原来的/bans调用是 404。 - /credits:后端无此端点,sketch 现在 graceful fallback 到 GitHub PR 链接。
- 原本 sketch 缺的 nice-to-have 全补齐:proxy 测试按钮 + 状态、batch-import 多行 proxy 语法说明、stats 范围切换 (24h/7d/30d)、清空统计按钮、stats + bans 的 30s 自动刷新。
Sketch skin contract drift fixes (called out by independent codex audit):
- logs SSE: switched from
EventSource ?token=tofetch + X-Dashboard-Passwordheader; payload field names corrected to{ts, msg}. The previous wiring 401'd against the real backend and rendered no log content. - experimental routes:
PATCH→PUT,POST /clear-pool→DELETE /conversation-pool; readsflags.cascadeConversationReuseinstead of a top-level field. Earlier toggles silently reverted on next load. - system-prompts editor: ported over from the default UI (
loadSystemPrompts/saveSystemPrompt/resetSystemPrompt); the sketch panel can now read/write/reset prompt templates the same way. - /stats response-shape drift: sketch was reading
r.hourly / r.models / r.accounts; backend shipsr.hourlyBuckets / r.modelCounts / r.accountCounts. Stats panel was empty until this release. - /bans nonexistent: sketch now derives the ban list from
/accounts(same pattern as the default UI). The previous/banscall 404'd. - /credits: backend has no such route; sketch now gracefully falls back to a link to the GitHub PR list.
- Nice-to-have catch-up: proxy test button + status line, batch-import help text covering the optional per-line proxy syntax, stats range presets (24h/7d/30d), reset-stats action, 30s auto-refresh on stats + bans.
Emoji → inline SVG
Sketch 里所有可见的 unicode 图标 (✓ ✗ → ↻ ▸ ☾ ☀ ✎ ↳ 📋 🔍 ♡ ⟳ ✱,共 30 处) 全部替换成 Lucide 风格的 inline SVG,零 npm 依赖,跨平台渲染一致。CSS content: 伪元素 (4 处) 留作字体装饰,不会渲染成 emoji。
All 14 unicode pictographs in the sketch UI (30 total occurrences) replaced with inline Lucide-style SVG. Zero npm deps. CSS pseudo-element content: rules are intentionally left as font glyphs.
兼容性 (Compatibility)
- 默认 UI 行为完全没变。已有用户切换前不会感到任何区别。
- 只新增了 cookie
dashboard_skin,路径/,max-age 一年,samesite=lax。 - 同一台浏览器在不同 dashboard 端点之间共享 cookie;多 origin 部署需要分别切换。
- Default UI behavior is unchanged. Existing users see no difference until they opt in.
- New cookie
dashboard_skinonly; path=/, max-age=1y, samesite=lax. - Cookie is per-origin; multi-origin deployments switch independently.
致谢 (Acknowledgements)
- Sketch UI 设计:Claude design 提供初稿,本版按 codex 5.4 审计 + claude 收尾把 API drift 全部补齐。
- Sketch UI: original draft from Claude design; this release reconciles it against the production backend per a codex 5.4 audit + claude review pass.
v2.0.10
v2.0.10 — hotfix: dynamic cloud probe was crashing silently
v2.0.9 部到 staging 跑端到端 probe 时发现的:每次账号 refresh 周期日志里都报
[WARN] Dynamic cloud probe failed: positiveIntEnv is not defined
src/auth.js::probeAccount() 调用了 positiveIntEnv('MAX_CLOUD_PROBES', 10),但这个 helper 只在 src/client.js 和 src/conversation-pool.js 里各自局部定义了,没在 auth.js 里声明也没 import。runtime 第一次跑 cloud-probe 路径就 ReferenceError,被外层 try/catch 吞了变成一行警告,free 账号的 cloud 候选模型发现完全不工作。不影响主代理路径,所以一直没人察觉。
修复 (Bug fix)
src/auth.js: 把positiveIntEnv在 module 顶部定义一份(跟client.js/conversation-pool.js同形态,零依赖)。
Bug fix
src/auth.js: definepositiveIntEnvinline at the top of the module. The helper exists in the two other files that need it; auth.js just never got its copy and the cloud-probe path went silent.
致谢 (Acknowledgements)
- 端到端 probe 在 Tokyo VPS 上发现:
scripts/_v209_probe.py+ pm2 logs grep。 - Found while running
scripts/_v209_probe.pyagainst a v2.0.9 deployment on the Tokyo staging box and grepping pm2 logs for warnings.
v2.0.9
v2.0.9 — Cloud-deployment hardening (#67 / #68 / cloud tool-calling regression)
This release fixes three independent issues that compounded in cloud deployments:
accounts.jsonwas getting orphaned on docker-compose upgrades.- Bare
claude-4.6requests silently fell through to a default model. - Large tool catalogs (Claude Code, Cline, opencode, Codex CLI) blew past the upstream LS panel-state ceiling and tools were never called.
This release fixes all three. No upstream protocol changes.
修复 (Bug fixes)
- #67 / docker-compose 升级账号丢失:
accounts.json现在固定写在 cluster-shared 的<DATA_DIR>/accounts.json,不再随REPLICA_ISOLATE/ 容器HOSTNAME变化。新增启动迁移:如果 shared 路径上没有 accounts.json 但发现一个或多个replica-*/accounts.json(旧版本残留),自动按 apiKey 取并集写入 shared 路径。重复出现的 apiKey 取首次出现的记录。 - docker-compose.yml 默认值改为单副本:
REPLICA_ISOLATE=0+replicas: 1。runtime-config.json/model-access.json/ 响应缓存 / Cascade reuse pool 都还是按进程隔离,多副本是 opt-in,需要使用方自己提供外部协调。 - #68 /
claude-4.6自报 4.5:根因是 catalog miss 的 silent fallback,不是身份重写缺失。resolveModel('claude-4.6')miss 后返回原字符串、getModelInfo返回 null、chat.js 把请求路由进 legacyrawGetChatMessage而 model name 都没传给上游,模型回到训练数据里"Claude 4.5"的自我认知。补 aliasclaude-4.6/-thinking/-1m/-thinking-1m→ 对应claude-sonnet-4.6*,并让 chat.js 对未知模型直接返 400model_not_found,不再 silent 降级。 - Cloud 部署 tool calling 不工作:
buildCascadeConfig()之前把同一份完整 tool schema blob 同时塞进 field 12(additional_instructions_section)和 field 10(tool_calling_section)。文件内已有的注释早已确认 NO_TOOL planner mode 抑制 field 10 — 它只是在膨胀 payload,给 30+ tools 的 Claude Code / Codex 请求把 panel state 推过 ~30KB 上限,触发Panel state missing on Send重试链直到放弃。field 10 现已不再注入。 - Tool preamble 容量护栏:proto-level preamble 超过
TOOL_PREAMBLE_SOFT_BYTES(默认 24KB)时自动降级为 names-only compact 形态(保留协议 + 工具名 + 环境块,丢弃参数 schema),超过TOOL_PREAMBLE_HARD_BYTES(默认 48KB)直接返 400tool_preamble_too_large,调用方明确知道要 trim。
Bug fixes:
- #67 / docker-compose upgrades dropped accounts:
accounts.jsonnow lives at the cluster-shared<DATA_DIR>/accounts.jsonregardless ofREPLICA_ISOLATEor containerHOSTNAME. On startup, if the shared file is missing but one or more legacyreplica-*/accounts.jsonfiles exist under the data dir (carry-over from prior versions or upgrade cycles with rotating hostnames), they are union-merged byapiKey(first-seen wins) and written to the shared path. DuplicateapiKeyentries are dropped. - docker-compose defaults are now single-replica:
REPLICA_ISOLATE=0andreplicas: 1.runtime-config.json,model-access.json, the response cache, and the Cascade reuse pool are still per-process; multi-replica is opt-in and requires external coordination for those state files. - #68 /
claude-4.6reported itself as 4.5: this turned out to be a routing fallthrough, not an identity-rewriting gap.claude-4.6was missing from the alias table,resolveModel()returned the raw string on miss,getModelInfo()returned null, andchat.jsrouted the request through the legacyrawGetChatMessagepath with no model name attached, so the upstream picked a default model whose self-knowledge predates 4.6. Added explicit aliases (claude-4.6/-thinking/-1m/-thinking-1m→ correspondingclaude-sonnet-4.6*) and made unknown models return400 model_not_foundinstead of silently degrading. - Cloud-deployment tool-calling regression:
buildCascadeConfig()was injecting the same full tool schema blob into bothadditional_instructions_section(field 12) andtool_calling_section(field 10). The in-file comments already established that NO_TOOL planner mode suppresses field 10 — it was bloating payload without contributing. With 30+ tool catalogs (Claude Code, Cline, opencode, Codex CLI) the doubled blob pushed total LS panel state past the documented ~30KB ceiling and tools silently failed via the panel-state retry path. Field 10 is no longer written. - Tool preamble byte budget: when the proto-level preamble exceeds
TOOL_PREAMBLE_SOFT_BYTES(default 24KB) it is replaced with a names-only compact form (protocol + tool names + environment block, no parameter schemas). When it exceedsTOOL_PREAMBLE_HARD_BYTES(default 48KB) the request returns400 tool_preamble_too_largeso the caller knows to reduce tool count or shrink schemas.
兼容性 (Compatibility)
- 未知模型不再 silent fallback — 之前依赖拼写错误回退的调用方现在会拿到 400。这是行为变更,但是正确方向。
- docker-compose 多副本变成 opt-in。如果你之前依赖默认的 3 副本部署,请显式
docker compose up -d --scale windsurf-api=3并接受runtime-config.json/model-access.json/ 缓存 / cascade pool 仍按进程隔离;后续版本会把这些外部化。 claude-4.6(无 sonnet/opus 后缀)现在路由到 sonnet 变体;如果你之前明确想要 opus 请改用claude-opus-4.6。
Compatibility:
- Unknown model names no longer silently degrade — callers that relied on typo fallback now get an explicit 400. Behavior change, but the correct direction.
- Multi-replica docker-compose is now opt-in. If you previously relied on the 3-replica default, scale explicitly with
docker compose up -d --scale windsurf-api=3and accept thatruntime-config.json/model-access.json/ cache / cascade pool stay per-process for now; externalizing them is on the roadmap. - Bare
claude-4.6(no sonnet/opus suffix) routes to the sonnet variant. Useclaude-opus-4.6explicitly if you wanted opus.
新环境变量 (New environment variables)
TOOL_PREAMBLE_SOFT_BYTES(default24000) — switch to names-only proto preamble above this size.TOOL_PREAMBLE_HARD_BYTES(default48000) — return 400tool_preamble_too_largeabove this size.
测试覆盖 (Test coverage)
- 全套 174 个测试通过(v2.0.8 = 156)。新增:
test/auth-migration.test.js— 7 cases 覆盖 replica-* 子目录单/多源迁移、apiKey 去重、损坏 JSON 容错、空目录跳过、shared 已存在短路、sharedDir 不存在。test/tool-emulation.test.js— 7 cases 覆盖 compact preamble 与 full schemas 的尺寸比、所有工具名保留、参数 schema 完全删除、环境块保留、tool_choice=required正确、空 tools 边界、无 jailbreak 措辞。test/models.test.js— 2 cases 覆盖 bareclaude-4.6系列 alias resolve + getModelInfo 落到真实 catalog 条目。test/tool-preamble-forbidden-words.test.js— 扩展覆盖 compact preamble。
Test coverage:
- All 174 tests pass (v2.0.8 had 156). New:
test/auth-migration.test.js— 7 cases covering single/multi-source migration fromreplica-*subdirs, apiKey dedup (first-seen wins), corrupt-JSON tolerance, empty subdir skip, short-circuit when shared accounts.json already exists, missing sharedDir.test/tool-emulation.test.js— 7 cases for the compact preamble: size ratio vs full schemas, every tool name preserved, schemas fully omitted, environment block preserved,tool_choice=requiredhandled, empty-tools edges, no jailbreak phrasing.test/models.test.js— 2 cases for bareclaude-4.6*resolve + getModelInfo lands on a real catalog entry.test/tool-preamble-forbidden-words.test.js— extended to cover compact preamble paths too.
致谢
- 报告 #67 / #68 / #69:@lihengcn
- 项目级审计:codex 5.4 high-reasoning(archive 在
tmp/audit-report-2026-04-26-claude-takeover.md)
Acknowledgements:
v2.0.8
v2.0.8 — 紧急登录修复 (Emergency login fix)
Windsurf 在 2026-04-26 当天调整了登录链路(半迁移状态),导致 dashboard 添加账号偶发 / 持续失败。本版只针对登录链路加固,无其他改动。
Windsurf reshuffled their login flow on 2026-04-26 (mid-migration), causing the dashboard "add account" flow to fail intermittently or persistently. This release is a focused login-path hardening; no other changes.
修复 (Bug fixes)
- 升
CheckUserLoginMethod为主邮箱探测(Connect-RPC,响应形态{userExists, hasPassword},跑在 Windsurf_backend后端,响应快、稳定)。 - 旧
/_devin-auth/connections路径降级为 fallback,并新增对其新 schema ({connections:[{type,enabled,...}]}) 的兼容;老 schema ({auth_method:{method,has_password}}) 仍兼容。 _devin-auth/*系列端点跑在 Vercel functions 上时不时 504 / 503 (FUNCTION_INVOCATION_TIMEOUT):增加 5xx 退避重试 (3 次, 0/2s/5s),4xx 仍直通不重试。password/login的错误detail字段从 string 变成 Pydantic v2 的 array 时不再触发.toLowerCase数组炸栈,统一拼成可读字符串。
Bug fixes:
- Promoted
CheckUserLoginMethod(Connect-RPC) to the primary email probe. The new endpoint returns a clean{userExists, hasPassword}and runs on Windsurf's_backendcluster; responses are fast and stable. - Demoted
/_devin-auth/connectionsto fallback and added compatibility for its new schema ({connections:[{type,enabled,...}]}); the old{auth_method:{method,has_password}}shape is still accepted. - Added 5xx exponential-backoff retries (3 attempts at 0 / 2s / 5s) for
_devin-auth/*endpoints, which intermittently timeout on Vercel functions; 4xx responses still skip retries. password/loginnow safely normalizes thedetailfield whether it comes back as a string (legacy) or an array of Pydantic v2 validation errors.
致谢
- Repro / 反馈:@Wenlong-Guo (#66)
- 路径迁移定位:codex worker (Playwright 抓 Windsurf 网页登录流量)
Acknowledgements:
- Repro / report: @Wenlong-Guo (#66)
- Migration localization: codex worker (Playwright capture of Windsurf web login traffic)
v2.0.7
v2.0.7 — Audit-driven hardening + Codex/Anthropic spec compliance
修复 (Bug fixes)
- 修复 #59 sub-bug 3 工具边界文本被切两段:
ToolCallStreamParser.feed()之前返回{text, toolCalls}两个独立数组,丢失了 text 与 tool_call 在同一 chunk 内的相对顺序,导致C. 我不会/ Read 工具 /修改文件,也不会扩大 scope这种"句子被工具切两段"的现象。新增items字段保留顺序,stream 消费方按 items 顺序 emit;text/toolCalls保留向后兼容。 - 修复 #66 限流时间精度与 cooldown 累积偏差:
rateLimitCooldownMs现在解析retry after N seconds/minutes/hours文案;markRateLimited改为Math.max(existing, new),并发 429 不再把 cooldown 不断后推;preflight 没拿到具体 retry 时间时不再本地标 cooldown,本次 skip 即可。 - 修复 #59 sub-bug 2 长输出被 180s 硬截断:
CASCADE_MAX_WAIT_MS默认从 180s 提到 600s,warmStallMs=25s仍负责真正卡死的 cascade 退出,给慢但持续流的长输出留足空间。 - 修复 #63 streaming response.completed 被静默丢弃:
/v1/responses生命周期事件(created / in_progress / completed / failed)payload 改为 OpenAI 规范的{ response: {...} }envelope;新增sequence_number单调递增字段;补response.in_progress事件。 - 修复 cache HIT 流式分支与 live-stream chunk shape 不一致:cache HIT 也拆成独立的
finish_reasonchunk + 单独的 usage chunk,跟 live-stream 路径同形。 - 修复全账号 RPM 满返 503:现在返 429 +
Retry-After头让客户端能正常重试,503 只在真没账号时返。 - 修复 preflight skip 占住本地 RPM headroom:account 增加
_lastReservationAt,preflight !hasCapacity 路径自动refundReservation退回_rpmHistory。
Bug fixes:
- Fixed #59 sub-bug 3 tool-boundary text split:
ToolCallStreamParser.feed()previously returned{text, toolCalls}as separate arrays and lost the relative order of text and tool_call within a single chunk, producing "sentence cut by a tool call" symptoms likeC. 我不会/ Read /修改文件…. Added an ordereditemsfield; stream consumer now emits in order;text/toolCallskept for back-compat. - Fixed #66 rate-limit cooldown precision and accumulation drift:
rateLimitCooldownMsnow parses explicitretry after N seconds/minutes/hours;markRateLimitedusesMath.max(existing, new)so concurrent 429s don't keep extending cooldown; preflight without explicit retry time only skips this attempt instead of poisoning local cooldown. - Fixed #59 sub-bug 2 long-output 180s truncation: raised
CASCADE_MAX_WAIT_MSdefault from 180s to 600s;warmStallMs=25sstill exits genuinely-stalled cascades. - Fixed #63 silently-dropped
response.completed:/v1/responseslifecycle events (created / in_progress / completed / failed) now use the OpenAI-spec{ response: {...} }envelope; added monotonicsequence_number; added the missingresponse.in_progress. - Fixed cache-hit stream chunk shape mismatch with live stream: cache-hit path also splits into a
finish_reasonchunk + a separate usage chunk, matching live-stream shape. - Fixed all-RPM exhaustion returning 503: now returns 429 +
Retry-Afterso clients can retry; 503 reserved for the genuinely no-account case. - Fixed preflight skip still consuming local RPM headroom: account adds
_lastReservationAt; the preflight!hasCapacitypath auto-refunds the reservation in_rpmHistory.
兼容性与规范 (Compatibility / spec)
/v1/responses拒绝非functiontools(如web_search_preview)改为 400 错误,避免静默 drop 后客户端语义已变;function-call-only 响应不再带空messageitem 在output。/v1/messages透传 Anthropicthinking字段;tool_choice映射到 OpenAI 形状(auto→auto、any→required、tool/name→{type:'function',function:{name}}、none→none)。/v1/responses流式新增 Codex CLI v0.125 期望的最小 envelope:{"type":"response.completed","response":{"id":"resp_…",…},"sequence_number":N};非生命周期事件(output_item.* / content_part.* / output_text.* / reasoning_. / function_call_arguments.*)shape 不变。
Compatibility / spec:
/v1/responsesrejects non-functiontools (web_search_preview, etc.) with 400 instead of silently dropping; function-call-only responses no longer include an emptymessageitem inoutput./v1/messagesnow passes through Anthropicthinking;tool_choiceis mapped to OpenAI shape (auto→auto,any→required,tool/name→{type:'function',function:{name}},none→none)./v1/responsesstreaming now emits the minimal envelope Codex CLI v0.125 expects:{"type":"response.completed","response":{"id":"resp_…",…},"sequence_number":N}. Non-lifecycle events (output_item., content_part., output_text., reasoning_., function_call_arguments.) shape unchanged.
CI / 发布流程 (CI / release)
- 新增
.github/workflows/release.yml:推v*tag 时自动构建linux/amd64Docker 镜像推 GHCR + 创建 GitHub Release(RELEASE_NOTES_x.y.z.md自动作为 body,含-自动 prerelease)。 docker-compose.yml同时保留image:与build::默认拉预构建镜像,docker compose up --build仍可本地源码迭代。- 修正镜像命名
windsurfapi→windsurf-api(与package.json项目名对齐)。
CI / release:
- Added
.github/workflows/release.yml: pushing av*tag now auto-builds thelinux/amd64Docker image to GHCR and creates a GitHub Release (withRELEASE_NOTES_x.y.z.mdas the body; tags containing-become pre-releases). docker-compose.ymlkeeps bothimage:andbuild:: pulls the prebuilt image by default,docker compose up --buildstill iterates locally.- Image name corrected to
windsurf-api(aligned withpackage.jsonproject name).
测试覆盖 (Test coverage)
- 全套 158 个测试通过(v2.0.6 = 144),新增 12 个:parser ordered-items 、cooldown 解析、preflight refund、429 not 503、
/v1/messagesthinking + tool_choice 透传、/v1/responses非 function tools 与空 message item、cache-hit chunk shape 与 live-stream 同形等。 - Mac arm64 (node v24.13.0) 同样 158/158 通过,跨平台无回归。
Test coverage:
- All 158 tests pass (was 144 in v2.0.6); 12 new: parser ordered items, cooldown parsing, preflight refund, 429 not 503,
/v1/messagesthinking + tool_choice passthrough,/v1/responsesnon-function tools and empty message items, cache-hit chunk shape parity with live stream. - Mac arm64 (node v24.13.0) also passes 158/158, cross-platform clean.
致谢
- 本版基于 gpt-5.5 high-reasoning 全项目代码审计(296 行 P0/P1/P2 报告,归档在
tmp/audit-report-2026-04-26.md,未发版)。 - CI / release workflow by @abwuge (PR #65)。
- Codex CLI v0.125 SSE 解析路径反查与 envelope 规范定位由 codex worker 完成。
- 关联 issue: #59, #63, #66。
Acknowledgements:
v2.0.6 — Security hardening: caller 隔离 / SSRF / 日志脱敏 / #57 thinking-stall 修复
v2.0.6 — Hardening release
安全 (Security)
- 修复 Cascade conversation pool 的跨调用方复用风险:fingerprint 现在包含 callerKey,API key、dashboard session 或 IP/User-Agent 会隔离相同首轮 prompt 的 hidden state。
- 修复多模态响应缓存污染:图片和 PDF data URL 的 MIME、内容 hash 与字节数进入 cache key,避免同文本不同图片命中旧回答。
- 代理、请求日志和 dashboard 登录返回值加强脱敏:LS proxy 日志不再输出账号密码;请求 probe 默认只记录长度和 hash;登录取号默认只返回 masked API key,并提供受 dashboard auth 保护的 reveal-key 接口。
- 加强 SSRF 防护:统一 DNS 后校验,覆盖 IPv4 私网、link-local、loopback、CGNAT、IPv6 ULA/link-local/loopback 和 IPv4-mapped IPv6。
- PDF FlateDecode 增加 zip bomb 防护:单 stream、累计解压和 stream 数量都有限制,超限时降级为“PDF 内容无法提取”。
- 未配置 API_KEY / DASHBOARD_PASSWORD 且服务监听非 localhost 时,启动横幅会输出中英双语高强度警告,保持兼容但明确暴露风险。
Security:
- Fixed cross-caller Cascade conversation reuse by including callerKey in fingerprints, isolating identical prompts by API key, dashboard session, or IP/User-Agent fallback.
- Fixed multimodal cache poisoning by adding image/PDF MIME, content hash, and byte size to cache keys.
- Hardened proxy, request, and dashboard key redaction: LS proxy logs no longer expose credentials, request probes default to length/hash, and login endpoints return masked API keys by default with a dashboard-authenticated reveal endpoint.
- Strengthened SSRF checks with post-DNS validation for private IPv4, link-local, loopback, CGNAT, IPv6 local ranges, and IPv4-mapped IPv6.
- Added PDF FlateDecode zip-bomb limits with a graceful “PDF 内容无法提取” fallback.
- Added loud bilingual startup warnings when auth is not configured on non-localhost binds.
修复 (Bug fixes)
- 修复 #57 thinking-only 长流被 cold-stall 误杀的问题,并将 stall 分类拆成
transient_stall/model_error,避免污染 capability 与限流判断。 - 修复 Responses API 对
reasoning_content的流式和非流式映射,输出 Responses reasoning item 与 summary delta。 - 修复 dashboard batch import 使用
result.accountId导致 per-account proxy 未绑定的问题,现在读取result.account.id。 - 修复三层 stream 错误协议:Chat 发送结构化 OpenAI error SSE,Messages 转成 Anthropic
event: error,Responses 转成response.failed。 - 修复 Linux auto-install 缺少
dirname/fileURLToPath/joinimport 的问题。 - 修复 account reprobe 并发入口,避免重复 probe 同时运行。
Bug fixes:
- Fixed #57 cold-stall false positives during thinking-only streams, and split stall/model classifications to avoid capability and rate-limit pollution.
- Mapped
reasoning_contentfor both streaming and non-streaming Responses API output. - Fixed dashboard batch import proxy binding by reading
result.account.id. - Structured stream errors across Chat, Anthropic Messages, and Responses protocols.
- Fixed missing imports in the Linux language-server auto-install path.
- Added a reprobe in-flight guard to prevent overlapping account probes.
健壮性 (Robustness)
- SIGTERM/SIGINT shutdown now aborts active SSE streams with a structured
server shutting downerror before draining HTTP connections. - Cache normalization now includes
response_format、reasoning_effort、thinking、stream_options,避免不同请求选项互相污染。 - Proto tool preamble 去除
Ignore/---等 jailbreak-shaped wording,并新增 forbidden wording 回归测试。 - 新增 23 个回归测试,覆盖 caller 隔离、cache 多模态 key、proxy 脱敏、stream error、SSRF、PDF 限制、auth warning、SSE registry 和 tool preamble 禁词。
Robustness:
- SIGTERM/SIGINT now abort active SSE streams with a structured
server shutting downerror before HTTP drain. - Cache normalization now includes
response_format,reasoning_effort,thinking, andstream_options. - Removed jailbreak-shaped wording such as
Ignoreand---from the proto tool preamble and added forbidden-word regression tests. - Added 23 regression tests covering caller isolation, multimodal cache keys, proxy redaction, stream errors, SSRF, PDF limits, auth warnings, SSE registry, and tool preamble forbidden wording.
致谢
Acknowledgements:
v2.0.5 — Codex CLI / Opus 4.6 multimodal / pool transient handling
TL;DR
围绕「上游瞬态 + Opus 多模态 + Codex CLI 兼容」三条线收尾。涵盖外部贡献者 4 个 PR + 反代内部 5 处 surgical fix + dashboard 重大 escape regression hotfix。
新功能
POST /v1/responses端点(#56 #63)—— OpenAI Responses API 兼容层。新版 Codex CLI 强制wire_api = "responses",原本 404 直接挂;现在 input/items/instructions/tools 全 map,流式完整 9 事件序列含response.completed,function_call / function_call_output round-trip 工作。
Bug 修复
upstream_transient_error错误类别(#28)—— Cascade 上游 9 个账号都返internal error occurred时不再误报"速率限制",账号间加 0.5s→5s 指数退避避免瞬态雪崩。- 空 user.content 拒绝(#28 OpenClaw probe 副产物)—— 之前会让模型对系统提示自言自语,现在直接 400
invalid_request_error。 - Opus 4.6 reuse + multimodal fallback 扩展(#59)—— 之前只 4.7 走的 strict-reuse / 禁用 user-message tool fallback 现在 4.6 也走,匹配 dotted(
claude-opus-4.6)和 dashed(claude-opus-4-6-medium)两种命名。 - Stream 路径错误优先级修正(cold-eyes audit)——
upstream_transient_error优先于 rate-limit 消息。 - 24KB 重 system prompt 警告日志(#28)—— OpenClaw / opencode / Cline 这种逼近 panel-state 阈值的客户端排查用。
贡献者 PR(按时间)
- PR #62 @baily-zhang —— dashboard inline-script escape critical fix + V8 静态解析 regression test
- PR #61 @baily-zhang —— Opus 4.7 + Claude Code + 图片 + tools 上下文爆炸根治
- PR #58 @abwuge —— docker-compose 部署死锁修复(nginx zone + config.js join import)
- PR #54 @aict666 —— tool preamble 1600→330 字符 + redact marker 第 6 代 U+2026 省略号 + neutralizeCascadeIdentity 5 个 pattern
测试
121/121 全过(v2.0.4 是 97/97,新增 24 个回归断言)。
部署
git pull origin master
# Docker
docker compose up -d --build
# 或 pm2
pm2 restart windsurf-api --update-env完整 commit log
gh release view v2.0.4..v2.0.5 或看 Compare。
v2.0.3 — 上下文稳定性 + Dashboard 大升级
v2.0.3 — 上下文稳定性 + Dashboard 大升级
今天一整天围绕 issue #24 上下文丢失 + #48 system prompt 注入残留 的打磨 已全部实测验证
🔧 issue #24 — Agent 场景文件幻觉 + Read 循环
Cascade 偶尔在响应里幻觉 /tmp/windsurf-workspace/xxx 这种沙箱路径(训练分布遗留)导致客户端循环
之前三代尝试都反噬:
| 尝试 | 问题 |
|---|---|
改写为 ./tail |
Claude Code Read 本地 ./src/main.py ENOENT 循环 |
改为 [internal] |
LLM 把 [internal] 当目录名 ls [internal] 继续循环 |
改为 <redacted-path> |
LLM 把它当 file_path 喂给 Read 工具 Windows 还 Errno 22 崩溃 |
v2.0.3 最终方案:改为自然语言 (internal path redacted) — 多词带空格带括号 既不是路径格式 也不是 token 客户端和 LLM 都不会把它当文件用 drift probe 实测 sonnet 不再崩溃
同时强化 NO_TOOL 的 additional_instructions proto 字段:从弱指令"你没有工具"改为行为级硬约束 列清具体禁止的 agent 叙述句式("Let me check X" / "Looking at the file...")+ 给 fallback 模板 实测"问模型 src/main.py 是什么"不再编代码 直接回 "I don't see that file in our conversation — please paste it"
🔧 issue #24 — Panel state not found 重试加固
opencode + omo 插件场景(30KB+ system prompt + 30+ tools)LS 会快速失效 panel state 单次 retry 不够
- Send 失败后 最多 retry 3 次 每次 full warmup + 新 StartCascade
- retry 间 250ms × n backoff 让 LS 落地新 panel state
- 3 次失败报清晰错误指向大 payload 本身是上游 LS 的上限
🔧 Tool-emulation 协议加固
合并 PR #50 @aict666:tool-emulation 请求跳过 cascade_id 复用(tool_use/tool_result body 每次变 fingerprint 命中率本来就近 0 reuse 只是徒增 pool 污染)
新增 safeParseJson lenient 解析:claude-4.5-haiku 偶尔输出 <tool_call>{...}}}</tool_call> 尾部多一个 } 之前严格 JSON.parse 失败直接把 <tool_call> 字面量回给客户端 现在宽容扫描第一个平衡 JSON 对象
🔧 issue #48 — 移除自动 system prompt 注入
用户报"关了模型身份注入开关还是有很多 system prompt"
injectLanguageHint删除 — CJK 消息无条件追加英文[IMPORTANT: You MUST respond in Chinese]即使开关关了这条还在跑modelIdentityPrompt删除 — 连带 identity prompts 模板编辑器 per-provider 模板/dashboard/api/identity-prompts端点 Dashboard UI 全部清理- 保留用户主动触发:response_format=json_* JSON hint / tools[] → tool-emulation preamble
- 保留响应侧:neutralizeCascadeIdentity("I am Cascade" → "I am {model}")只改响应不碰请求
✨ Dashboard 大升级
账号管理:配额详情面板
每行账号前加 ▸ 展开四个分区:
- 订阅信息:套餐/层级/开始-到期/Trial 剩余天数/透支余额
- 配额使用:日/周大 bar + 重置时间 + Prompt Credits + Flex Credits
- 可用模型:按 provider 分组彩色 swatch(Anthropic/OpenAI/Google/DeepSeek/xAI/Alibaba/Moonshot/Zhipu)每个模型 chip 显示每次调用扣多少 credit
- 运行状态:status/RPM/最后使用/最后探测/完整 ID + Key/上次错误
统计分析:4 种图表切换 + 日期范围
- 图表类型:面积图 / 折线图 / 柱状图(默认) / 堆叠图
- 日期范围:6小时 / 24小时 / 7天 / 30天 / 全部 / 自定义(原生 date picker)
- 数据存储:hourly buckets 从 72 扩到 720(30 天)
- 动画:480ms ease-out 柱从 0 长起 折线从 baseline lerp 数据点淡入
- 模型分布饼图:top 8 模型请求量占比 + center total label
- tooltip:pinned 在 canvas 内部(之前 translate(-100%) 会跑出 header)
🔧 hvoy.ai 验真签名
openai-processing-ms从硬编码0改为实际耗时(hvoy.ai 模型签名验证探测点)- 新增
openai-organization: org-windsurf-proxyheader response.model回传 request 原始名 不再是 Windsurf 内部 alias(之前claude-opus-4-7request 返回claude-opus-4-7-medium触发 hvoy.ai 模型一致性 fail)- identity prompt 注入的模型名剥离
-medium/-low/-highreasoning-effort 后缀 避免 Claude 把"claude-opus-4-7-medium不是我认识的模型"判为 prompt injection
📊 测试 & 实测
- 56 个单元测试 100% 通过
- 新增实测 probe(已删除未入库):5 类症状 × 2 模型 = 34 断言
- 上下文保持 6/6 ✓
- 跑题/牛头不对马嘴 10/10 ✓
- 大 payload panel state (23KB + 30 tools × 5 轮) 2/2 ✓
- 幻觉(PDF/image/虚构文件)6/6 ✓
- Coding agent(含真实 bug 发现)7/10
📦 部署
154.40.36.22:8996master HEAD 已推 pm2 管理- Base URL:
http://154.40.36.22:8996/v1(OpenAI) //v1/messages(Anthropic)
🙏 贡献
- @aict666 PR #50 — disable Cascade reuse for tool-emulated turns
- issue #24 报告者 @19920122441 @baily-zhang @kinzhi @jagernb @xyiqq
- issue #48 @stacklura
Commits
- 9120b8b fix(#24): redact marker →
(internal path redacted) - 2e0a2b1 fix: lenient JSON parse for
<tool_call>bodies - 64be622 fix(#24): retry panel-state-missing up to 3 times with backoff
- 5cb49e0 Merge PR #50 — disable Cascade reuse for tool-emulated turns
- ec8c7c7 fix(#24): redact leaked paths (中间态
<redacted-path>) - e48eb5e fix(#24): stop Cascade file-path hallucination + read-loop
- 2d3e569 feat(#48): remove all automatic system-prompt injection
- 955b837 feat(dashboard): rich per-account quota detail panel
- 4e2b8ca feat(dashboard): polished charts + date range + drop prompt bar
- 06f0940 feat(dashboard): default to bar + chart animation + pinned tooltip
- 85ea924 feat(dashboard): 4-type chart switcher + model pie + quota column i18n fix
- ad540f3 fix: model-signature headers + phantom-attachment guard
- 1f46a72 fix: robust JSON extraction + full OpenAI PDF input coverage
- e8a6f42 fix: response.model echoes request.model verbatim
v2.0.2 — 结构化输出 + PDF 识别 + #47 工具调用修复
v2.0.2
修复
- #47 工具调用 bug:
if (emulateTools)条件导致解析出的 tool calls 被静默丢弃,Claude Code 看到的是空文本而非 tool_calls 数组。三处条件全部移除。
新功能
- 结构化输出:
response_format: { type: "json_object" }和json_schema模式。双层注入(system message + user hint + schema 内容)+ response markdown fence 自动剥离。 - PDF 文档识别:零依赖 PDF 文本提取器 (
src/pdf.js)。支持 Anthropictype: "document"content block。FlateDecode 解压 + Tj/TJ 操作符解析。扫描件返回明确提示。 - 思维链自动升级:请求带
thinking或reasoning_effort参数时自动选择 thinking 变体模型。
v2.0.1 — hvoy.ai 验真优化 + 结构化输出 + 思维链
v2.0.1
基于 v2.0.0,针对 hvoy.ai 验真评分优化:
修复
- 结构化输出:支持
response_format: { type: "json_object" } - 思维链自动升级:请求带
thinking参数时自动选 thinking 变体模型 - 模型签名 headers:
x-request-id/openai-model/openai-version/anthropic-model - Response 身份替换:"I am Cascade" → "I am {model}, made by {provider}"
- Tier 降级修复 (PR #44 @aict666)
- Cascade reuse offset 增量拉取 (PR #45 @baily-zhang)
- Dashboard 完整 i18n (PR #43 @smeinecke)
完整 changelog 见 v2.0.0 release notes。