dsh-opencode-go 0.1.19 → 0.1.20

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/README.en.md CHANGED
@@ -86,7 +86,7 @@ To build from source and install a local package:
86
86
  npm ci
87
87
  npm run compile
88
88
  npm pack
89
- dsh plugin --profile web add ./dsh-opencode-go-0.1.19.tgz
89
+ dsh plugin --profile web add ./dsh-opencode-go-0.1.20.tgz
90
90
  ```
91
91
 
92
92
  Source development builds the plugin explicitly with `npm run compile`; `--legacy-peer-deps` is not required. Multi-version DSH compatibility tests use independent environments and are not installed during ordinary installation or builds. For Headless, replace `web` with `headless`.
@@ -97,7 +97,7 @@ Source development builds the plugin explicitly with `npm run compile`; `--legac
97
97
  npm run compile # Regenerate lib/ and commit it with source changes
98
98
  npm test # Core tests, rebuilding the plugin first
99
99
  npm run check:dist # Verify shipped artifacts match the source
100
- npm run test:compat # One tarball tested in 8 independent DSH environments
100
+ npm run test:compat # One tarball tested in 9 independent DSH environments
101
101
  npm run test:install # npm / pnpm Git installs without plugin build approval
102
102
  npm run verify # All of the above
103
103
  ```
@@ -114,6 +114,8 @@ dsh plugin --profile web update dsh-opencode-go --latest
114
114
 
115
115
  Restart `dsh web` and refresh the browser afterwards. For Headless, replace `web` with `headless`; if both profiles have the plugin installed, update each one separately.
116
116
 
117
+ After updating the desktop plugin, fully quit and reopen DeepSeek Harness to load the updated plugin code.
118
+
117
119
  ## Subscription usage display
118
120
 
119
121
  Under **Settings → OpenCode Go → Advanced settings → Usage display**, choose a mode and save: **Auto (default)** shows the pill only for this plugin's DSH OpenCode Go models; **Always** keeps it visible with other models, including the built-in `opencode-go` provider; **Off** hides it and stops polling. Disabling OpenCode Go hides the pill in every mode. Click the pill to open the usage panel.
package/README.md CHANGED
@@ -86,7 +86,7 @@ dsh --profile headless --patch ./headless.patch.yml "你好"
86
86
  npm ci
87
87
  npm run compile
88
88
  npm pack
89
- dsh plugin --profile web add ./dsh-opencode-go-0.1.19.tgz
89
+ dsh plugin --profile web add ./dsh-opencode-go-0.1.20.tgz
90
90
  ```
91
91
 
92
92
  源码开发时显式运行 `npm run compile`;普通用户直接安装预编译产物,无需 `--legacy-peer-deps`。多版本 DSH 兼容性测试使用独立环境,不参与普通安装或构建。Headless 用户将 `web` 换成 `headless`。
@@ -97,7 +97,7 @@ dsh plugin --profile web add ./dsh-opencode-go-0.1.19.tgz
97
97
  npm run compile # 更新 lib/,与源码一起提交
98
98
  npm test # 基础功能测试(自动重新构建)
99
99
  npm run check:dist # 检查已提交产物是否与源码一致
100
- npm run test:compat # 同一安装包在 8 套独立 DSH 环境中测试
100
+ npm run test:compat # 同一安装包在 9 套独立 DSH 环境中测试
101
101
  npm run test:install # npm / pnpm 无额外构建授权的 Git 安装检查
102
102
  npm run verify # 执行以上全部检查
