Skip to content

Releases: YixuAn-13/we-need-ds

v5 - Turn-Aware DSH Minimal Simulation (v5.1)

Choose a tag to compare

@YixuAn-13 YixuAn-13 released this 01 Sep 16:56

v5.1(最新有效版本)

  • 判定轮:DSH 官方单行人格 You are a helpful software engineer assistant. + 工具裁至 Bash/Edit 两件套(常态生效,不限首轮)
  • 执行轮(v5.1):全量 MCP/Skill 工具放行 + DSH 人格同步切换(executionDshPersona: false 可退回完全透传)
  • 纯请求结构判定,零运行时状态,重试幂等

版本沿革

Tag 语义 Commit
v1 双阶段工具解耦 v1.0.0 (27367a6)
v2 消耗型武装窗口 d8b6a4b
v3 链感知武装窗口 69cceab
v4 判定轮裁剪 + 提示词层引导 18ab59c
v5 轮次感知 DSH 极简模拟,武装窗口退役 3adc757 → a2b7b57

测试:17 组模拟断言 + 18 组端到端断言全部通过。

v2.4.4 接管态路由 fail-safe + 误杀防护 + 漂移告警

Choose a tag to compare

@YixuAn-13 YixuAn-13 released this 08 Sep 10:53

审计三项修复 (v2.4.4)

机制版本不变 (v5.2),本次为安全边界与可观测性加固。

🔴 D2 — 接管态路由 fail-safe

cc-haha 接管态下,若请求携带的 API Key 未在 keyMap 命中,代理不再兜底 defaultUpstream,而是显式返回 502。防止被接管 provider 换了 key 后请求被带新 key 静默错发到原始上游(违背"502 显式失败优于静默错发"设计原则)。无 key 请求与纯 Claude Code 模式兜底语义不变。

⚪ D6 — killDaemonOnPort 误杀防护

boot/restart 清理残留 daemon 前加双闸门:先 /health-check 确认是本插件自家 daemon 才杀;无应答则检查进程映像须为 node.exe,端口被无关程序占用时返回 foreign 不杀(boot 跳过、restart 中止并提示改端口)。

🟡 D1 — doctor 接管漂移告警

账本处于接管态但被接管的 provider 已从池中消失(id 漂移)/ baseUrl 被外部改回直连 / 当前无任何 provider 指向代理时,doctor 打印醒目的接管漂移告警及恢复命令(纯输出、零行为改动)。补上"cc-haha 不跑钩子 → 接管悬空时 We need 静默失效"的盲区。

测试

  • test_full 144 → 150 断言(新增 A35–A40:D2 未命中拒绝错发/命中正常/无 key 兜底;D6 空闲端口/自家 daemon 识别杀掉)
  • test_stress S4 夹具修正(旧夹具经由 pb 的 key 走 pa 上游,正是 D2 修复的错发路径假绿,改用被接管 provider 自身 key)
  • 三套件全绿 (full 150 + consume 18 + simulation 18) + stress 全部通过

明确不修的项 (设计边界内)

  • D3 直连副本可误激活:逃生口与陷阱同体,已有标记与对账
  • D4 cc-haha 轨无自愈:用户否决系统级注册的直接代价,副本+其余 provider 直连已防死锁
  • D5 keyMap 明文:与 providers.json 同等敏感等级,无新增泄露面
  • D7 探测经裁剪:裁剪不改协议结构,无害

v2.4.3 — 判定轮注入 thinking + 剥离 system-reminder,修复 "We need" 实测不触发

Choose a tag to compare

@YixuAn-13 YixuAn-13 released this 08 Sep 09:21

两根因修复(16 组对照实验闭合因果链)

实测仍无法触发 "We need" 后,做了三轮共 16 组直连官方端点的对照实验,锁定触发条件链:单行人格 + thinking 字段存在 + user 消息 reminder 噪声 < 3 份,缺一不可。旧版只满足第一条。

