dsh-dupguard 1.2.0 → 1.2.1

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
@@ -2,6 +2,24 @@
2
2
 
3
3
  本文件遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循 [SemVer](https://semver.org/lang/zh-CN/)。
4
4
 
5
+ ## [1.2.1] - 2026-09-02
6
+
7
+ ### Fixed
8
+
9
+ - 设置页写入在 DSH 0.1.2 上完全失效:0.1.2 的客户端 `connection` 服务不再暴露
10
+ `api`(wire 面改为 `ctx.remote.<namespace>`),此前的直连 RPC 写法在 0.1.2 上
11
+ 同步抛错——表现为点击「添加」后列表乐观更新、底部停在「保存中…」,写入从未落盘、
12
+ 关闭重开不显示、白名单也不生效。现改为经 `settingsScope` 控制器写
13
+ (`set` / `unset`:自动携带最新 revision、串行化并发写、把宿主应答折叠回共享镜像),
14
+ 写完回读快照校验后显示「已保存」或失败原因;该控制器接口在 0.1.1 与 0.1.2 一致,
15
+ 因此同时兼容两版。
16
+
17
+ ### Added
18
+
19
+ - `tests/client.test.js`:用最小 React / DSH 客户端桩直接驱动设置页组件,覆盖写路径
20
+ (控制器 `set`/`unset`)、「保存中…→已保存」回显、重开显示持久化白名单,并断言组件
21
+ 不再依赖 `connection.api`;`npm test` 与 CI 改为同时运行两个测试文件。
22
+
5
23
  ## [1.2.0] - 2026-09-02
6
24
 
7
25
  ### Added
package/README.md CHANGED
@@ -197,6 +197,7 @@ default), Markdown table separator rows and horizontal rules (whitelisted by def
197
197
  │ └── client.js # 浏览器端设置页(ModuleLoader 格式,dsh.client 入口)
198
198
  ├── tests/
199
199
  │ ├── detector.test.js # 端到端测试:双入口防漂移 + reasoning 开关 + settings 集成
200
+ │ ├── client.test.js # 设置页组件测试:最小 React/DSH 桩驱动写路径
200
201
  │ └── experiment-cancel.mjs # 诊断实验(不进 CI):验证截停不阻塞于底层流取消
201
202
  ├── .github/workflows/ci.yml # GitHub Actions:Node 20/22/24
202
203
  ├── cordis.patch.yml # bundle 补丁层(dsh.bundle.patch:插入宿主行)
@@ -209,13 +210,17 @@ default), Markdown table separator rows and horizontal rules (whitelisted by def
209
210
  ## 测试 / Tests
210
211
 
211
212
  ```bash
212
- node tests/detector.test.js # 或 npm test
213
+ npm test # 两个测试文件
214
+ node tests/detector.test.js # 检测端到端(48 项)
215
+ node tests/client.test.js # 设置页组件(6 项)
213
216
  ```
214
217
 
215
218
  同一套 15 项用例分别驱动两个入口(`plugin/host.js` 经 `new Function` 求值、`lib/index.js` 经
216
219
  `require` 加载),覆盖:透传完整性、各类复读形态、阈值边界、协议闭合、上游 `return()` 调用、
217
- 默认不检测 reasoning/工具参数、未闭合工具调用块的闭合、多次调用状态隔离等。CI 在 Node 20/22/24
218
- 上运行(与 DSH 一致,不支持 Node 18)。
220
+ 默认不检测 reasoning/工具参数、未闭合工具调用块的闭合、多次调用状态隔离等。`client.test.js`
221
+ 用最小 React 与 DSH 客户端桩驱动设置页组件,断言写入走 `settingsScope` 控制器
222
+ (`set`/`unset`)而非已移除的 `connection.api`。CI 在 Node 20/22/24 上运行
223
+ (与 DSH 一致,不支持 Node 18)。
219
224
 
220
225
  The same 15-test suite drives both entries, guarding against drift between the two forms. CI runs on
221
226
  Node 20/22/24 (matching DSH; Node 18 is not supported).
package/lib/client.js CHANGED
@@ -31,6 +31,7 @@ window.__ModuleLoader__.load({
31
31
  reset: '恢复默认',
32
32
  loading: '加载中…',
33
33
  note: '修改即时生效;「恢复默认」回到连字符与竖线。',
34
+ saveFailed: '保存失败:宿主未接受该值,请查看 DSH 宿主日志。',
34
35
  remoteHint: '设置修改仅支持本机连接:请通过本机地址(127.0.0.1)打开 DSH 页面后重试。远程访问时 DSH 的设置通道被禁用,白名单将使用默认值(连字符与竖线)。',
35
36
  unavailable: '设置服务不可用:未读取到「重复守卫」的设置数据。请确认 DSH 为 npm 常驻版挂载(宿主日志应包含「已注册设置命名空间 dsh-dupguard」),并刷新页面重试。',
36
37
  }
@@ -45,6 +46,7 @@ window.__ModuleLoader__.load({
45
46
  reset: 'Reset to defaults',
46
47
  loading: 'Loading…',
47
48
  note: 'Changes take effect immediately; "Reset to defaults" restores hyphen and pipe.',
49
+ saveFailed: 'Save failed: the host did not accept the value; check the DSH host log.',
48
50
  remoteHint: 'Settings editing requires a local connection: open the DSH page through the local address (127.0.0.1) and retry. Remote browsers have the settings channel disabled and the whitelist stays at its defaults (hyphen and pipe).',
49
51
  unavailable: 'Settings unavailable: no data for the Dupguard section was received. Verify the npm build is mounted (the host log should contain "已注册设置命名空间 dsh-dupguard") and refresh the page.',
50
52
  }
@@ -128,50 +130,34 @@ window.__ModuleLoader__.load({
128
130
  }
129
131
  }, [])
130
132
 
131
- // 直接经设置 RPC 写(models 页同款),可观察到真实错误;
132
- // 乐观更新保证界面即时反映,写入结果异步回填状态。
133
- const persist = (next) => {
133
+ // 写路径走 settingsScope 控制器(DSH 自己的设置写通道):自动携带最新
134
+ // revision、串行化并发写,并把宿主应答折叠回共享镜像。
135
+ // 注意:DSH 0.1.2 起客户端不再暴露 connection.api(改为 ctx.remote.<ns>),
136
+ // 直接调 wire 面会在 0.1.2 上抛错并卡在「保存中…」;控制器接口两版一致。
137
+ const finishWrite = (expected) => {
138
+ // 写入结果由控制器折叠进镜像,回读快照校验是否真的落盘。
139
+ const snapNow = props.controller.getSnapshot()
140
+ const applied = snapNow !== undefined && snapNow.value !== undefined && Array.isArray(snapNow.value.ignoredChars)
141
+ ? snapNow.value.ignoredChars
142
+ : null
143
+ const same = applied !== null && applied.length === expected.length &&
144
+ applied.every((ch, index) => ch === expected[index])
145
+ setWriteState(same ? 'saved' : 'error:' + t('saveFailed'))
146
+ }
147
+ const runWrite = (operation, expected) => {
134
148
  setWriteState('saving')
135
- const revision = snap.revision === undefined ? undefined : snap.revision
136
- props.api.settings.mutate({
137
- ns: SETTINGS_NS,
138
- ops: [{ op: 'set', path: ['ignoredChars'], value: next }],
139
- ...(revision === undefined ? {} : { expectedRevision: revision }),
140
- }).then((response) => {
141
- if (!response || !response.result || !response.result.ok) {
142
- const message = response && response.result && response.result.error ? response.result.error.message : 'unknown'
143
- setWriteState('error:' + message)
144
- } else {
145
- setWriteState('saved')
146
- // 写成功后重拉 describe 刷新镜像,保证重开设置页显示持久化值。
147
- if (props.mirror && typeof props.mirror.load === 'function') {
148
- props.mirror.load()
149
- }
150
- }
151
- }, (error) => {
152
- setWriteState('error:' + String(error && error.message || error))
153
- })
149
+ Promise.resolve()
150
+ .then(() => operation())
151
+ .then(() => finishWrite(expected), (error) => {
152
+ setWriteState('error:' + String((error && error.message) || error))
153
+ })
154
+ }
155
+ const persist = (next) => {
156
+ runWrite(() => props.controller.set('ignoredChars', next), next)
154
157
  }
155
158
  const persistUnset = () => {
156
- setWriteState('saving')
157
- const revision = snap.revision === undefined ? undefined : snap.revision
158
- props.api.settings.mutate({
159
- ns: SETTINGS_NS,
160
- ops: [{ op: 'unset', path: ['ignoredChars'] }],
161
- ...(revision === undefined ? {} : { expectedRevision: revision }),
162
- }).then((response) => {
163
- if (!response || !response.result || !response.result.ok) {
164
- const message = response && response.result && response.result.error ? response.result.error.message : 'unknown'
165
- setWriteState('error:' + message)
166
- } else {
167
- setWriteState('saved')
168
- if (props.mirror && typeof props.mirror.load === 'function') {
169
- props.mirror.load()
170
- }
171
- }
172
- }, (error) => {
173
- setWriteState('error:' + String(error && error.message || error))
174
- })
159
+ // unset 后解析值回到 schema 默认(宿主 base)白名单。
160
+ runWrite(() => props.controller.unset('ignoredChars'), ['-', '|'])
175
161
  }
176
162
 
177
163
  const remove = (ch) => {
@@ -266,7 +252,7 @@ window.__ModuleLoader__.load({
266
252
  ctx.effect(() => () => {
267
253
  controller.dispose()
268
254
  }, 'dupguard: settings scope')
269
- const injected = () => ({ controller, t, isLoopback, mirror, api: connection.api })
255
+ const injected = () => ({ controller, t, isLoopback, mirror })
270
256
  ctx.slots.inject('settings.section', () => ctx.slots.register({
271
257
  name: 'settings.section',
272
258
  id: 'dupguard',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-dupguard",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Real-time repetition guard for DeepSeek Harness (DSH): stops model generation when the same string repeats >=10 times in the streamed output. 实时检测 DSH 大模型流式输出中的重复内容,同一字符串重复十次以上立即停止生成。",
5
5
  "keywords": [
6
6
  "dsh-plugin",
@@ -45,7 +45,7 @@
45
45
  "node": ">=20"
46
46
  },
47
47
  "scripts": {
48
- "test": "node tests/detector.test.js"
48
+ "test": "node tests/detector.test.js && node tests/client.test.js"
49
49
  },
50
50
  "publishConfig": {
51
51
  "access": "public"