103
103
  ```
@@ -114,6 +114,8 @@ dsh plugin --profile web update dsh-opencode-go --latest
114
114
 
115
115
  完成后重启 `dsh web` 并刷新浏览器。Headless 用户将 `web` 换成 `headless`;如果两个 profile 都安装了插件,需要分别升级。
116
116
 
117
+ 桌面版升级后,请完全退出并重新打开 DeepSeek Harness,以加载更新后的插件代码。
118
+
117
119
  ## 订阅用量显示
118
120
 
119
121
  在「设置 → OpenCode Go → 高级设置 → 额度显示」中选择并保存:**自动(默认)**仅在选中本插件的 DSH OpenCode Go 模型时显示;**常驻**在使用其他模型(包括宿主自带的 `opencode-go`)时也显示;**关闭**隐藏胶囊并停止轮询。停用 OpenCode Go 后所有模式均隐藏。显示时点击胶囊展开用量面板。
@@ -1,5 +1,23 @@
1
1
  # Verification
2
2
 
3
+ ## Release 0.1.20: usage pill and settings draft fixes (2026-10-02)
4
+
5
+ The release includes the Host scope receiver fix and shared no-op draft predicate from PR #34. The installed-client compatibility fixture now reads instance state and exercises both SSR snapshot reads and client subscription cleanup. Desktop follow-up confirmed that saving Always works after restarting a Host process that predated the installed plugin update; the upgrade instructions now explicitly require quitting and reopening desktop Harness.
6
+
7
+ On macOS / Node 24.14.1, `DSH_COMPAT_CONCURRENCY=4 npm run verify` passed **401 tests in 28 files**, freshness checks for **34 shipped build files**, **all nine installed-host generations**, and both **npm and pnpm Git installation checks**. The compatibility run took **87.9 seconds** including cleanup; installation checks took **11.8 seconds**. Both installation paths loaded the shipped artifacts without source builds or plugin build approval and retained the private pi-ai 0.87.1 alongside the independent peer's 0.85.1. The test suite used local gateways and isolated consumers.
8
+
9
+ ## Usage pill rendering on a real Host and the staged override badge (2026-10-02)
10
+
11
+ The browser half no longer hands the Host settings scope's methods over as plain values. `UsagePill` subscribed with `useSyncExternalStore(settings.subscribe, settings.getSnapshot, ...)`, and a Host scope is a class instance whose methods read their own state (`ConfigFormController` behind `ctx.configForms.get(entryId)`, `SettingsScopeController` behind `ctx.settingsScope.bind(spec)`); both providers return that instance as-is. The detached reference lost its receiver, so the pill threw `TypeError: Cannot read properties of undefined (reading 'store')` on its first render, the slot's per-entry error boundary removed it, and no usage mode could ever show a pill — including Always, which is why a saved `usageDisplay: always` had no visible effect. React now reaches the scope through `useCallback` wrappers, the same shape the Host's own slot outlet uses.
12
+
13
+ The staged form's override badge now answers for the same state its save plan does. `plan` skipped a draft equal to the field's effective value while `field` reported it as an override, so a value typed back to its original (`60 → 59 → 60`) disabled Save and Discard yet kept the "overridden" badge, its reset button, and the advanced card's badge. Both paths now share one `unchangedDraft` predicate: a no-op draft reports whatever the user layer already holds, a clear still previews as not overridden, and an invalid draft still blocks the save.
14
+
15
+ Before the fix, `npx vitest run tests/usage-pill.client.spec.tsx tests/stores.client.spec.ts` failed 25 of 54 cases: all **24** usage-pill cases (including the new receiver case) with the detached-call `TypeError`, and the new no-op-draft case on `overridden`. The test double now keeps its scope methods on the prototype (`tests/support/settings-scope.ts`) and the installed-host fixture builds the same class shape, so a method passed as a value fails in tests exactly as it failed in a browser; before this change every double used arrow-function literals, which is what hid the regression from the suite and from the `v020-rc2` compatibility row.
16
+
17
+ Host/Client type checks and production compilation passed; `npm run test:ci` passed **401 tests in 28 files** and freshness checks for all **34 shipped artifacts** on Windows / Node 24.15.0. `DSH_COMPAT_CONCURRENCY=1 npm run test:compat -- v020-rc2 v017-rc2` passed both installed generations — 0.2.0-rc.2 and 0.1.7-rc.2 (both use the `configForms` path) — including the client fixture's module table, settings binding, rendering, CSS and cleanup. On this machine the client fixture also exceeded the runner's 30-second per-fixture limit before this change, so the two local rows ran with that limit temporarily raised to 180 seconds; the committed limit is unchanged. The other seven host generations, macOS and Linux, and a live Desktop session were not exercised. Every check used loopback fixtures; no installed user profile, GitHub issue or npm publication was changed.
18
+
19
+ Follow-up on macOS / Node 24.14.1: the installed-host fixture now reads `this.snapshot` and `this.listeners`; prototype methods that only closed over local variables still allowed detached calls. Its SSR checks exercise the snapshot reader, and a client mount/unmount now checks the scope subscription and cleanup while Auto keeps the pill hidden. The receiver unit case also checks a live display change and subscription disposal. Two temporary negative-control bundles failed as expected: the original detached snapshot reader threw on `snapshot`, and a client with only the subscription detached threw on `listeners`; the fixed bundle passed. Both type checks, **401 tests in 28 files**, all **34 shipped artifacts**, and **all nine installed-host generations** passed. The compatibility run took **88.2 seconds** including cleanup, with the committed 30-second fixture limit unchanged. The matrix exercises legacy `settingsScope` on 0.1.5/0.1.6 and `configForms` on 0.1.7/0.2. No live Desktop UI or installed user profile was exercised.
20
+
3
21
  ## Release 0.1.19: consolidate pending features (2026-10-02)
4
22
 
5
23
  The prepared-call configuration, Anthropic alias replay and usage-display changes are now integrated with 0.1.18's MiMo controls. Prepared calls retain the captured endpoint, credential reference and nested capacities; later calls use updated settings. Anthropic replay preserves signed thinking against the requested model ID. Settings now offer Auto / Always / Off usage visibility, with Off and a disabled plugin stopping the poller. The existing default remains Auto.
package/lib/client.js CHANGED
@@ -932,7 +932,9 @@ var StagedForm = class {
932
932
  const write = staged.clear ? { kind: "clear" } : spec.parse(staged.text);
933
933
  return {
934
934
  text: staged.text,
935
- overridden: write?.kind === "set",
935
+ // A draft a save would skip leaves the user layer exactly as it stands, so
936
+ // the badge answers for that standing override instead of the edit.
937
+ overridden: this.unchangedDraft(field, staged) ? this.stored(field) : write?.kind === "set",
936
938
  invalid: write === void 0
937
939
  };
938
940
  }
@@ -1003,7 +1005,7 @@ var StagedForm = class {
1003
1005
  if (this.stored(field)) plan.push({ field, run: () => this.clear(field) });
1004
1006
  continue;
1005
1007
  }
1006
- if (staged.text === spec.format(this.sectionValue(field))) continue;
1008
+ if (this.unchangedDraft(field, staged)) continue;
1007
1009
  const write = spec.parse(staged.text);
1008
1010
  if (write === void 0) plan.push({ field, run: void 0 });
1009
1011
  else if (write.kind === "clear") plan.push({ field, run: () => this.clear(field) });
@@ -1045,6 +1047,16 @@ var StagedForm = class {
1045
1047
  const user = this.userLayer();
1046
1048
  return user !== void 0 && Object.hasOwn(user, field);
1047
1049
  }
1050
+ /**
1051
+ * Whether a draft already equals the field's effective value, so a save would
1052
+ * write nothing. The save plan and the override badge both answer for it.
1053
+ * @param field - field name inside the namespace section.
1054
+ * @param staged - the staged edit to test.
1055
+ * @returns true when the draft is a no-op; a clear is never one.
1056
+ */
1057
+ unchangedDraft(field, staged) {
1058
+ return !staged.clear && staged.text === this.spec(field).format(this.sectionValue(field));
1059
+ }
1048
1060
  publish() {
1049
1061
  for (const listener of this.listeners) listener();
1050
1062
  }
@@ -1690,7 +1702,9 @@ function usageLevel(window) {
1690
1702
  }
1691
1703
  function UsagePill({ directory, settings, ...props }) {
1692
1704
  const state = (0, import_react3.useSyncExternalStore)(directory.subscribe, directory.getSnapshot, directory.getSnapshot);
1693
- const config = (0, import_react3.useSyncExternalStore)(settings.subscribe, settings.getSnapshot, settings.getSnapshot);
1705
+ const readSettings = (0, import_react3.useCallback)(() => settings.getSnapshot(), [settings]);
1706
+ const subscribeSettings = (0, import_react3.useCallback)((listener) => settings.subscribe(listener), [settings]);
1707
+ const config = (0, import_react3.useSyncExternalStore)(subscribeSettings, readSettings, readSettings);
1694
1708
  const mode = config.value?.usageDisplay ?? DEFAULT_USAGE_DISPLAY;
1695
1709
  const visible = config.status === "ready" && config.value?.enabled !== false && (mode === "always" || mode === "auto" && state.current?.provider === PROVIDER_ID);
1696
1710
  return visible ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ActiveUsage, { ...props }) : null;
@@ -191,5 +191,13 @@ export declare class StagedForm {
191
191
  private baseValue;
192
192
  private userLayer;
193
193
  private stored;
194
+ /**
195
+ * Whether a draft already equals the field's effective value, so a save would
196
+ * write nothing. The save plan and the override badge both answer for it.
197
+ * @param field - field name inside the namespace section.
198
+ * @param staged - the staged edit to test.
199
+ * @returns true when the draft is a no-op; a clear is never one.
200
+ */
201
+ private unchangedDraft;
194
202
  private publish;
195
203
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-opencode-go",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "description": "OpenCode Go model provider and settings UI for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "license": "MIT",