Skip to content

(feat) support the DSH 0.1.7 seam line — SettingsForms settings integration + verified 0.1.5–0.1.7 peer band - #180

Open
ranxianglei wants to merge 4 commits into
mainfrom
2026-09-24_dsh-017-seam-line
Open

ranxianglei wants to merge 4 commits into
mainfrom
2026-09-24_dsh-017-seam-line

Conversation

@ranxianglei

Copy link
Copy Markdown
Collaborator

Problem

DSH 0.1.7 removed dsh-settings.installSection (service class renamed SettingsProvider → SettingsForms). After the #173 fix, hosts on the 0.1.7 line degrade cleanly but lose runtime hot-tuning of the six /acp-prune config knobs (composition row + restart only), with a startup warn. This PR brings the 0.1.7 line formally into the supported range.

Cause

The settings integration spoke only the ≤0.1.6 dialect (installSection(owner, ns, schema, entry, hooks)). On 0.1.7 the service exposes configure/describe/update/replace/mutate, addressed by profile ENTRY ID, with editable fields gated by meta.volatile schema annotations; ~/.dsh/settings.yaml no longer exists (renamed .imported at startup, imported through LEGACY_SECTION_ENTRIES, which maps only ui-developer-tools / ui-onboarding / shell).

Fix

  • Dual-dialect capability probe (src/index.ts inject callback): installSection present → ≤0.1.6 legacy path (unchanged); describe/update/replace present → ≥0.1.7 forms path; neither → [bug] DSH 0.1.7 移除 dsh-settings.installSection 后 ACP 设置节注册抛 TypeError(启动 error) #173 single-warn degrade.
  • Forms branch: addressed by profile entry id compaction-acp (the bundle patch row); the six knobs appear in the form because the static plugin Config schema annotates them .volatile(); writes go through update() with SettingsConflictError revision handling; service methods bound to the receiver (host methods read state via this).
  • Hot-apply driver: form writes commit into our fiber config's volatile refs between steps and emit NO event — the engine re-reads them each agent/pre-step via resyncSettings() (before the autoNudge gate, so a just-committed change applies to the current step).
  • Six keys' carrier on 0.1.7: composition row + volatile refs (settings.yaml is gone; legacy import falls back to the same-name entry id for unmapped sections) — documented in docs/settings-integration-design.md §4.9.
  • Seam verification before widening (house rule): five seams × three lines verified against published npm artifacts; evidence recorded in docs/dsh-porting-verification.md (2026-09-24 entry). 0.1.6 shows no drift vs 0.1.5 → admitted. dsh-llm-deepseek 0.1.7 switched adapter protocol (OpenAI chat-completions SSE → DeepSeek Messages typed SSE) → e2e fake LLM rewritten and wire-level checks adapted (docs/e2e-harness-design.md log).
  • Peer band explicitly widened to >=0.1.5-alpha.1 <0.1.8-0 (never caret; 0.1.8-0 sorts ahead of any 0.1.8-x prerelease, so the next line stays rejected until verified); devDep baseline moved to 0.1.7-rc.1 (43 dsh-* packages); tests/peer-range.test.ts re-pinned for the three lines.

Behavior changes (old → new)

  • 0.1.7 hosts: startup warn + composition-row-only tuning → fully supported, no warn, six knobs hot-tunable at runtime via /acp-prune config.
  • 0.1.5 / 0.1.6 hosts: unchanged (installSection path kept verbatim).
  • Out-of-band hosts: unchanged (single-warn degrade preserved).

Verification

  • Seam artifacts verified per house rule (docs/dsh-porting-verification.md, 2026-09-24 entry).
  • Full suite 367/367 (incl. re-pinned peer-range accept/reject for all three lines, SettingsForms fixture with real volatile-ref reuse, dual-shape checkpoint fixtures).
  • e2e: 4 scenarios green against the real DSH host stack on 0.1.7-rc.1 (fake LLM speaks the new DeepSeek Messages protocol).
  • typecheck clean; build clean.

Closes #174

@ranxianglei

Copy link
Copy Markdown
Collaborator Author

🤖 Powered by ework

收到,开始审这个 PR(不重做,按 [PR] 流程核对代码 + CI)。先报初步状态:

拓扑核验

已发现的阻塞项(初判)

接下来做完整代码审查(②回归审查 + ③代码审查 + ④测试有效性 + ⑤diff 整洁度),结论回帖。

ework-agent added 3 commits September 27, 2026 14:11
…y rejected every 0.1.6/0.1.7 prerelease

The single clause '>=0.1.5-alpha.1 <0.1.8-0' looked like it admitted three verified lines, but node-semver compares same-tuple prereleases lexicographically against the bare upper bound (0.1.8 < 0.1.8-0), so every 0.1.6-x / 0.1.7-x prerelease fails the range. npm install against those hosts would have hit ERESOLVE peer conflicts — exactly what the band exists to prevent. Split into one clause per verified line; each clause's upper bound sorts ahead of its own line's prereleases and behind the next line's first prerelease. Accept fixtures now mirror the full published version set per line (verified against the npm registry).
…hot-apply driver regression

The union resolution of tests/settings.test.ts during the rebase dropped the closing 'finally' block of both issue #176 preset tests (file no longer parsed). Restored verbatim from origin/main and pointed them at the worktree's LegacySettingsProvider fixture (main's MemorySettingsProvider stands in for the dsh-settings runtime class, which the 0.1.7 baseline does not ship). Also adds the missing end-to-end regression for the 0.1.7 hot-tune loop: a SettingsForms write that the engine picks up on the NEXT agent/pre-step through resyncSettings, asserted by the threshold-inversion warn the engine logs when applySettings sees min >= max.
@ranxianglei
ranxianglei force-pushed the 2026-09-24_dsh-017-seam-line branch from e7e92b8 to 86926ab Compare September 27, 2026 06:37
@ranxianglei

ranxianglei commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 Review complete — verdict: mergeable, after fixing two issues I found in review (one severe) and rebasing onto current main. All local gates and CI are green on the updated head.