🔴 缺口 1 — thinking 字段从未注入(第一根因)

  • thinkingBudget 默认 0 使 applyThinkingBudget 成 no-op → 判定轮发出"极简但无 thinking"形态(实验 P 组 = ❌)
  • 默认 0 → 8000(loadConfig 与 config.json 双处),判定轮在 Anthropic 路径注入 thinking:{type:"enabled",budget_tokens:8000}
  • 新增 max_tokens clamp 守卫:宿主小 max_tokens 请求(标题生成等,≤ budget)跳过注入,防 max_tokens > budget_tokens 400;OpenAI 路径维持不注入(防中转站 400)

🔴 缺口 2 — system-reminder 噪声未剥

  • CLAUDE.md / memory / MCP instructions / currentDate 以 <system-reminder> 注入 user content,几十份起步(实验 F/G 组 = ❌)
  • 判定轮新增 stripSystemReminders:按标签剥所有 user 消息(string 与数组 text 块,含历史轮);整条剥空保留原文(防空 content 400);非 text 块(tool_result/image)原样保留
  • Q 组关键洞察:抑制来自标签结构本身,非文本量 → 按标签匹配不按长度

🟡 缺口 3 — 接受为设计边界

历史 tool_use/tool_result 块抑制字面前缀(H 组),但删除会致 tool_result 孤儿 → 400,协议上不可裁。推理质量不崩,"We need" 是可观测量非目标。

🧪 端到端活体实测(发布前对官方端点验证)

构造"脏请求"(12 块人格 + 30 工具 + 4 份 reminder)过塑形管线后直发 api.deepseek.com/anthropic:

  • 英文任务 → thinking 块以字面 "We need answer user asks..." 开头,链长 9000+ 字符 ✅
  • 中文任务 → 同构深度规划链以 "我们需要回答中文…" 开头(模型随任务语言镜像前缀——历史上这常被误读为"没触发")
  • input_tokens 30k+ → 163,证明裁剪真实发生

其他

  • 机制版本 v5.1 → v5.2;测试 135 → 144(新增 E10–E18:注入/clamp 守卫/reminder 剥离全形态/非 DS 透传);三套件全绿(含 stress 25)
  • 非 DS 模型底线不变:字节级原样透传

v2.4.2 — 并发/极端场景压力审查 + 生产端到端复验

Choose a tag to compare

@YixuAn-13 YixuAn-13 released this 07 Sep 17:26

v2.4.2 — 2026-09-08

