dsh-sidecard-ask 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.0
4
+
5
+ **主题:核对并加固与 dsh-better-sidebar 0.22.1 的适配。**
6
+
7
+ 核对方式(可复现):把 0.22.0 与 0.22.1 的发布产物都拉下来解包,逐文件 SHA256 比对。结论:
8
+
9
+ - 消费端契约 `lib/types/client/service.d.ts` **除 `SIDEBAR_SERVICE_VERSION` 常量外逐字节相同**;
10
+ `SIDEBAR_FEATURES`、`dsh.client.inject`、peer 依赖均未变;
11
+ - 0.22.1 唯一的大体量改动是它**自己** `src/client/native/index.ts` 的健壮性修复(`disposeSafely` +
12
+ 槽位注册失败时回滚已注册的 tab 类型),不涉及本插件调用的任何字段;
13
+ - 本插件用到的 `registerTab / openTab(seed, scope) / closeTab / features / version` 全部仍在。
14
+
15
+ 即 **0.22.1 下无需改动即兼容**。据此做了三处加固:
16
+
17
+ 1. **按能力而非版本判断**:适配器仅在 `features` 含 `tabMeta` 时可用(该能力自 better-sidebar 0.12 起提供,
18
+ 更早的版本会打开一个读不到 `meta.cardId` 的空 tab);不满足时 `auto` 跳到原生右侧栏/内置浮层,
19
+ 并在自检里给出 `no-tab-meta`。
20
+ 2. **原生承载面注册回滚**:先注册 tab 类型再注册槽位,槽位注册抛错时释放已占用的类型 id
21
+ (正是 0.22.1 在自己原生胶水里修的同类问题,否则该 kind 会永久占用、渲染宿主"无实现"的空面)。
22
+ 3. **运行时可见**:设置页「运行自检」新增一行,显示侧边卡片插件**自己报告的 `version` 与能力项数**,
23
+ 以及本插件对它的判定(可用 / 版本过旧 / 未检测到)。
24
+
25
+ 自测 276 → **289 项**(verify 53 / contract 118 / smoke 118),新增用例覆盖:0.22.1 形状的服务被接受、
26
+ `openTab(seed, scope)` 的字段与作用域、缺 `tabMeta` 时被拒绝并落到浮层、缺 `registerTab` 时被拒绝、
27
+ 槽位注册失败时类型被释放且适配器转为不可用。
28
+
3
29
  ## 1.1.0
4
30
 
5
31
  **改名:`dsh-selection-followup` → `dsh-sidecard-ask`**(显示名「划词追问」→「侧边卡片追问」)。