① Issue & direction (#174)

Direction is sound and hits the root cause: 0.1.7 removed installSection, so the settings integration must learn the SettingsForms dialect (entry-id addressing, volatile refs, describe/update/replace). Dual-dialect probe order (legacy → forms → single-warn degrade) keeps every existing host class working. Seam verification followed the house rule (docs/dsh-porting-verification.md, 2026-09-24 entry) — evidence checks out.

② Regression audit (pre-existing behavior on touched paths)

  • 0.1.5 / 0.1.6 installSection path: kept verbatim — probe order and legacy-branch semantics unchanged → no drift.
  • Out-of-band hosts: single-warn degrade preserved → no drift.
  • Detach fallback () => current → () => initialCurrent (construction-time snapshot): deliberate, documented in-code (legacy providers reset their source to the registered entry on dispose anyway; on the forms line last-applied values may no longer be editable). Disclosed, accepted.
  • toolCallId extraction content[0].toolCallId ?? source.callId → message.toolCallId ?? source.callId: 0.1.7 promotes tool results to role:'tool' messages carrying toolCallId at message level; on older lines the field is absent at runtime and the fallback holds → consistent across all three lines.
  • High-risk pattern sweep (fail-fast → swallow, threshold/timing/output-format drift, cache/retry silent invalidation): none found.

③ Issues found — fixed directly on the PR branch

  1. [SEVERE] The peer band's actual admit-set ≠ its claimed admit-set. >=0.1.5-alpha.1 <0.1.8-0 looks like three lines, but node-semver compares same-tuple prereleases lexicographically against the bare upper bound (0.1.8 < 0.1.8-0), so every 0.1.6-x / 0.1.7-x prerelease was rejected outright — npm install would hit ERESOLVE peer conflicts on exactly the hosts this PR exists to support (verified empirically, version by version). Split into one clause per verified line:

    • old: >=0.1.5-alpha.1 <0.1.8-0 (actually admitted only the 0.1.5 line + bare finals)
    • new: >=0.1.5-alpha.1 <0.1.6-0 || >=0.1.6-alpha.1 <0.1.7-0 || >=0.1.7-alpha.1 <0.1.8-0 (each upper bound sorts ahead of its own line's prereleases, behind the next line's first prerelease)

    tests/peer-range.test.ts accept fixtures now mirror the complete published version set per line (checked against the npm registry). Behavior-change disclosure: the admitted version set expands to include the 0.1.6/0.1.7 prereleases that were silently rejected before — which is exactly what the PR claims to do, now actually true.

  2. [GAP] No end-to-end regression for the 0.1.7 hot-tune loop. Existing forms tests proved the env getters read live refs, but nothing exercised the PR's central mechanism: a committed form write being picked up by resyncSettings() on the next agent/pre-step. Added a driver test: SettingsForms.update({min: 0.9, max: 0.4}) → fire a real pre-step → assert the engine logs the threshold-inversion warn (nudgeMinContextLimitPct (0.9) >= nudgeMaxContextLimitPct (0.4)) and the env reflects the new values.

  3. e2e fake LLM missing the error turn kind. The overflow-recovery scenario scripts a provider failure, but the rewritten fake-llm only dispatched text/tool turns — an error turn would misroute as a tool call. Added the error branch (HTTP 400 + DeepSeek context-length overflow wording; verified dsh-llm-deepseek 0.1.7-rc.1 normalizes that exact wording to CONTEXT_WINDOW_EXCEEDED before the loop retries).

④ Rebase (was required)

The branch was based on v0.2.25; main moved to v0.2.26 meanwhile (#176 preset-fill fix touches the same settings layer). A straight merge would have produced conflicting, contradictory settings wiring. Rebased onto 241aaf2; resolved 5 conflicts keeping BOTH features coherent — #176's presetFilledSettingsEntry(config) runs first, then #174's unwrap: initialCurrent = resolveAcpSettings(liveSettingsFromRefs(presetFilledSettingsEntry(config))). One self-inflicted scratch during resolution: the union of tests/settings.test.ts dropped the closing finally blocks of both #176 tests (file stopped parsing); restored verbatim from origin/main and pointed them at the worktree's LegacySettingsProvider fixture (main's MemorySettingsProvider subclasses the dsh-settings runtime class, which the 0.1.7 baseline doesn't ship).

⑤ Test validity & diff cleanliness

Assertions have teeth (real meter/registry E2E, revision tracking, dual-shape checkpoint fixtures, wire-level byte-stability); the diff stays on-purpose — no stray files or churn.

Verification (local, head 86926ab): typecheck clean · 385/385 unit · build clean · e2e 5/5 scenarios green (incl. overflow-recovery against the real 0.1.7-rc.1 host stack). CI: ci / pr-title / e2e all green on 86926ab.

Branch 2026-09-24_dsh-017-seam-line @ 86926ab = rebased feat + (fix) peer-band + (test) repair/driver commits. Merge is yours: #180

中文摘要:审查了 DSH 0.1.7 seam 支持 PR——方向与代码正确,但发现并直接修复了一个严重问题(peer band 因 node-semver 同元组预发布规则实际拒绝了全部 0.1.6/0.1.7 预发布版本,与 PR 声称的准入集不符,已拆分为逐行子句)、补上了缺失的 forms 热更新端到端回归测试和 e2e error turn kind,并把分支 rebase 到最新 main(保留 #176 预设填充语义);本地全部门禁与 CI 均绿,可以合并。

@Lion-Li-git

Copy link
Copy Markdown

field report from a real 0.1.7 host — pre-merge evidence that the widened band is needed, plus one question about existing workarounds.

环境:DSH Desktop 2.0.14 (stable) · dsh 0.1.7-rc.1 · Windows 11 x64 · profile desktop · billion-context-dsh@0.2.26(= 当前 npm latest,也是本 PR 的 base commit 241aaf2)。

1. 未豁免时,用户看到的是"装了但完全不工作"

profile 启动期整包跳过,且没有任何 UI 状态区分"待重启"与"被拒绝"——用户会反复重启而不会好转:

dsh: skipping profile bundle "billion-context-dsh": Error: Plugin billion-context-dsh@0.2.26 is
incompatible with dsh 0.1.7-rc.1: peerDependencies {"@deepseek-ai/dsh-compaction":">=0.1.5-alpha.1 <0.1.6-0", ...}
... grant the exact-version exemption ... with `dsh plugin allow-version` ... Exact-version exemption: not active.
dsh: [cordis.patch.yml] patch: entry "compaction-acp" not found

第二行值得注意:被跳过不只是"引擎没加载",还会让用户已经写好的 compaction-acp 组合行静默失效(entry not found 只进日志,不进 UI)。在 Desktop 2.0.14 上这就是一个社区插件把另一份用户配置一起打没的例子。

2. 豁免之后,#173/#175 的降级路径在真机上表现与描述一致

dsh: allowed billion-context-dsh@0.2.26 for DSH 0.1.7-rc.1

重启后:

[W] [acp-compaction-engine] billion-context-dsh: host settings service has no installSection
    (removed in dsh-settings >= 0.1.7) — the compaction-acp settings section is not registered;
    the six knobs keep their composition values and /acp-prune config reports the section unavailable

引擎本体与四个工具正常挂载,无 ERR_MODULE_NOT_FOUND、无 TypeError。也就是说 0.1.7 线上的"不做任何事"路径是干净的,这与 PR 描述一致。

3. 为什么豁免不能当作长期方案(支持尽快合本 PR)

豁免是 exact package@version × exact runtime version × per-profile 三元组。后果:

  • 0.2.26 → 0.2.27 之后豁免自动失效,用户要每次升级重新批一次,而界面上只会继续显示"已安装,未生效";
  • 提示语里那句 "can break the application or corrupt data" 会让用户以为这是真不兼容,从而放弃插件而不是去批——一个纯元数据问题被表述成了数据风险;
  • 同一条门禁在这个 profile 上同时拦着两个插件(另一个是 @wingsky-1/dsh-notifier@0.2.5,它的 peer 精确钉 0.1.5-rc.1),所以用户是在被要求逐个做风险决策,而不是装一次就完。

本 PR 把区间显式写成 >=0.1.5-alpha.1 <0.1.8-0(并说明为什么不用 caret)正好解决这一类问题:下一个线仍然默认拒绝,但当前线不再需要任何手工动作。

4. 一个关于既有 workaround 的问题

在 #180 之前,被跳过的用户里有一部分是直接在 profile 的 cordis.patch.yml 里手写 compaction-acp 行来调阈值继续用的,典型形状:

- id: compaction-acp
  config:
    modelContextLimit: 1000000
    nudgeMaxContextLimitPct: 0.85
    nudgeEmergencyThresholdPct: 0.95
    coreOverrides:
      nudge: { growthFloor: 200000, growthCap: 250000, growthRatio: 0.2 }

我按 dsh-settings 的写路径核了一下(SettingsForms.write → mergeLayers(strip(raw, form), next),strip() 只摘掉表单拥有的路径),结论是表单接管六键之后不会冲掉这类行里非表单的键,所以升级路径是安全的 —— 提这一点是因为这是用户最担心的事,PR 描述里没有覆盖,值得在 release note 里写一句。

真正想问的是:coreOverrides 在 #180 的 schema 里算不算 volatile?

  • 若不算:这些用户的 coreOverrides 会永远留在组合行上,而表单里的六键与它同处一行。这本身没问题(strip() 保住非表单键),但一旦有人为了"清理"删掉整行,coreOverrides 会一起消失,而表单不会重新写回它。
  • 若算:validatePaths 允许写,但 coreOverrides 是嵌套对象,mutate() 的 applyPathOp 对嵌套 dict 的 unset 语义是否覆盖 coreOverrides.nudge.growthFloor 这种深度,可能值得补一条测试。

不管哪种答案,都不阻塞本 PR —— 只是想知道你们打算把这条边界放在哪,好让 release note 对"已有手写组合行的用户"给一句准确的说法。

5. 我没有验证的部分(避免被当成测试证据)

如果需要在合并前补一份真机数据(Desktop 2.0.14 / Windows / 1M 窗口长会话),我可以在这个 issue 下跑一轮再回报。

@ranxianglei

ranxianglei commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 感谢真机报告——第 1–3 点正是本 PR 要消除的失败模式(第 2 点的 warn 文案与 #173 降级路径逐字一致,交叉印证了豁免后的干净降级)。第 4 点的问题用代码 + 钉住产物直接回答:

coreOverrides 不是 volatile——它根本不在设置 schema 里

  • src/settings.ts:19-23(模块头):Deliberately NOT exposed through settings: coreOverrides, countTokens, autoTools, autoCommand, prompts…
  • SETTINGS_KEYS(src/settings.ts:39)= 恰好六个标量键;AcpPluginConfigSchema(src/settings.ts:200-205)只声明这六个字段、每个带 .volatile()。非严格校验只是让未声明行在 fiber 启动期原样通过,不等于它们进了表单。

所以你问题里的「若算」分支不成立:isVolatilePath(schema, ['coreOverrides', …]) 在任何深度都是 false,而 dsh-settings 的 write() 对 mutate 的 op 路径有前置检查(非 volatile 路径直接抛 Config field "<path>" is not volatile),update/replace 载荷也过 validatePaths 同关——forms API 根本寻址不到 coreOverrides.nudge.growthFloor,我们这边没有可补的测试面。

你的 strip() 读法是对的——且已对钉住产物核验(不依赖源码目测)

对 @deepseek-ai/dsh-settings@0.1.7-rc.1 的 lib/index.js 逐行核过:update / replace / mutate 全部以 mergeLayers(strip(raw, form), next) 收尾(write() :534):

  • strip(raw, form) 只从 entry 当前 raw config 摘掉 volatile 拥有的路径 → coreOverrides / preset / prompts 原样保留;
  • next 只含 volatile 路径内容(projectForm,:141);
  • mergeLayers(under, over)(:281)递归合并,且 over 层永不携带 undefined 条目(注释原话:"a sparse patch cannot erase lower keys")。

因此本引擎三种写形都保住手写行上的非表单键:/acp-prune config set <key>(单键 update patch)、单键 reset(我们本来就把 section 其余键逐字透传,src/commands.ts:348)、甚至 reset all → replace({})——它只清掉该行上的 volatile 值,coreOverrides 留在 strip(raw) 里。升级路径安全这一结论现在是产物级证据,不是推断。

边界放在哪 + release note 建议措辞

边界:forms 层恰好拥有那六个 .volatile() 标量键;compaction-acp 行上的其余成员是普通组合配置,引擎既不经 settings 读也不经 settings 写。 唯一破坏性操作是手工删整行——coreOverrides 随之消失且无人回写;这一点在三条线上行为完全一致(≤0.1.6 六键在 settings.yaml、组合行独立存在;0.1.7 六键落到同一行),不是 #180 引入的变化。建议 release note 一句(中英各一版,中文版供 README.zh 用):

EN: "Hand-written compaction-acp rows keep working after upgrade: the runtime form owns only the six threshold/on-off knobs and preserves every other key on the row (coreOverrides, preset, prompts). Deleting the row by hand also deletes those keys — keep them if you rely on them."

ZH: "手写的 compaction-acp 组合行升级后继续有效:运行时表单只拥有六个阈值/开关旋钮,行上其余键(coreOverrides、preset、prompts)原样保留;但手工删除整行会连这些键一起删掉且不会回写——如依赖它们请保留该行。"

这条边界连同上述产物级核验已写入 docs/settings-integration-design.md §4.9(PR 分支新 commit 545718f,docs-only),release note 可直接引用该节。

关于第 5 点:你的报告按「已发布 0.2.26 + 豁免」的真机状态算作合并前旁证是准确的定位;分支侧验证维持此前报告(385/385 单元 + e2e 5/5,真实 0.1.7-rc.1 宿主栈)。如果你愿意在 Desktop 2.0.14 上补一轮真机数据,欢迎回报——但不阻塞合并。

中文摘要:回答了手写组合行中 coreOverrides 的归属问题——它不在设置 schema 内(非 volatile),并对钉住的 dsh-settings 0.1.7-rc.1 产物逐行核验确认所有 forms 写形都保留行上非表单键(含 reset all),唯一风险是手工删整行且三线上行为一致、非本 PR 引入;边界与 release note 措辞已定并写入设计文档 §4.9,不阻塞合并。

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 支持 DSH 0.1.7 接缝线:settings 集成从 installSection 迁移到 SettingsForms

2 participants