Added (concurrency & extreme-scenario stress regression)

  • New test_stress.js — 25 assertions covering process-level concurrency and hostile state mutation that the main suite (single-process, sequential) does not reach. Scenarios: 20-way concurrent forwarding under takeover (no cross-talk, no packet loss/duplication at the upstream), 10-round on/off flapping (every round converges to a consistent state, zero copy leaks), 8 concurrent ctl processes racing on/off (file-lock + atomic-write keep providers.json valid with no stray .tmp), an in-flight slow request vs a concurrent off (neither blocks the other), user hand-editing providers.json mid-takeover (deleting the hooked provider / switching activeId) then off (no crash, ledger retains the deleted provider's original upstream, surviving providers untouched), daemon killed mid-session → dead-state detection → boot recovery re-takeover, concurrent takeover switching A→B→A (exactly one copy, single-provider invariant holds), and 30 interleaved health/forward/ctl requests. All green across repeated runs.
  • Production end-to-end re-verified with a fresh off → on cycle against the real DeepSeek upstream: off restored the provider to direct and removed the copy; on re-hooked it and created the escape copy; a real request through :20329 returned HTTP 200, the proxy logged decision turn DSH-minimal, input tokens were trimmed (full 7-tool payload → DSH minimal set), the "We need" reasoning chain fired, and every tool the model requested fell within the trimmed {Bash, Edit} set — proving the trimming is real and the model acts inside the minimal environment.

Docs

  • Engineering report: registered test_stress.js in the component table, test-system section, and file tree.

v2.4.1 — 端口边界精确化 + M4 守卫补测

Choose a tag to compare

@YixuAn-13 YixuAn-13 released this 07 Sep 14:51

v2.4.1 — 2026-09-07

Fixed (port-boundary precision + guard coverage)

  • isSelfProxyUrl / isProxiedUrl substring false-match fixed. Both used url.includes(':<port>'), so a real upstream on port 203290 was misjudged as "the proxy itself" (:20329 is a substring of :203290) → rejected → 502. Same class as the already-fixed M5. Now matched with :${port}(?![0-9]) so the port must be followed by a non-digit (slash, colon, or end). Fail-safe direction, but wrong behavior — fixed for correctness. Tests M1a–M1c, M2a–M2c.
  • M4 malformed-path guard now has end-to-end coverage. The /^\/(?!\/)/ rejection of //host absolute-form paths existed in proxy.js since v2.4.0 but had no test; added a live-daemon test proving //evil.example.com/v1/messages → 400 while /v1/messages → 200 passthrough. Tests M4a/M4b.
  • Test flakiness reduced. Bumped the post-killPort settle wait from 300ms to 600ms in Phase L — a killed daemon occasionally hadn't fully released its port before the next spawn, causing a rare spurious EADDRINUSE failure (a known trap, not a logic bug).

Tests

  • New Phase M (test_full, +8): port-boundary precision for both URL predicates, malformed-path guard end-to-end. 135 assertions green.

v2.4.0 — 深度审计 + 三方重试对照全面修复

Choose a tag to compare

@YixuAn-13 YixuAn-13 released this 07 Sep 13:59

v2.4.0 — 2026-09-07

Fixed (deep audit + third-party retry comparison)

  • H1 — invalid --provider no longer destroys an existing takeover. enableInterception ran restoreAllProxied/removeAllCopies before validating the requested provider, so a bad --provider id wiped the current hook then bailed. Validation now happens first; every rejection path returns before any mutation. Regression test L9a/L9b.
  • Retry storm eradicated (classified retry). The proxy sits between aggressive-retry clients (Claude Code CLI retries ~10×) and an upstream that may itself retry; the old blanket "retry on any error" could stack into 30+ attempts. Now: deterministic network errors (ENOTFOUND/EAI_AGAIN/EHOSTUNREACH/EACCES/EPERM/invalid URL) fail fast with no retry; only transient failures (empty body, reset, 408/409/425/429, 5xx, timeouts) retry. Default upstreamRetries lowered 2 → 1. ECONNREFUSED deliberately stays retryable (covers local-relay restart windows). Tests L2a–L2d, L5, L6a/L6b.
  • Client disconnect cancels upstream. A hoisted per-request ctx.clientGone flag (set on res close) is checked before every attempt, after the response arrives, and in the catch — a gone client no longer triggers further retries or leaves a half-forwarded stream. A latent const ctx = {} shadowing bug inside the request handler (which had silently disabled the whole mechanism) is removed. Test L7.
  • Retry-After clamped to 0–60s and the exponential backoff capped at 60s, so an absurd header can't wedge the proxy. Tests L1a–L1d.
  • Standard error bodies. Proxy-generated 502/400 responses now emit Anthropic ({type:"error",error:{type:"api_error",message}}) or OpenAI ({error:{message,type,code}}) shapes based on the request path, instead of an ad-hoc {error:{message}}. Tests L3a/L3b, L5b.
  • M2 — ds substring false-match fixed. isDeepSeekProModel/providerHasTargetModels matched any normalized name containing ds, so models, adsl, etc. could false-trigger. Now ds must be a standalone token (delimited by separators/boundaries). Tests L4a–L4e.
  • M1 — hook intent consistency. SessionEnd now calls recoverOrphans (restore providers to direct but keep enabled) instead of disableInterception (which cleared the intent); SessionStart only auto-hooks when the ledger is enabled, so a deliberate off is no longer silently re-opened next session. Tests L10a/L10b, L11.
  • M3 — stale keyMap pruning. Keys no longer present in the provider pool are dropped on takeover, preventing routing to a deleted provider's old upstream.
  • M4 — malformed request-path guard. Paths not starting with a single slash are rejected with 400 (blocks //host-style absolute-form / SSRF-ish probes).
  • M5 — exact port match in killDaemonOnPort. Port matching now compares the full trailing segment instead of a substring, so killing :2032 no longer matches :20329.
  • L2 — env/default sentinel restored in resolveTargetBaseUrl (a prior cleanup had dropped it, hanging the env-fallback route).
  • L3 — --provider missing-value validation in ctl.js (a trailing --provider with no argument now errors clearly instead of silently taking over the default).

Docs

  • README (zh + en): upstreamRetries default 2 → 1 with classified-retry rationale; backoff/Retry-After clamp documented; hook semantics rewritten (SessionEnd keeps intent, SessionStart respects off); DS-detection rule updated to the standalone-ds-token form; version line 2.3.x → 2.4.x.

Tests

  • New Phase L (test_full, +28): backoff growth/clamp, retry classification, standard error bodies, M2 false-match regression, ENOTFOUND fast-fail, 5xx retry count, client-disconnect no-retry, /ctl on providerId end-to-end + H1 regression, session-end intent retention, session-start off respect. 127 assertions green.

we-need-ds v2.3.0

Choose a tag to compare

@YixuAn-13 YixuAn-13 released this 07 Sep 08:27

v2.3.0 — provider 选择接管 + 关机持久化根因显式告知

  • 新增 ctl list:列出全部 provider,标注 🎯含DS模型 / ⭐当前默认 / 🔌代理中,DS 优先排序
  • on --provider <id|name>:直接接管指定 provider,无需先在 cc-haha 手动设默认
  • 接管非默认 provider 时自动同步 activeId(sidecar 按 activeId 路由,必须同步)
  • 拒绝路径防呆:指定非 DS / 不存在的 provider 在写状态前拒绝,不污染 providers.json
  • skills(on / we-need-ds) 改为先列清单再让用户选择接管目标
  • README(zh+en) cc-haha 段顶部加醒目警告框:接管改写 providers.json 且关机/重启后持久保留,副本 + 未接管 provider 为兜底
  • test_full 新增 Phase K (+10),共 99 断言全绿

v2.2.1 — 统一 daemon 端口释放

  • restart/boot 共用 killDaemonOnPort:taskkill 后轮询确认端口释放,根治残留进程占端口导致的假接管
  • README 澄清接管只看 provider 声明的 models 字段

v2.2.0 — 单服务商接管 + 直连副本逃生口(根治重启死锁)

Choose a tag to compare

@YixuAn-13 YixuAn-13 released this 07 Sep 05:03

核心:根治重启死锁 —— 单服务商接管 + 直连副本逃生口

代理不再把 providers.json 里全部 27 个服务商的 baseUrl 批量改写到代理端口(旧版一旦 daemon 死亡即全局瘫痪、连恢复命令都发不出)。现在:

  • 只接管当前默认(activeId)且确实提供 DeepSeek Pro 模型的那一个服务商,其余服务商保持各自真实上游,物理层面 100% 直连可用。
  • 接管时克隆一份 · 直连副本(baseUrl 指向原始真实上游),作为 cc-haha 下拉框里随时可切的逃生口;daemon 死了切到它即恢复直连,再从容跑 off/boot/on。
  • 副本兼任冗余账本:即使 runtime-state.json 丢失/损坏,也能从副本 baseUrl 把本体救回真实上游。
  • 两段式安全释放:先还原本体 → 重读验证确已直连 → 才删副本;中途崩溃绝不让用户失去备用出口。
  • 旧全量接管态自动迁移:下次 on/boot 先把指向代理的 provider 还原、清旧副本,再按新方案接管;切换默认服务商重跑 on 时旧本体自动还原、副本始终只留一份。

文档

  • README(中/英):cc-haha 使用新增「开启前先把当前真正在用的服务商设为默认」,并说明直连副本逃生口与「退出前手动 off」习惯。
  • 新增 docs/ENGINEERING_REPORT.md 完整工程报告(架构/实现细节/问题史/边界/测试体系)。

测试

  • test_full.js 新增 Phase J(+10 项):单服务商接管、非 active 不动、副本字段语义、账本剪枝、幂等不重复建副本、off 还原+清副本、账本丢失副本救援、非 DS active 不接管。全套 89/89 通过,生产文件零污染。

机制版本 v5.1(轮次感知 DSH 极简模拟)不变;插件 semver 2.2.0。

v2.1.13 — 失败还原可逆(当轮恢复裁剪) + 中途断开透明化

Choose a tag to compare

@YixuAn-13 YixuAn-13 released this 05 Sep 17:21

本次更新

修复:失败还原不再是单向的

  • 用户点破:v2.1.12 的"失败即还原"是单向的——还原后即使 daemon 恢复,后续消息也一直直连不裁剪,直到手动 on。
  • 现在 user-prompt-submit 新增分支:账本 enabled + daemon 存活 + 零 provider 被接管 → 自动重接管。恢复后的第一轮立即回到代理+裁剪。
  • 防御性检查:端口被非 we-need-ds 进程占用时不会误重接管(daemon 起不来仍走还原直连)。

变更:中途断开透明化

  • 首字节后上游断开/错误透传给客户端:客户端收到真实的 aborted(而非伪造的 502),由客户端 harness 自行决定重试——与真实 harness 面对不稳定上游的语义一致。
  • 首字节前的重试、三段健康门、空 body 检测、5xx 透传全部不变。

测试

  • 三套全绿(test_full 79 + simulation 18 + consume 18),生产文件 md5 前后字节一致;
  • 新增 Phase I(钩子重接管语义 4 项)+ 中途断连 e2e(客户端先收到首块、上游断开后收到真实 aborted)。

v2.1.12 — 死锁根治:恢复失败即还原直连 + 死状态告警

Choose a tag to compare

@YixuAn-13 YixuAn-13 released this 05 Sep 16:14

本次更新

修复:杜绝"端点指向死代理"死锁

  • 用户点破的关键死锁:重启后 provider 残留指向代理端口、daemon 未运行时,连恢复指令本身(经模型端点)都发不出去——鸡生蛋死锁。
  • 失败即还原:所有恢复路径(on / boot / session-start 钩子 / user-prompt-submit 钩子)一旦拉起 daemon 失败,自动把仍指向代理端口的 provider 还原为真实上游直连——失败永远落在可用状态(直连),绝不留在死状态;端口冲突清除后随时可重试 on。
  • 新增 detectDeadState(config):统计指向代理端口的 provider 数。
  • status / doctor 死状态告警:检测到 provider 指向代理端口但 daemon 未运行时,打印醒目告警 + 精确恢复命令。

保留(按用户要求)

  • Claude Code 全部钩子路径(SessionStart/UserPromptSubmit/SessionEnd)保持前几个版本已验证可用的代码不动,仅失败分支加了还原兜底。

测试

  • 三套全绿(test_full 74 + simulation 18 + consume 18),生产文件 md5 前后字节一致;
  • 新增隔离 e2e:检测死状态 → recoverOrphans → provider 回到真实上游、死标记清除。

附:本机故障实测结论

  • 转发机制本身正常(YJS 经 20329 返回 200 真实推理);api.b.ai 系 provider 是上游自身拒绝连接(直连同样 ECONNREFUSED),与插件无关;本地 ollama/litellm 服务未运行。