package/README.md CHANGED
@@ -152,14 +152,37 @@ plugin_manager(action="install_bundle", target="D:\\Users\\34332\\AI\\dsh-sideca
152
152
 
153
153
  | 顺序 | 承载面 | 依赖 | 失败时 |
154
154
  |---|---|---|---|
155
- | 1 | DSH 原生右侧栏 | `ctx.sidebarRightTabs`(注册 tab 类型)+ `ctx.sidebarRight`(`openTab`),并占用槽位 `sidebar.right.pane.tab` / `…tab.title` | 记 `surfaces.native.error`,落到下一层 |
156
- | 2 | **dsh-better-sidebar**(侧边卡片插件) | 客户端服务 `ctx.betterSidebar`:`registerTab` + `openTab(seed, scope)`;卡片 id 走 `tab.meta.cardId`(依赖其 `features` 含 `tabMeta`) | 同上 |
155
+ | 1 | DSH 原生右侧栏 | `ctx.sidebarRightTabs`(注册 tab 类型)+ `ctx.sidebarRight`(`openTab`),并占用槽位 `sidebar.right.pane.tab` / `…tab.title` | 记 `surfaces.native.error`,落到下一层;**类型注册会回滚**(见下) |
156
+ | 2 | **dsh-better-sidebar**(侧边卡片插件) | 客户端服务 `ctx.betterSidebar`:`registerTab` + `openTab(seed, scope)`;卡片 id 走 `tab.meta.cardId`,因此**要求其 `features` 含 `tabMeta`** | 同上(缺 `tabMeta` 时记 `no-tab-meta` 并跳过,而不是开一个空 tab) |
157
157
  | 3 | **内置浮层卡片**(默认兜底) | 只需要 `shell.overlay` 槽位 | ——(这是保底面,永远可用) |
158
158
 
159
159
  - 装了 `dsh-better-sidebar`:卡片以它的 tab 形式出现在它的面板里,关闭卡片会同时 `closeTab`,不残留空 tab。
160
160
  - 没装:自动使用内置浮层卡片(右下角卡片栈),功能完全一致——**这条路径是本插件的默认与保底路径**。
161
161
  - 强制指定了一个不可用的承载面:回退到内置浮层,并在卡片上标注「已回退」。
162
162
 
163
+ ### 5.1 与 dsh-better-sidebar 0.22.1 的适配核对(2026-09-28)
164
+
165
+ 做法:把 0.22.0 与 0.22.1 的发布产物都拉下来、解包、逐文件比对(`npm pack` + SHA256)。
166
+
167
+ | 检查项 | 结果 |
168
+ |---|---|
169
+ | 消费端契约 `lib/types/client/service.d.ts` | **除 `SIDEBAR_SERVICE_VERSION` 常量外逐字节相同** → `BetterSidebarService` 的方法/参数/返回类型没变 |
170
+ | `SIDEBAR_FEATURES`(能力清单) | **未变**(`tabMeta` 等仍在;契约承诺"Features are never removed") |
171
+ | `dsh.client.inject` / peer 依赖 | 未变(仍是 locale / ui-slots / ui-conversation / ui-sidebar-right / client-modules) |
172
+ | `src/client/native/index.ts`(0.22.1 唯一大体量改动) | 是它**自己原生胶水的健壮性修复**:`disposeSafely` + 槽位注册失败时回滚已注册的 tab 类型;不涉及我们调用的任何字段 |
173
+ | 本插件实际用到的面 | `registerTab({id,title,description,order,dedupeKey,component})`、`openTab({type,id,title,meta},{sessionId})`、`closeTab(id)`、`features`、`version` —— 全部仍在 |
174
+
175
+ **因此 0.22.1 下适配为「无需改动即兼容」**;本轮据此做了三处加固:
176
+
177
+ 1. **能力门控而非版本判断**:适配器只在 `features` 含 `tabMeta` 时可用(0.12 起才有,旧版会开出空 tab);
178
+ 不满足时 `auto` 直接跳到原生右侧栏/内置浮层,设置页自检里写明原因(`no-tab-meta`)。
179
+ 2. **原生承载面类型注册回滚**:先注册 tab 类型、再注册槽位;槽位注册抛错(reload/teardown 期的失活 context)时
180
+ **释放已占用的类型 id**——这正是 0.22.1 在自己原生胶水里修的同一类问题(否则该 kind 会永久占用并显示宿主"没有实现"的空面)。
181
+ 3. **运行时可见**:设置页「运行自检」新增一行,直接显示**侧边卡片插件自己的 `version` 与能力项数**,
182
+ 以及本插件对它的判定(可用 / 版本过旧 / 未检测到),不再依赖 package.json 的声明。
183
+
184
+ > 注意:`version` 是加载到浏览器里的那份模块报告的版本。页面刷新后即可用它确认"跑的是不是 0.22.1"。
185
+
163
186
  ---
164
187
 
165
188
  ## 六、未知 DSH API:占位接口与替换方式
@@ -410,14 +433,27 @@ Host: ctx.subagents.start('spawn')
410
433
  ## 十三、发布流程
411
434
 
412
435
  ```powershell
413
- npm whoami # 先确认登录态(改过 2FA/密码会让旧 token 失效)
436
+ npm whoami # 先确认登录态;本机用的是 granular Publish token
414
437
  node test/verify.mjs; node test/contract-test.mjs; node test/smoke-test.mjs
415
- npm publish --access public
438
+ npm publish --access public # 1.0.1 那次被 npm 暂存(staged)后才转正;再遇到可用 --otp=<验证码>
439
+ git push origin main # 仓库:https://github.com/HERO476/dsh-sidecard-ask
440
+ ```
441
+
442
+ 改名/迁移时的额外两步(1.1.0 实际做过):
443
+
444
+ ```powershell
445
+ plugin_manager(action="remove_bundle", target="<旧包名>") # 卸旧 bundle(同时移除 profile 里的 link)
446
+ plugin_manager(action="install_bundle", target="D:\...\<新目录>")
447
+ npm deprecate "<旧包名>@*" "Renamed to <新包名> - <原因>" # 旧名指路,避免别人再装到废弃名
416
448
  ```
417
449
 
418
450
  版本号同时出现在三处,必须一致:`package.json` 的 `version`、`index.js` 的 `PLUGIN_VERSION`、`client.js` 的 `api.version`
419
451
  (`test/verify.mjs` 会断言前两处;第三处在契约测试中同样被断言)。
420
452
 
453
+ **发布前必查"地址类信息"**(1.0.0 的教训):包名不能只查 npm —— 还要查 GitHub 仓库名是否已被占用,以及
454
+ [`awesome-dsh-plugin` 收录索引](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/tree/main/data/plugins)
455
+ 里是否已有同名插件(本插件第一次改名就是因为漏查了后者)。
456
+
421
457
  ---
422
458
 
423
459
  ## 十四、已知限制(含未在本机验证的部分)
package/client.js CHANGED
@@ -167,6 +167,9 @@ window.__ModuleLoader__.load({
167
167
  diagZones: '区域锚点(data-slot)',
168
168
  diagZonesMissing: '缺失,区域过滤已停用',
169
169
  diagSlots: '槽位落点',
170
+ diagSidecard: '侧边卡片插件(dsh-better-sidebar)',
171
+ diagFeatures: '项能力',
172
+ diagNoTabMeta: '版本过旧:没有 tabMeta 能力,已跳过它改用其它承载面',
170
173
  toolGuardUnavailable: '该 provider 不支持工具白名单,本次作答继承了当前会话的工具',
171
174
  toolGuardInherited: '按配置继承当前会话的工具',
172
175
  diagUnreachable: '宿主接口不可达:{msg}',
@@ -266,6 +269,9 @@ window.__ModuleLoader__.load({
266
269
  diagZones: 'Zone anchors (data-slot)',
267
270
  diagZonesMissing: 'missing — region filtering disabled',
268
271
  diagSlots: 'Slot landing',
272
+ diagSidecard: 'Side-card plugin (dsh-better-sidebar)',
273
+ diagFeatures: 'capabilities',
274
+ diagNoTabMeta: 'too old: no tabMeta capability, skipped in favour of another surface',
269
275
  toolGuardUnavailable: 'This provider supports no tool allow-list, so the answer inherited the session tools',
270
276
  toolGuardInherited: 'Inherits the session tools, as configured',
271
277
  diagUnreachable: 'Host API unreachable: {msg}',
@@ -859,12 +865,35 @@ window.__ModuleLoader__.load({
859
865
  error: null,
860
866
  dispose: null,
861
867
  features: [],
868
+ /**
869
+ * Why this adapter is (not) usable, in the settings page's own words.
870
+ * `null` means "usable"; a string explains the refusal.
871
+ */
872
+ reason: 'not-detected',
873
+ /**
874
+ * Reported by the plugin itself (`BetterSidebarService.version`), so the
875
+ * settings page can say which side-card plugin version is really in use.
876
+ */
877
+ version: null,
878
+ /**
879
+ * Usable only when the service advertises the `tabMeta` capability: the
880
+ * card id travels in `tab.meta`, and `SIDEBAR_FEATURES` has listed
881
+ * `tabMeta` since v0.12.0 with newer versions only ever ADDING features
882
+ * ("Features are never removed"). A pre-0.12 instance would open an
883
+ * empty tab, so it is skipped and the ladder continues to the native
884
+ * right rail or the built-in flow card instead.
885
+ */
862
886
  available() {
863
- return this.service !== null && this.error === null
887
+ if (this.service === null || this.error !== null) return false
888
+ return Array.isArray(this.features) && this.features.includes('tabMeta')
864
889
  },
865
890
  open(card) {
866
891
  if (!this.available() || typeof this.service.openTab !== 'function') return false
867
892
  const tabId = `${CARD_KIND}:${card.id}`
893
+ // `meta` is the transport for the card id (feature `tabMeta`), and
894
+ // `dedupeKey` on our descriptor is what collapses repeat opens onto
895
+ // the same tab; both are part of the stable consumer contract
896
+ // (`lib/types/client/service.d.ts`, unchanged across 0.22.0 → 0.22.1).
868
897
  this.service.openTab(
869
898
  { type: CARD_KIND, id: tabId, title: card.question.slice(0, 32), meta: { cardId: card.id } },
870
899
  store.state.sessionId === null ? undefined : { sessionId: store.state.sessionId },
@@ -1523,6 +1552,9 @@ window.__ModuleLoader__.load({
1523
1552
  h('div', { key: 'session' }, `${t('diagSession')}: ${state.sessionId ?? t('none')}`),
1524
1553
  h('div', { key: 'zones' }, `${t('diagZones')}: ${state.zoneAnchors === false ? t('diagZonesMissing') : t('yes')}`),
1525
1554
  h('div', { key: 'slots' }, `${t('diagSlots')}: ${Object.entries(state.slots).map(([k, v]) => `${k}=${v}`).join(' · ')}`),
1555
+ // The side-card plugin's own report: which version is loaded and
1556
+ // whether it advertises the capability this adapter needs.
1557
+ h('div', { key: 'sidecard' }, `${t('diagSidecard')}: ${describeSidecardAdapter()}`),
1526
1558
  h('div', { key: 'path' }, `${t('settingsTitle')} → ${diag.provenance?.persistedPath ?? ''}`),
1527
1559
  ])
1528
1560
  : null)
@@ -1549,6 +1581,27 @@ window.__ModuleLoader__.load({
1549
1581
  return capturedComposerActions === null ? null : { actions: capturedComposerActions }
1550
1582
  }
1551
1583
 
1584
+ /**
1585
+ * One line describing how the optional side-card plugin resolved, for the
1586
+ * settings page's capability check. Reported from the plugin's OWN
1587
+ * `version`/`features` fields, so the answer is what is running, not what
1588
+ * package.json claims.
1589
+ * @returns {string} human-readable state.
1590
+ */
1591
+ function describeSidecardAdapter() {
1592
+ const better = surfaces.better
1593
+ if (better.error !== null) return `${t('no')}(${better.error})`
1594
+ if (better.version === null && better.service === null) return t('none')
1595
+ const version = better.version ?? '?'
1596
+ if (better.available()) {
1597
+ return `v${version} · ${better.features.length} ${t('diagFeatures')} · ${t('yes')}`
1598
+ }
1599
+ if (better.reason === 'no-tab-meta') {
1600
+ return `v${version} · ${t('diagNoTabMeta')}`
1601
+ }
1602
+ return `v${version} · ${better.reason ?? t('no')}`
1603
+ }
1604
+
1552
1605
  // ─────────────────────────────────────────────────────────────────────
1553
1606
  // §10 plugin face
1554
1607
  // ─────────────────────────────────────────────────────────────────────
@@ -2099,6 +2152,18 @@ window.__ModuleLoader__.load({
2099
2152
  if (registry === null || registry === undefined) return
2100
2153
  surfaces.native.registry = registry
2101
2154
  surfaces.native.controller = controller ?? null
2155
+ // Register the tab TYPE first, then its body/title slots — and
2156
+ // roll the type back if a slot refuses to register.
2157
+ //
2158
+ // The host takes the id the moment `register` returns and refuses
2159
+ // a second registration of the same id; a slot registration that
2160
+ // throws (an already-inactive context during a reload/disposal)
2161
+ // would then leave the kind taken for the rest of the page's life,
2162
+ // rendering the host's "nothing can view this" face forever. This
2163
+ // mirrors what dsh-better-sidebar 0.22.1 fixed in its own native
2164
+ // glue (`disposeSafely` + partial-set release in
2165
+ // `src/client/native/index.ts`).
2166
+ let releaseType = null
2102
2167
  if (typeof registry.register === 'function') {
2103
2168
  const off = registry.register({
2104
2169
  id: CARD_KIND,
@@ -2111,20 +2176,40 @@ window.__ModuleLoader__.load({
2111
2176
  description: () => t('settingsDesc'),
2112
2177
  }],
2113
2178
  })
2114
- surfaces.native.dispose = typeof off === 'function' ? off : null
2179
+ releaseType = typeof off === 'function' ? off : null
2180
+ surfaces.native.dispose = releaseType
2181
+ }
2182
+ const slotDisposers = []
2183
+ try {
2184
+ slotDisposers.push(ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({
2185
+ name: 'sidebar.right.pane.tab',
2186
+ key: CARD_KIND,
2187
+ inject: sessionId => ({ sessionId }),
2188
+ }, (props) => h(CardHost, { props, actions }))))
2189
+ slotDisposers.push(ctx.slots.inject('sidebar.right.pane.tab.title', () => ctx.slots.register({
2190
+ name: 'sidebar.right.pane.tab.title',
2191
+ key: CARD_KIND,
2192
+ }, () => h('span', null, t('answerTitle')))))
2193
+ } catch (error) {
2194
+ for (const dispose of slotDisposers) {
2195
+ try {
2196
+ dispose()
2197
+ } catch { /* releasing must not mask the failure */ }
2198
+ }
2199
+ if (releaseType !== null) {
2200
+ try {
2201
+ releaseType()
2202
+ } catch { /* releasing must not mask the failure */ }
2203
+ surfaces.native.dispose = null
2204
+ }
2205
+ surfaces.native.registry = null
2206
+ surfaces.native.controller = null
2207
+ throw error
2115
2208
  }
2116
- const offBody = ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({
2117
- name: 'sidebar.right.pane.tab',
2118
- key: CARD_KIND,
2119
- inject: sessionId => ({ sessionId }),
2120
- }, (props) => h(CardHost, { props, actions })))
2121
- const offTitle = ctx.slots.inject('sidebar.right.pane.tab.title', () => ctx.slots.register({
2122
- name: 'sidebar.right.pane.tab.title',
2123
- key: CARD_KIND,
2124
- }, () => h('span', null, t('answerTitle'))))
2125
2209
  disposers.push(() => {
2126
- try { offBody() } catch { /* already disposed */ }
2127
- try { offTitle() } catch { /* already disposed */ }
2210
+ for (const dispose of slotDisposers) {
2211
+ try { dispose() } catch { /* already disposed */ }
2212
+ }
2128
2213
  try { surfaces.native.dispose?.() } catch { /* already disposed */ }
2129
2214
  })
2130
2215
  } catch (error) {
@@ -2137,9 +2222,15 @@ window.__ModuleLoader__.load({
2137
2222
  }
2138
2223
 
2139
2224
  // The dsh-better-sidebar plugin, when installed: its client service
2140
- // registers a tab type and opens one per card. `features.tabMeta`
2141
- // gates the meta-carried card id, so an older version of that plugin
2142
- // degrades to the built-in flow card instead of showing an empty tab.
2225
+ // registers a tab type and opens one per card.
2226
+ //
2227
+ // Verified against the published consumer contract: between 0.22.0 and
2228
+ // 0.22.1 `lib/types/client/service.d.ts` is byte-identical apart from the
2229
+ // version constant, and 0.22.1's `src/client/native/index.ts` changes are
2230
+ // robustness fixes in ITS own native glue — no field we use changed. The
2231
+ // `features` array is the forward-compatible half of that contract
2232
+ // ("Features are never removed"), so the adapter gates on `tabMeta`
2233
+ // instead of on a version number.
2143
2234
  try {
2144
2235
  ctx.inject(['betterSidebar'], (injected) => {
2145
2236
  try {
@@ -2147,7 +2238,12 @@ window.__ModuleLoader__.load({
2147
2238
  if (service === null || service === undefined) return
2148
2239
  const features = Array.isArray(service.features) ? service.features : []
2149
2240
  surfaces.better.features = features
2150
- if (typeof service.registerTab !== 'function') return
2241
+ surfaces.better.version = typeof service.version === 'string' ? service.version : null
2242
+ surfaces.better.reason = features.includes('tabMeta') ? null : 'no-tab-meta'
2243
+ if (typeof service.registerTab !== 'function') {
2244
+ surfaces.better.reason = 'no-register-tab'
2245
+ return
2246
+ }
2151
2247
  const off = service.registerTab({
2152
2248
  id: CARD_KIND,
2153
2249
  title: () => t('answerTitle'),
@@ -2241,7 +2337,7 @@ window.__ModuleLoader__.load({
2241
2337
  * `window` and exercises these without a browser.
2242
2338
  */
2243
2339
  api: Object.freeze({
2244
- version: '1.1.0',
2340
+ version: '1.2.0',
2245
2341
  /** Read-only state accessor for diagnostics and the test harness. */
2246
2342
  snapshot: () => store.state,
2247
2343
  pure: Object.freeze({
package/index.js CHANGED
@@ -42,7 +42,7 @@ export const name = 'dsh-sidecard-ask'
42
42
  export const inject = ['webServer']
43
43
 
44
44
  /** Version of this plugin (kept in step with package.json by test/verify.mjs). */
45
- export const PLUGIN_VERSION = '1.1.0'
45
+ export const PLUGIN_VERSION = '1.2.0'
46
46
 
47
47
  /** Route prefix of the plugin's own API. */
48
48
  export const ROUTE_PREFIX = '/sidecard-ask/api'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-sidecard-ask",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "DSH 划词追问(侧边卡片作答):在聊天区与任务区选中文本就地追问,答案由独立子代理在侧边卡片里流式呈现,也可选择落回主对话。",
5
5
  "type": "module",
6
6
  "main": "index.js",