Repository navigation
Releases: ChenRuoT/dsh-sidebar-qa
Release list
v1.2.1
[1.2.1] - 2026-10-19
修 DSH 0.2.1 平台上的两个 P0:/sidebarqa/api 信任围栏退化(LAN / --trusted-host 恒 403),以及追问面板不再流式、轮次失败没有任何提示。同时把类型桩升到 0.2.1-rc.2、装载闸测试改成「0.2.0 peer / 0.2.1+ engines」两代语义。
Fixed
- 信任围栏迁移到
webStartup(host 半改动,需重启dsh web/ 桌面应用):DSH0.2.1-alpha.1删除了webRuntime服务;src/index.ts仍只读它,回退读到 loader row 里未求值的!!js表达式,Array.isArray判否后围栏静默退化为 loopback-only——dsh web --host 10.0.0.7或--trusted-host app.example的浏览器访问/sidebarqa/api全部 403(config/context/title/catalog一起失效),且没有任何报错。trustedHostsOf现在按webStartup.trustedHosts(0.2.1+)→webRuntime.trustedHosts(旧宿主)→ loader 的id: connection行字面量顺序读;服务存在即以其值为准(空列表表示 loopback-only,不再回退到别的来源)。- 新增
bindHostOf/bindProtocolOf(读webServer.host/.protocol),围栏按上游packages/client/connection/src/api-request-trust.ts接受监听 bind IP、按 http(s) 规范化默认端口、拒绝 cross-site 与不匹配的 Origin。 webServer镜像补host/protocol,webStartup镜像补上出处;webRuntime保留为旧宿主回退。- 回归:新增
tests/trust-fence.spec.ts(17 例)与tests/host-route.spec.ts(9 例,用 fake host 驱动真实apply路由:webStartup 优先、bind IP、旧 face 回退、malformed 值不抛、403/200、Origin 与 cross-site)。
- 追问恢复流式;失败轮次不再永久停在「生成中…」(client 半改动,浏览器硬刷新即可):DSH 0.2.x 的 agent loop 不再写持久化的
assistant/chunk——成功提交assistant/message,失败/中断提交assistant/attempt(含 compactstream),实时增量改为 opt-in 的follow({ assistantStream: true })。插件原实现两者都没接,回答只在结束时整段出现,模型报错时面板没有任何提示。followSession显式传assistantStream: true,转发start/chunk/end帧与重连 snapshot 的assistantStream.activeAttempt。answer.ts新增foldAssistantStreamRecords/assistantAttemptOf/liveStreamOf/applyAssistantStreamFrame,把文本增量、失败原因(finish.reason.failure)与旧格式assistant/chunk统一折叠;assistant/attempt成为带failure的 transcript 行(保留已生成的部分文本)。AskPanel维护 live 行:首 token 起逐段渲染,durable 事件落地后替换;失败轮次显示「本轮回答失败:」。MarkdownText的labels仍按 locale memo。- 新增文案键
askTurnFailed(zh/en);回归:tests/answer.spec.ts(新增 9 例)、tests/session-wire.spec.ts(新增 follow 帧转发用例)。
Changed
- 装载闸叙事与测试对齐 0.2.1 的新语义:DSH
0.2.1-alpha.3+的evaluatePluginCompatibility只要 manifest 有engines.dsh就只按它判、完全忽略 peer(0.2.0 仍只看 peer)。tests/dsh-compat.spec.ts重写为两代闸门(14 例):两代区间都必须是纯下限、反向检测器覆盖「peer 天花板」与「engines 天花板」、engines.dsh存在时忽略 peer、并新增package.json↔dsh.plugin.json的 version /engines.dsh同步守卫。 - README / README_EN / AGENTS / CLAUDE 修正:配置面板是 8 个常用键(schema 共 12 字段);测试规模更新为 30 文件 / 443 用例;
engines.dsh不再是「DSH 不读」的纯声明;安装/升级补充 0.2.1-alpha.2+ 的 remove → add 规则;平台种子词与webStartup的出处同步。 pnpm-workspace.yaml的peerDependencyRules.allowedVersions随桩的 cordis peer 从4.0.4升到4.0.5(与该 checkout 的 vendored cordis 一致)。
Dependencies
- 类型桩精确钉到
0.2.1-rc.2:@deepseek-ai/dsh-client-locale、@deepseek-ai/dsh-client-ui-primitives。 - 构建期打包的 schema 库同步到宿主版本:
@deepseek-ai/schemastery@3.18.5(精确钉,noExternal打进lib/index.js)。 - 升级需重新
pnpm install。
部署提醒:host 半(信任围栏)需重启宿主;client 半(流式/失败行)只需硬刷新浏览器。桌面版请完全退出应用后重启 launcher。
Full Changelog: v1.2.0...v1.2.1
v1.2.0
[1.2.0] - 2026-10-15
修 issue #21「设置保存失败」:配置改为声明在插件自己的 Config schema 上、并按 profile 条目 id 寻址——DSH ≥ 0.1.7(含 0.2.x)上「追问」设置页从「只读默认值」变成可保存。
Fixed
- 设置面板在 DSH ≥ 0.1.7 上可以保存了(host 半改动,需重启
dsh web):dsh-settings@0.1.7-alpha.1起没有register,可编辑字段改为从插件自报的Configschema 派生(volatileForm只认标了volatile()的字段),describe/update接受的是 profile 条目 id(cordis.patch.yml里那一行的id,本插件是sidebar-qa),而不是插件声明的命名空间。1.1.0 的接缝只会注册命名空间,于是在这类宿主上configFace恒为undefined——每一次保存都回 503「this host exposes no sidebarqa settings namespace」(正是 #21 截图里那行红字),面板只能显示默认值。- host 半现在导出
export const Config = SidebarqaPrefsSchema,12 个字段全部volatile();apply(ctx, config)拿到的是每字段一个活引用,新增的src/live-config.ts负责把它读成普通配置(字段缺失或读抛错只回落该字段的默认值,绝不让一次读失败打掉整个路由)。 src/settings-face.ts的接缝现在有两形、互斥:宿主有register(≤ 0.1.6,低于本包声明下限)走原来的命名空间;没有就按条目 id(取ctx.fiber.entry.options.id)打开同一个 revision 守卫的describe/update面。id 取不到、或服务连describe/update都没有时给出带原因的拒绝(no-entry/no-form),503 文案由settingsRefusalMessage按原因生成,不再是一句「可能缺服务、也可能缺注册」的含糊话。- 保存的值经 loader 的 volatile 提交(
updateVolatile/loader/volatile-update)直接写进运行中的引用,插件不重挂;摘要与标题路由下一次调用读到的就是新值。 - 新增
claimOwnSettingsCard:在同代宿主上用settings.configure({ auto: false }, ctx.fiber)抑制宿主按 schema 自动生成的表单——本插件自己已有一整页设置卡(settings.section),同一份配置不该出现两个入口(结构探测;宿主没有该方法时静默跳过,且 disposer 挂在 effect 上随插件卸载)。
- host 半现在导出
- 配置的落点变了(≥ 0.1.7):写在插件自己的 profile 行(
cordis.patch.yml里id: sidebar-qa的config:),不再写settings.yaml的sidebarqa段(那一代宿主本来也没有该命名空间)。老宿主行为与 1.1.0 一字不变。 - 回归:新增
tests/live-config.spec.ts(12 例:活引用识别全有全无、读永远给出完整普通配置、坏引用只降级单个字段);tests/config.spec.ts增加「每个字段必须volatile()、根不可volatile」(漏一个字段 = 宿主表单少一项且写入被Config field "…" is not volatile拒绝)与「解析结果就是运行期能就地更新的活引用」;tests/settings-face.spec.ts重写(21 例)——两形接缝、条目 id 寻址、描述符值解引用(活引用直接上线会JSON.stringify成{},面板全空)、畸形宿主不抛、拒绝文案逐原因唯一,以及接线守卫(profileEntryIdOf(ctx);src/index.ts不得再出现no-register)。 - 实测(本机 checkout DSH
0.2.1-alpha.3,只读):用真实的SettingsForms+volatileForm/isVolatilePath+ loader 的 volatile 提交原语,驱动本插件构建产物的apply与/sidebarqa/api路由处理器(config editor 换成内存假件,不写用户 profile):config.get→ 200 / revision 0 / 12 个字段;config.update→ 200(#21 里这是 503)/ revision 1 / 值落进行 config;活引用立刻读到新值;stale 写入 → 409settings-conflict;未声明键 → 400;非 loopback Host → 403。同一路径上「按命名空间寻址」仍然失败(No configurable plugin entry "sidebarqa"),反向钉住了这个修法的必要性。
Dependencies
schemastery→@deepseek-ai/schemastery@3.18.4(devDependency,精确钉):volatile()只存在于 DSH 的分叉(npmschemastery停在3.18.0),而宿主只读 schema 的meta/dict/toJSON(),所以这份 schemastery 由构建期打包进lib/index.js(tsdown.config.ts的noExternal)——运行期不依赖宿主那一份,也就不会与它漂移。dependencies因此变空(npmschemastery不再被引用)。升级需要重新pnpm install。
Full Changelog: v1.1.0...v1.2.0
v1.1.0
[1.1.0] - 2026-09-29
修复 DSH 0.2.0 上插件整个不加载:peer 区间写成了闸门会拒的「天花板形」,而 DSH 0.2.0 新增了一道启动期 peer 兼容闸。
Fixed
- peer 区间从「^锚点」改成「纯下限」,插件重新能在 0.2.x 上加载(host 半改动,需重启
dsh web):DSH0.2.0的 boot 在装载每个 profile bundle 前先跑packages/boot/app-boot/src/plugin-compatibility.ts的evaluatePluginCompatibility——对 manifest 里每一个@deepseek-ai/dsh/@deepseek-ai/dsh-*peer 区间做semver.satisfies(runtimeVersion, range, { includePrerelease: true })。只要有一个不满足,loadProfileDirectory就把这个 bundle 的整个 patch 层丢掉(skippedBundles),只在 stderr 打一行dsh: skipping profile bundle "…"——host 半不加载、client bundle 不组装,插件像没装一样。不是降级,是消失。- 本插件的 peer 写的是
@deepseek-ai/dsh-client-locale: ^0.1.0-rc.8。带预发布的 caret 不是下限而是「下限 + 天花板」:validRange('^0.1.0-rc.8')=>=0.1.0-rc.8 <0.2.0-0。运行时的0.2.0-rc.1已经越过<0.2.0-0,于是每个 0.2.x 宿主上插件全层被跳过——这正是「升级 0.2.0 后侧边栏追问没了」的样子。 - 现改为纯下限:
@deepseek-ai/dsh-client-locale: >=0.1.0-rc.8、@deepseek-ai/dsh-client-ui-primitives: >=0.1.7-alpha.1。这条修法本身就是新版本生成上线时插件再次消失的通解:不带天花板,任何将来的0.x.y/0.x.y-pre运行时都满足;带天花板的区间(含^落在 0.1.0 预发布上、以及任何>=a <b组合)都只是在预测下一个版本号。 - 豁免通道(各 profile 一个
compatibility.json,dsh plugin allow-version)没有采用:那是给「明知不兼容仍要跑」的逃生门,改手改用户的 profile,不是第三方插件的修法。 - 附带把
engines.dsh从>=0.1.2-alpha.1抬到>=0.1.7-alpha.1(如实反映 1.0.1 起就依赖的SettingsForms世代与 1.0.2 的图标改名世代;DSH 不校验engines,它只是声明)。 dsh.plugin.json的engines.dsh同步更新、version对齐package.json(此前它一直停留在1.0.0;随本次pnpm version minor一起到1.1.0)。
- 本插件的 peer 写的是
Changed
- 类型桩钉到
0.2.0-rc.1世代(dev-only):@deepseek-ai/dsh-client-locale与@deepseek-ai/dsh-client-ui-primitives两个 devDependency 均精确钉0.2.0-rc.1,pnpm-workspace.yaml的peerDependencyRules.allowedVersions随之更新(两个桩 peer 的是@deepseek-ai/cordis@~4.0.4,本包从不链接)。client 半源码零改动(随包发布的lib/client.js/lib/client-registry.js重新构建过一次,内容与 1.0.2 等价)。升级后tsc --noEmit零报错通过——client 半对 0.2.0 API 世代的全部调用(MarkdownText.labels/Tooltip/Menu/iconOf的 7 个 glyph /conversation.input.for(actx).setDraft/locale.register(ns, locale, dict)/sidebarRightTabs三阶段注册)都在新桩上重新校验过一遍。 pnpm-workspace.yaml的minimumReleaseAgeExclude改为作用域通配符(dev-only,装机 prerequisite):pnpm ≥ 11 默认开启 24hminimumReleaseAge供应链接入闸,而@deepseek-ai/*的 rc 发布总是「当天发、当天被本类插件钉上」,name@version形式的旧条目在 pnpm 11 上已不再生效,pnpm install会以ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION直接失败。改为'@deepseek-ai/*'一条(范围仅限 devDependency 的类型桩:不随包发布、运行期不链接、且全部精确钉),旧的 rc.6 条目保留作溯源注释。- 0.2.0 兼容性逐点核对结论(无源码改动):右侧栏类型注册表(
SidebarRightGuideEntry的id+order+ 可选icon、title: (address) => string)、sidebar.right.pane.tab/.title两个 keyed 席位、session-controller的远端动词(rename/fork/prompt/page/follow/create/selectModel/modelCatalog)、session-query的SessionSurfaceSnapshot、llm的GenerateOptions、webserver的WebRoute、WebRuntimeValues.trustedHosts(信任围栏读的那个服务面)、PLATFORM_MODULES九个平台种子词、SettingsForms(无register→ 设置接缝安静降级,与 1.0.1 的行为一致)——v0.1.7-rc.2 → v0.2.0-rc.1两档之间这些文件的源码 diff 为空,仅版本号变化。因此本次没有源码层适配工作,唯一的根因就是 peer 区间。 - 新回归
tests/dsh-compat.spec.ts(7 例):在 node 里逐行复刻 0.2.0 的闸门——(1)插件声明的每一档 DSH 运行时(0.1.7-alpha.1/0.1.7-rc.2/0.2.0-rc.1/0.2.0/0.3.0-rc.1)都必须通过;(2)「旧区间^0.1.0-rc.8在0.2.0-rc.1上必须被判不兼容」这条检测器反向钉住本套测试能抓到这个故障;(3)每个 gated peer 区间不得含<天花板(semver.validRange归一化后不含<);(4)装了 DSH checkout 时直接读它app-boot的版本再跑一遍闸门;(5)每个@deepseek-ai/*devDependency 必须是精确钉(semver.valid)且与node_modules里装好的副本逐字一致。 tests/primitives.spec.ts的桩世代判定改为从装好的桩自身读(Regular/Medium后缀 = 改名后世代,否则为改名前),不再假定「桩一定在旧世代」;两个世代的解析链仍各自全量覆盖。- 新回归在 node 里复刻 DSH 闸门需要同一个 semver 判定器,故 devDependency 增加
semver+@types/semver(仅 tests 使用,不进 client bundle、纯度门不受影响)。 tests/markdown-labels.spec.ts的说明改为指向新的钉子(精确钉 + 纯下限 peer),语义不变。
Full Changelog: v1.0.2...v1.1.0
v1.0.2
[1.0.2] - 2026-09-24
修「打开侧面板就报 面板渲染出错:Minified React error #130 … but got: undefined」:宿主在 0.1.7-alpha.1 重命名了整套图标,而本插件按名 import 的那些 glyph 在运行期就是 undefined。
Fixed
- 宿主图标改为按名解析,不再按名 import(仅 client 半改动,浏览器硬刷新即可):
feat(web): unify the client visual language(首个发布0.1.7-alpha.1)把宿主整套图标从「尺寸后缀」改成「字重后缀」——IconQuestionOutline14/IconCheckOutline16→IconQuestionOutlineRegular/IconQuestionOutlineMedium——且不留任何别名(packages/client/ui-primitives/src/icons/index.tsx里旧名字已彻底删除)。- 症状:面板输入区(
ModelSelect/StrategySelect的 chevron、勾选、警告 glyph)在本插件的 containment 边界内抛错,于是整块面板被替换成那条可重试的崩溃说明条;tab chip 的 glyph 在边界之外(活标题席位)抛错,由宿主的 per-entry 边界接管——代价是整个标题注册被 retire(页面上 tab 标题丢失)。两处都是同一个根因。(那条说明条本身按设计工作:以前这种情况是整块变白且无法恢复。) - 为什么
tsc拦不住:本包的类型桩精确钉在0.1.6-alpha.2(改名前的世代),旧名字在桩里仍然声明;而运行期交给插件的是宿主自己的模块命名空间,于是这些绑定就是undefined——React 对undefined的答复是 error #130(Element type is invalid … but got: undefined,URL 里args[]=undefined&args[]=正是它)。 - 新增
src/client/primitives.ts(纯规则,node 可测):pickExport/iconIn/Glyph——解析链Regular → Medium → <base><legacySize>;「名字在但不是组件」的值会被跳过;三种名字都没有时返回undefined,由Glyph渲染为空(素材缺失是降级,不是故障)。 - 新增
src/client/host-primitives.ts:唯一 value-import@deepseek-ai/dsh-client-ui-primitives的模块,把宿主命名空间绑给上面那条规则(iconOf)。平台模块表在任何 bundle factory 运行前就已冻结,所以在模块作用域读取是安全的——这与 cordis 服务「必须在调用点现场探测」的规则不同。 - 调用点全部改走
iconOf+Glyph:index.tsx(两个 tab 的 glyph)、ModelSelect.tsx(4 个)、StrategySelect.tsx、HistoryPanel.tsx。SidebarTab.icon变成可选;guide 条目在拿不到 glyph 时不写icon键(把宿主的方块占位留给宿主自己画);titleFor经Glyph渲染,绝不createElement(undefined, …)。 MarkdownText/Tooltip/Menu仍是按名 import:它们是有类型的 API 面,宿主跨版本保留;会被随意改名的只有图标素材。- 回归:
tests/primitives.spec.ts(解析链 / 非组件值 / 两种世代 / 接线守卫「只有host-primitives.ts能 value-import 宿主命名空间」「任何 client 文件都不得再按名 importIcon*」/ 装了 DSH 源码 checkout 时直接核对真机导出表);tests/panel-icons.spec.ts(用只有 0.1.7 名字的宿主 mock 渲染StrategySelect——旧代码在这条用例上会抛 #130);tests/sidebar-native.spec.ts新增「宿主没有这个 glyph 时」的注册 + 标题渲染用例。
- 症状:面板输入区(
Full Changelog: v1.0.1...v1.0.2
v1.0.1
[1.0.1] - 2026-09-24
修 issue #19:在 @deepseek-ai/dsh@0.1.7-alpha.1+ 上启动不再打 settings registration failed: TypeError: settingsService.register is not a function。
Fixed
- settings 命名空间改为按结构探测,不再假定
register存在(host 半,需重启dsh web):@deepseek-ai/dsh-settings在 0.1.7 把服务从SettingsProvider(有公开register(ns, schema, options))换成SettingsForms——服务同名、没有register(0.1.7-alpha.1/0.1.7-alpha.2的 npm tarball 逐字节核对过,无兼容入口),可编辑字段改由插件自报的Configschema 派生、describe/update按 profile 条目 id 寻址。此前src/index.ts直接调用settingsService.register(...),在没有该方法的宿主上每次启动抛TypeError;外层catch让它只是降级(回落SIDEBARQA_DEFAULTS、追问链路照常)而不是崩溃,但日志里永远多一行失败。- 新增
src/settings-face.ts:openSettingsSeam(service, ns, schema)用typeof …register === 'function'判定(结构校验,不是版本比较——与 client 半对sidebarRightTabs/slots的规则一致),返回{ ok: true, scope, face }或带原因的拒绝{ ok: false, reason: 'service-missing' | 'no-register' | 'register-failed', error? };探测本身绝不抛(它跑在 cordisinject回调里,抛出即启动期故障)。describe/update也各自探测,并补Array.isArray校验。 - 宿主没有
register时只记一条console.info(说明该宿主把可编辑字段交给插件Configschema、本插件因此走默认值),不再是 warn + 堆栈;只有register真的被拒(命名空间重复等)才 warn。 config.update的 503 文案改为如实描述:原来的「the settings service is not mounted in this deployment」在 0.1.7 上是误导——服务在,缺的是register。src/context-types.ts的SidebarqaSettingsService镜像补上出处与「0.1.7+ 没有register」的说明,避免下一个人再按版本假设这个成员存在。- 0.1.7+ 上设置页仍不可写(回落默认值):保留设置卡需要按新模型重做(导出带 volatile 的
Config并按条目 id 寻址),属于单独一条,不在本次范围。 - 回归用例
tests/settings-face.spec.ts(12 例):0.1.7 形状必须被判成no-register且不触碰describe/update;provider 形状必须转发 ns/schema、读回 value + revision、把expectedRevision透传下去;describe返回非数组 / 无该命名空间 / 缺update都必须降级而不是抛;另有一条接线守卫——src/index.ts必须走openSettingsSeam,且不得再出现settingsService.register。
- 新增
engines.dsh已是>=0.1.2-alpha.1(issue #14 的建议早已落地),#19 里「建议顺带补engines.dsh」那句属过时信息。
Full Changelog: v1.0.0...v1.0.1
v1.0.0
[1.0.0] - 2026-09-20
首个 1.x:只注册进 DSH 自带右侧栏,零额外依赖。 走 major 的原因只有一条——移除了 dsh-better-sidebar 后端(### Removed 一节):升级后不再需要任何 peer 侧边栏基座,装 dsh-sidebar-qa 一个包即可;此前依赖 better-sidebar 的部署请一并卸掉它。
Added
- 只注册进 DSH 自带右侧栏(
@deepseek-ai/dsh-client-ui-sidebar-right,DSH ≥0.1.5-alpha.1):本版本移除dsh-better-sidebar兼容,插件只有一个侧边栏后端,且不需要任何额外依赖。 - 三阶段注册,共用一个实现 id:① 类型进
ctx.sidebarRightTabs.register({ id, kind, priority: 'extension', title, guide })(页面类型:无patterns、无multiple);② body 进 keyed 席位sidebar.right.pane.tab;③ 活的标题进 keyed 席位sidebar.right.pane.tab.title。后两者都是ctx.slots.inject(name, () => ctx.slots.register({ name, key: id }, Component))——席位按实现 id 分发,不是按 kind;打开走ctx.sidebarRight.openTab(kind, { params })。 - 标题席位是新增能力:DSH 的 tab 标题只在打开时刻求值一次并写进布局记录、之后永不改写(上游也明确「没有标题注册时使用打开时捕获的文本」),所以以前已打开的 tab 会冻结在打开时的语言。注册一个组件到标题席位后,它订阅语言 revision 并在原处重渲染——已打开 tab 的标题现在跟随语言切换。宿主不声明该席位时回退为那个冻结字符串(陈旧但无害)。
+(guide)条目仍是可发现性的唯一来源:原生侧边栏的+控件只打开 guide 页,注册的类型靠guide条目暴露成可点胶囊;本插件为两个 tab 各注册一个条目(标题/说明按语言实时读取),新增askGuideDesc/histGuideDesc文案。- 配置面板有了归宿:迁入 DSH 官方设置页。它注册成一个
settings.section(注册逻辑在src/client/settings-slot.ts,纯模块、可测;页面组件src/client/settings-section.tsx渲染ConfigPanel),因此设置页左侧导航新增一整页「追问 / Follow-up」(文案键cfgNavLabel/cfgSectionTitle/cfgSectionDesc),order: 30—— 排在 DSH 自带各页(general 0 / models 10 / plugins 15 / agent-presets 20 / archived-sessions 25)之后。不再有齿轮弹窗。- 注册形态:
ctx.slots.inject('settings.section', () => ctx.slots.register({ name: 'settings.section', id: 'sidebar-qa', order: 30, label: () => t('cfgNavLabel') }, ConfigSection));插槽注册表仍是ctx.get()探测(src/client/slots.ts,与侧边栏模块共用同一个探针)。 - 为什么不是
plugins.item:该席位的契约明文写着它留给ui-settings-plugins那些 host-plane 配置页,并要求 bundle 的配置走plugins.bundle.config;而那个插件页在没有受管 profile 的部署上整页不可用,会把配置入口一起带走。settings.section永远可达。
- 注册形态:
- 新增 client 纯模块
src/client/ask-mode.ts(resolveAskMode:面板视图模式;自meta-quote.ts搬出,后者随 better-sidebar 一起删除)与src/client/show-session.ts(showSessionFirst:用ctx.uiWorkspace.openSession把目标会话切到屏幕上)。 - 回归用例:
tests/sidebar-native.spec.ts(重写)、tests/ask-mode.spec.ts(新增)、tests/settings-section.spec.ts(新增)。
Changed
inject收窄为['sessions', 'remote', 'remote.session', 'workspaces'],betterSidebar不再出现(它此前是硬依赖:任何没装dsh-better-sidebar的部署都会让整个插件不激活)。侧边栏服务sidebarRightTabs/sidebarRight/slots一律ctx.get()探测 + 监听internal/service重探,绝不写进 cordisinject——cordis 没有可选依赖,注入一个宿主不提供的服务会让 fiber 停在 PENDING,而boot-client.ts会让整个 web boot 抛错(白屏,不是降级)。三件套齐备才落定:只有注册表没有席位,tab 会出现但 body 渲染不出来。全都缺失时插件仍激活(浮层 + 「添加到对话」),只记一条console.warn。- 面板不再感知侧边栏:
AskPanel/HistoryPanel删掉了「推 tab 标题」与「面板自愈展开」两个 effect(前者由标题席位承担,后者原生openTab自己会展开栏位),也删掉了onTabActivated订阅(原生路径上本来就是死代码)。面板现在只消费一个中性的「出现实例」SidebarqaTabOccurrence:{ tabId, revision, takeQuote(), openHistory(scope) }(src/context-types.ts)。revision就是navigation.revision——每次被导航到都递增,是「再次打开」唯一的信号(一次外部打开 = 一个新的出现实例,所以引文能重新递送)。 - 跨会话手势统一走
src/client/show-session.ts:原生openTab只作用于已挂载的会话 surface(无 surface 时抛no session surface is mounted),而导航后会话绑定要到下一次 React commit 才由 seat 的useEffect重新发布——同 tick 的openTab会静默打到刚离开的会话。现在先uiWorkspace.openSession切屏,随后用openTabIn(sessionId, …)(DSH 自己 Tab 域的路径,静默但绝不会落错会话)跨帧重发并以active()确认;宿主没有openTabIn时退回跨帧openTab,且检查期间没有更晚的导航接管。 id与kind是同一个SidebarTab对象上的两个字段,打开时从被注册的那个对象读回 kind。「注册ask却打开dsh-sidebar-qa:ask」这类分叉(placeTab会抛no tab type is registered as "…")因此不可表达。engines.dsh仍是>=0.1.2-alpha.1(浏览器侧 RPC 走ctx.remote.session的下限),但 DSH 完全不校验engines,所以它只是声明、不是闸门;真正的前置是侧边栏 tab 需要 DSH ≥0.1.5-alpha.1。更早的 DSH 上插件仍然激活,只是不注册侧边栏 tab。dsh.client.inject(包名依赖边)保留:它是纯信息性的声明,不决定加载顺序,缺失会被静默跳过,不是版本硬约束。src/context-types.ts随之收敛:删掉端口/桥/两后端相关类型,SidebarqaTabComponentProps只保留tab.id+ 可选的sidebar出现实例,新增原生侧边栏、slots、uiWorkspace、webRuntime的服务面镜像并挂 Context augmentation;与上游的漂移仍然只收敛在这一个文件。
Removed
- peer 依赖
dsh-better-sidebar及其peerDependenciesMeta条目(package.json)。 pnpm-workspace.yaml:两条dsh-better-sidebar的minimumReleaseAgeExclude,以及整个allowBuilds块(node-pty/protobufjs只因 better-sidebar 才在依赖图里);pnpm-lock.yaml重生成后二者已完全消失。- 源文件:
src/client/sidebar-port.ts(端口契约 + 后端探测)、sidebar-install.ts(后端选择与安装)、sidebar-better-sidebar.ts(适配器)、ensure-panel.ts(面板收起自愈——原生openTab自己展开栏位)、tab-activation.ts(onActivate桥——navigation.revision取代)、meta-quote.ts(resolveAskMode搬到ask-mode.ts,引文形状校验改在sidebar-native.ts的quoteOfNavParams里)。 - 测试:
tests/sidebar-port.spec.ts、sidebar-seam.spec.ts、sidebar-better-sidebar.spec.ts、ensure-panel.spec.ts、tab-activation.spec.ts、meta-quote.spec.ts。 - 文档:
docs/better-sidebar-expand-feature-request.md(该功能请求随 better-sidebar 一起作废)。 - 随旧后端一起作废的还有 issue #16(PR #17 曾按「
panelOpen缺席 = 原生时代」修过这条路径):那个 bug 出在ensure-panel.ts的宽屏判定上——better-sidebar ≥ 0.19 把右栏交给 DSH 原生 Sidebar 后,插件只剩底部工作台一棵树,而它的activePane恒为底部叶子,于是每次「提问」都会在原生侧栏之外再强行打开底部面板。1.0.0 已不存在任何旧面板自愈(ensure-panel.ts/tab-activation.ts一并删除),该问题在结构上不可能再出现;这段记录是说明为什么不把 #17 的补丁带进来,而不是漏合。
Fixed
- 侧边栏的 tab 图标没了:
+页(guide)的胶囊画的是宿主的方块占位,tab 条上只有裸文字。原生改造(1.0.0 的重构)把图标整个丢了——旧的 better-sidebar 注册里写着icon: (size) => <IconQuestionOutline14 size={size} />(追问)与IconQueueOutline14(追问记录),而新的SidebarTab描述里没有这个字段。宿主的两处放置都会在缺省时回退到别的东西(guide 胶囊 → 自己的方块占位GuideBody.tsx:48,59;tab chip → 裸标题),所以缺图标不是「朴素」,而是别人家的占位符。现修:SidebarTab新增必填icon: ComponentType<IconProps>,index.tsx两个 tab 分别用回IconQuestionOutline14/IconQueueOutline14(宿主自带图标集,走平台模块表,不引入其它特性插件的值)。- guide 胶囊:
guide[].icon直接传该 glyph。 - tab chip:布局记录里的标题只是字符串,图标不可能随标题捕获,所以 glyph 由活标题席位渲染——
titleFor现在返回Fragment(glyph, title)(与宿主自己的 chip 标题GuideTitle.tsx同构),glyph 的样式在新的src/client/tab-title.module.css(flex: none+color: var(--dsw-alias-label-tertiary),与宿主一致)。 - 顺带修掉一处镜像漏声明(同一类:镜像自己就是唯一声明,漏了编译器不会报,运行时静默丢):
context-types.ts的SidebarqaSidebarRightGuideEntry没有icon(上游tab-registry.ts:79),所以即便传了也无处安放。现已补上并注明出处;IconProps直接从宿主包里import type,不再二次镜像。 useLocaleRevision的useSyncExternalStore补上第三个参数(server snapshot):缺它时任何服务端渲染都会抛Missing getServerSnapshot,也让标题席位在 node 环境无法验收。活动语言 id 是稳定原语,正是服务端渲染该读的值。- 回归用例:
tests/sidebar-native.spec.ts(guide 条目必须带 tab 的 glyph;活标题席位必须渲染「glyph 在前、标题在后」)。
- 「全量继承」追问会让面板全白且不可恢复(本轮实测发现,根因是一条上游 prop 改名)。
MarkdownText的本地化文案 prop 从codeLabels改名成了labels(MarkdownLabels = { code: { copyLabel, copiedLabel }, footnotes },改名发生在 DSH0.1.2-alpha.2之前),而宿主渲染器读labels.code.copyLabel时没有任何守卫(ui-primitives/src/markdown/render.tsx:400)。本插件一直在传已退役的codeLabels,于是labels为undefined—— 任何一条含围栏代码块的消息只要被渲染就抛TypeError。这解释了为什么它看起来是「全量继承特有的」:继承会把主导对话的整段历史(写代码的对话,满是代码块)画进面板,而普通追问的回答常常是纯文字。异常逃出席位后被 abdicate(ui-slots/src/index.ts:1541),本插件的 tab body 因此在整页所有会话上被永久摘除——只能刷新。- 现在传
labels(AskPanel的markdownLabels,按 locale memo:MarkdownText以它的引用身份缓存流式渲染),新增文案键mdFootnotes(脚注小节标题),退役的codeLabels从代码里彻底删除。 - 为什么此前
tsc报不出来:类型来自 devDependency 桩@deepseek-ai/dsh-client-ui-primitives,其范围写的是^0.1.0-rc.8,而带预发布的 semver 区间永远匹配不到另一个 patch 的预发布版本(0.1.6-alpha.2不满足^0.1.0-rc.8,也不满足>=0.1.2-alpha.2)——桩因此被永久冻结在改名之前的 API 上,编译器一直拿旧契约校验新代码。现把 devDependency 精确钉到0.1.6-alpha.2(与运行中的 DSH 一致),peer 只声明下限>=0.1.2-alpha.2;pnpm-workspace.yaml新增peerDependencyRules.allowedVersions(该桩 peer 了一个本包从不链接的@deepseek-ai/cordis副本,cordis 由平台模块表提供)。lock 随之去掉旧桩那条重依赖图(katex / micromark / mdast 一族,约 860 行),新桩自身零依赖。 - 回归用例:
tests/markdown-labels.spec.ts直接钉住装好的桩(必须声明labels:、必须不再有codeLabels:属性),把「范围悄悄退回预发布旧版 → 类型检查重新放行」这条路堵死。
- 现在传
- 面板里的任何渲染错误都不再可能让 tab body 整页失效:新增本插件自己的错误边界(
src/client/panel-boundary.tsx,挂在sidebar-native.ts的bodyFor最外层):崩溃被就地渲染成一条可重试的说明条(errPanelCrashed/panelCrashHint/ 复用commonRetry),标签页本身留着;切换会话(resetKey= sessionId)会自动清掉崩溃面。containPanel(resetKey, Panel, props)接收组件而不是现成元素,因此「把面板当普通函数调用」这种会让边界失效的写法(本节第一条修 bug 时我自己就先写成了这样)变成类型错误;测试另有一道 wiring 守卫(tests/panel-boundary.spec.ts:bodyFor必须return containPanel(props.sessionId, tab.component, bodyProps),且不得再出现tab.component(bodyProps))。注:React 的服务端渲染器不咨询错误边界(直接 rethrow),因此containment 在 node 环境以元素结构断言,而非渲染。 - 在「追问」页点到一条已被归档 / 已删除的追问,会让面板内容整块消失、而且再也调不出来(本轮实测发现)。根因是插件自己维护的 localStorage 父子映射不会随 DSH 侧的归档/删除清理:切换条里的陈旧行照旧可点,点下去就是把面板切到一段读不出内容的会话上(正文永远停在「生成中…」);更糟的是这类 id 一旦被拿去导航,
uiWorkspace.openSession会先把它 retain 成主视图,DSH 自己的导航策略随后再把它丢掉(ui-workspace/src/client/navigation.ts:287-301的clearArchivedCurrent→clearMain),主视图与整个右栏一起失效——用户正在读的面板随之消失。三处一起修(均为 client 半):- 切换条逐条分类:新增纯函数
followUpAvailability/isFollowable/lastFollowableFollowUp(src/client/history-scope.ts)。已归档 / 已删除的追问保留可见(映射行还在,DSH 侧恢复归档后立刻可用)但置灰不可点,并标注「已归档 / 已删除」——与「追问记录」的陈旧行同一套语言。面板的默认选中也改为「最新一条可读的追问」,不再默认落到一条读不出的会话上。 - 选中项在阅读时被归档:正文改为明确的说明文案 + 「移除」(只清插件自己的映射,DSH 侧会话不动),输入框、策略、模型座与发送键一并停用。不再有「面板开着却什么都没有、也不知道为什么」的状态。
- 导航入口补围栏:
sidebar-native.ts切屏前先读workspaces.list的archivedSessionIds(isArchivedTarget),命中则只记一条 warn 并放弃这次手势,绝不把已归档会话 retain 成主视图;读不到 archive 集时一律当作「未归档」,围栏不会因为读不到输入而开始拒绝打开。 - 分类特意不复用「追问记录」的
sessionStatus:行标签可以立刻把「feed 里还没有」写成「已删除」,而切换条一旦照此禁用,冷启动(phase !== 'ready')时就会把全部追问锁死。followUpAvailability因此多出一个unknown(feed 尚未 hydrate,按可用来处理)。
- 切换条逐条分类:新增纯函数
- 宿主
useTabInfo抛错会让本插件的 tab body 在整页范围内永久失效。DSH 的 keyed 席位错误边界对崩溃条目的处理是 abdicate(ui-slots/src/index.ts:1541:abdicated.add(entry))——把这条注册从席位上永久摘除,于是所有会话的这个 tab 一起变空白,重新打开也回不来(只能刷新页面),正是「内容完全消失且不能再调出」。而tab-info.ts:35抛的tab "<id>" is not committed in session "<id>"是宿主自己就可能瞬时产生的状态(布局提交与渲染不一致)。现在 body 读取该 h...
v0.5.0
[0.5.0] - 2026-08-28
Added
- 划选浮层由一个按钮拆成两个(issue #11):
- 「添加到对话」(新增):把划选文本以 Markdown
>引用块追加进当前会话的主输入框(composer),聚焦并把光标停在引用块下方的空行——不新建会话、不打开也不展开侧边栏。适合「引用这段话,我就在主对话里接着说」,与开一个独立追问会话互为轻重两条路。 - 「提问」:文案与行为逐字节不变(存 pending 引文 +
openTab({ type: 'dsh-sidebar-qa:ask' }))。之所以没有按 issue 原文改名「侧边聊天」,是因为dsh-better-sidebar0.16.x 自己就有一个叫 Side Chat / 侧边聊天的视图,改名会与上游功能撞车。 - 引用块格式:逐行加
>前缀(选区内的空行渲染成裸>,否则一个引用块会被截断成两个);块尾留一个空行,所以光标落在引用块外面——紧贴> x下一行写字在 Markdown 里属于 lazy continuation,会被吸进引用块里。 - 追加而非覆盖:输入框里已有的内容一个字都不动,引用块空行分隔接在后面;连点两次会追加两个引用块(刻意不做幂等)。
- 走 DSH
ui-conversation的conversation服务(ctx.get('conversation')→input.for(scope).setDraft),与dsh-better-sidebar资源管理器的@文件引用是同一条路径,没有任何 DOM hack。 - 新增纯模块
src/client/quote-draft.ts(引用块格式化 + 草稿合并)与薄接线模块src/client/draft-insert.ts,各自带单测(tests/quote-draft.spec.ts21 例、tests/draft-insert.spec.ts12 例)。
- 「添加到对话」(新增):把划选文本以 Markdown
Changed
src/context-types.ts增补conversation服务的结构化镜像(SidebarqaConversationService/SidebarqaSessionInput)并挂上 Context augmentation——与上游的漂移仍然只收敛在这一个文件里。声明它同时让ctx.get('conversation')返回有类型的服务面而不是any(cordis 把get定义为<K extends string & keyof this>(name: K) => this[K] | undefined)。- 新增文案键
askAddToConversation(zh添加到对话/ enAdd to chat)。askPopoverButton与三条提到「提问」的空状态提示均未改动。
降级契约
conversation 服务缺失(没装 ui-conversation 的部署)、会话 scope 解析不到、或调用链上任意一处抛错 → 「添加到对话」记一条 console.warn 后什么都不做,并且保留划选与浮层,用户可以改点「提问」。插件本身、浮层、以及原有的追问流程都不受影响;conversation 没有进 inject,所以它的缺席不会让插件不激活。
部署提醒:仅 client 半改动,浏览器硬刷新即可,无需重启
dsh web。
已知限制:输入框里已有的@引用 chip 会在这次写入中被降级成纯文本——setDraft是 composer 唯一的公开写入路径,它按纯文本重建整棵节点树(dsh-better-sidebar的appendToDraft有同样的行为)。输入框为空时(最常见路径)完全不受影响。
Full Changelog: v0.4.2...v0.5.0
v0.4.2
[0.4.2] - 2026-08-28
Changed
package.json新增engines.dsh: ">=0.1.2-alpha.1"(issue #14):0.4.1 起浏览器侧 RPC 走 DSH 的 Remote 服务(ctx.remote.session),该服务首次落地于上游dsh-v0.1.2-alpha.1。此前这个前置条件只写在 release notes 里,机器读不到;现在成为清单里的显式声明,供市场 / CLI / CI 在安装前校验(配套提案见 dsh-market#404)。注意这是声明而非闸门:npm / pnpm 的
engines只校验node(及包管理器自身),未知键会被忽略;dsh plugin --profile web add又是 pnpm 的薄转发。真正的安装前拦截需要市场 / CLI 侧消费该字段。在此之前,早于0.1.2-alpha.1的 DSH 上本插件因inject解析不到remote而静默不激活(无 UI、无报错),请留在0.4.0。dsh.plugin.json(plugin-registry 渠道清单)的engines.dsh由>=0.0.1同步为>=0.1.2-alpha.1:该字段此前是一条陈旧声明(写于插件还没有任何宿主版本要求的时候),按 registry 清单做校验的消费方会读到「任何版本都行」,与package.json的声明互相矛盾。两份清单现在给出同一个下限。dsh-better-sidebarpeer 依赖由^0.16.0放宽为>=0.16.0:caret 对0.x只放行同一 minor(>=0.16.0 <0.17.0),而上游迭代快于本插件——better-sidebar 发到0.17.0时本插件会平白无故解析失败,尽管并没有任何已知不兼容。改为只声明下限,若将来某个版本确实破坏兼容再收窄上限。
部署提醒:本版仅清单与依赖声明改动(
engines.dsh、peer 范围),无运行时代码变化;已安装用户无需重启。但早于 DSH0.1.2-alpha.1的宿主上插件会静默不激活(与 0.4.1 行为相同),请留在dsh-sidebar-qa@0.4.0。
Full Changelog: v0.4.1...v0.4.2
v0.4.1
[0.4.1] - 2026-08-28
Fixed
-
版本兼容(issue #12):
dsh-better-sidebarpeer 依赖由^0.14.0调整为^0.16.0,正式声明兼容 0.16.x(本次 Remote 迁移正源于该版本线的上游重构);0.14.x / 0.15.x 不再被 peer 解析。 -
追问面板整块报错
dsh-better-sidebar: Cannot read properties of undefined (reading 'sessions'):DSH 上游把浏览器侧 RPC 从ctx.connection.api迁到了类型化 Remote 服务ctx.remote(refactor(connection): own RPC transport contracts删掉了ConnectionHandle.api;refactor(client): consume migrated Remote namespaces把各命名空间挂成独立 cordis 服务)。本插件的 11 处调用点全部落在那个已消失的对象上,其中ModelSelect的挂载 effect 是同步取值,因此直接被 better-sidebar 的单 tab 错误边界捕获、整块渲染成错误条。现已全量迁移到新接口:旧 新 connection.api.sessions.create / fork / rename / selectModel / promptctx.remote.session.*(信封由{result:{ok,…}}改为扁平{ok,…};prompt新增必填requestId,由客户端生成 UUID v4)connection.api.sessions.models({sessionId})ctx.remote.session.modelCatalog()(Host 级目录,无会话地址)+会话的modelSelectionprojection(当前选择改为订阅而非轮询,主对话或/model改模型会自动同步到座位)connection.api.sessions.history({sessionId})每 1.2s 轮询ctx.remote.session.follow()实时流(开窗 + 增量帧)+session.page()向上翻页。追问回答现在是真流式,不再等轮询节拍 -
inject增补remote/remote.session,并且两个 tab 组件改为接收本插件自己的 ctx(此前用的是 better-sidebar 传下来的 ctx——cordis 只解析各自 plugin 注入过的服务,而 better-sidebar 并未注入remote,沿用会再次拿到 undefined)。
Added
- 新增纯模块
src/client/session-wire.ts(含tests/session-wire.spec.ts,20 例):Remote 信封拆封、requestId生成、把 wire 上打包的 assistant delta run(chunkrow/text-chunks等)还原成逐 seq 的assistant/chunk事件——因此answer.ts的折叠逻辑与其全部单测保持零改动——以及重开 follow 时按 seq 合并窗口(避免覆盖已向上翻页的继承历史)。 - projection 读取抽成共享的
src/client/use-projection.ts,上下文占用环与模型座位共用一条路径。
部署提醒:仅 client 半改动,浏览器硬刷新即可,无需重启
dsh web。
前置条件:本版本要求 DSH 已包含上述 Remote 迁移(0.16.x 系 DSH);在更早的 DSH 上请留在 0.4.0。
已知上游问题:dsh-better-sidebar0.16.1 的 Side Chat 视图仍在调用ctx.connection.api.sessions.history,在同一版 DSH 上打开该 tab 会报同样的错——那是上游代码,不在本插件范围内。
Full Changelog: v0.4.0...v0.4.1
v0.4.0
[0.4.0] - 2026-08-21
Added
- 中英双语(i18n):界面文案与模型侧提示词都跟随 DSH 的语言设置(
locale.preference> 浏览器语言 > en)实时切换,无需刷新。- 界面词表在
src/client/locales.ts(zh 为键集基准,en 由Record<CopyKey, string>类型标注锁定;单测另有键集平价、空值与占位符一致性检查),并通过ctx.locale注册进 DSH 的sidebarQa命名空间。locale 服务为软依赖:缺失时插件照常工作,回退浏览器语言。 - 模型侧词表在
src/prompt-locale.ts:追问引导语、上下文压缩与标题系统提示,以及提示词指名引用的结构标记(用户:/助手:、【背景】、问题:)作为一个原子单元同进同退(单测断言这一配对)。 - 已打开的 tab 标题同样跟随切换:better-sidebar 的 tab 标题是持久化定值,面板改为在语言变化时回推
updateTab自愈。
- 界面词表在
- 明确的回答语言策略:提示词现在显式要求「用与用户提问相同的语言作答;问题语言不明确时跟随划选文本」——回答语言跟内容走,不跟界面语言走(与 DSH 官方 session-title 同款策略)。此前得到中文回答只是「提示词恰好是中文」的副作用。
Changed
- 追问会话标题去掉「追问」二字:
❓追问·<主题>→❓<主题>。emoji 本身已足够标记追问会话,去掉后该处不再需要翻译,也不会因切换语言而出现混合语言前缀。已有会话的标题不会被追溯重命名。 - 占位主题的截断预算按文种自适应:含 CJK 取 12 字,纯拉丁取 24 字(
TOPIC_MAX_LEN/TOPIC_MAX_LEN_LATIN)——12 个拉丁字符只有两三个词,此前会截在词中间。判定依据是被划选文本的文种,不是界面语言。 /sidebarqa/api/context与/sidebarqa/api/title新增可选locale字段('zh' | 'en'):host 侧没有语言信号,由 client 下发当前 DSH 语言。字段缺省等价于zh,旧版 client 打到新 host 行为与今日逐字节一致;无法识别的语言一律落 en,永不抛错。压缩摘要缓存键加入 locale 维度,切换语言后不会继续命中旧语言的摘要。- 面板内的宿主错误提示改为「本地化前缀 + 原始英文详情」(追问失败 / 保存失败 / 模型加载失败),此前直接裸露英文原文。
Fixed
- 追问记录的相对时间标签此前恒为中文;现在跟随语言(en 使用
5m/3h/2d等紧凑缩写,规避复数形态)。
部署提醒:host 半与 client 半都有改动——需重启
dsh web,再硬刷新浏览器。只刷新不重启会短暂得到「英文引导语 + 中文压缩提示词」的混合状态(功能正常,仅提示词语言不一致)。
已知外观差异:dsh-better-sidebar 插件目录中对本插件的描述仍写作❓追问,属上游只读文案,不影响功能。
Full Changelog: v0.3.2...v0.4.0