@mars-sea/dsh-commandcode-provider 0.11.10 → 0.11.11

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
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.11.11] - 2026-09-23
8
+
9
+ ### Fixed
10
+
11
+ - **Saving account-rotation edits is visible on a long settings page** ([#60](https://github.com/Mars-Sea/dsh-commandcode-provider/issues/60)). The page's single global Save/Discard bar stays inside the settings panel, attached to the bottom of its scrolling area. A new account can be saved with only a label, then signed in through the same browser authorization flow as the default account; the Host writes the resulting key to that saved account's credential reference. Login attempts for different accounts cannot silently rejoin one another.
12
+
13
+ - **Command Code responses remain readable when a DSH HTTP dispatcher passes through compressed bytes** ([#59](https://github.com/Mars-Sea/dsh-commandcode-provider/issues/59)). Host-side chat, account usage, catalog, web search, decision, and login requests now ask for `Accept-Encoding: identity`. This also lets a Go account's `403 upgrade_required` body reach the existing CLI-transport fallback. If every usage endpoint answers but its body still cannot be parsed, the account view reports an unreadable response instead of claiming that no request reached the server.
14
+
7
15
  ## [0.11.10] - 2026-09-23
8
16
 
9
17
  ### Added
package/README.md CHANGED
@@ -54,7 +54,7 @@ Pick the release line that matches your DeepSeek Harness version:
54
54
  **pnpm 11 holds back new releases.** Its `minimumReleaseAge` defaults to 1440 minutes, so a version published less than a day ago is skipped and `@latest` resolves to the *previous* release — silently, with a success exit code. To install a release from the last 24 hours, name it exactly:
55
55
 
56
56
  ```sh
57
- dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.11.9
57
+ dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.11.11
58
58
  ```
59
59
 
60
60
  The same applies to every profile you install into, including the terminal UI below.
@@ -73,7 +73,7 @@ dsh plugin --profile web update @mars-sea/dsh-commandcode-provider@0.9.1 #
73
73
  Each profile updates separately — the terminal UI owns its own plugin list (see below):
74
74
 
75
75
  ```sh
76
- dsh plugin --profile dsh-tui update @mars-sea/dsh-commandcode-provider@0.11.9
76
+ dsh plugin --profile dsh-tui update @mars-sea/dsh-commandcode-provider@0.11.11
77
77
  ```
78
78
 
79
79
  To move to a version published less than 24 hours ago, name it exactly as in Install above; pnpm 11's age gate resolves `@latest` to the previous release instead.
@@ -102,7 +102,7 @@ After restart, enter your API key in **Settings → Command Code** and save; **S
102
102
  The plugin also works under a terminal front door. **Each dsh profile owns its own plugin list**, so the web install above does not reach the terminal — add the plugin to the `dsh-tui` profile as well:
103
103
 
104
104
  ```sh
105
- dsh plugin --profile dsh-tui add @mars-sea/dsh-commandcode-provider@0.11.9
105
+ dsh plugin --profile dsh-tui add @mars-sea/dsh-commandcode-provider@0.11.11
106
106
  ```
107
107
 
108
108
  Pin the exact version here. For the first 24 hours after a release, a bare package name (or `@latest`) is silently resolved to the previous one: the install succeeds, but the profile gets the older build — which is how a fresh terminal install ends up with no **`/settings` → Command Code** page and no `commandcode` models at all.
@@ -156,7 +156,7 @@ The command's user-facing copy follows the shell's locale: explicit `lang: 'en'
156
156
 
157
157
  With several Command Code subscriptions, the plugin **switches to the next account automatically** when one hits its usage limit:
158
158
 
159
- - **Setup** — use the **Account rotation** card at Settings → **Command Code** to add accounts with a label and API key; the top-level key always serves first as the `default` account.
159
+ - **Setup** — use the **Account rotation** card at Settings → **Command Code** to add an account and label it. Paste its API key, or use the page's global **Save** button, then **Sign in to Command Code** and open the authorization link shown on that account's row in a new browser tab. The global Save/Discard bar stays inside the settings panel, attached to the bottom of its scrolling area. The top-level key always serves first as the `default` account.
160
160
  - **Manual switching** — the **Active account** dropdown pins a preferred account; if it is exhausted, requests fall back to other accounts and return once its window resets.
161
161
  - **Route models to accounts** — the **Route models to accounts** card picks catalog models (multi-select, fetched from the live catalog) and routes them to an account. A request whose model is in a rule serves from that account while it is usable; an exhausted or invalid routed account falls back to the normal rotation. Rules match in list order — the first hit wins.
162
162
  - **Show only favorite models** — the **Model allowlist** card keeps only the checked models in the model picker; unchecked shows all (the default).
package/README.zh-CN.md CHANGED
@@ -52,7 +52,7 @@
52
52
  **pnpm 11 会拦下刚发布的新版本。** 它的 `minimumReleaseAge` 默认为 1440 分钟,发布不足一天的版本会被跳过,`@latest` 解析到**上一个**版本 —— 而且是静默的,命令照样以成功退出。想装 24 小时内发布的版本,必须写精确版本号:
53
53
 
54
54
  ```sh
55
- dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.11.9
55
+ dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.11.11
56
56
  ```
57
57
 
58
58
  这一点对每个 profile 都成立,包括下面的终端界面。
@@ -71,7 +71,7 @@ dsh plugin --profile web update @mars-sea/dsh-commandcode-provider@0.9.1 #
71
71
  每个 profile 各自更新 —— 终端界面有独立的插件列表(见下文):
72
72
 
73
73
  ```sh
74
- dsh plugin --profile dsh-tui update @mars-sea/dsh-commandcode-provider@0.11.9
74
+ dsh plugin --profile dsh-tui update @mars-sea/dsh-commandcode-provider@0.11.11
75
75
  ```
76
76
 
77
77
  要更新到发布不足 24 小时的版本,和上面的安装一样写精确版本号;pnpm 11 的年龄门禁会把 `@latest` 解析成上一个版本。
@@ -100,7 +100,7 @@ cmd login # macOS/Linux;Windows 原生版:cmdc login
100
100
  插件同样支持终端前端。**每个 dsh profile 有独立的插件列表**,所以上面那条 Web 安装命令不会装到终端里 —— 还要把插件装进 `dsh-tui` profile:
101
101
 
102
102
  ```sh
103
- dsh plugin --profile dsh-tui add @mars-sea/dsh-commandcode-provider@0.11.9
103
+ dsh plugin --profile dsh-tui add @mars-sea/dsh-commandcode-provider@0.11.11
104
104
  ```
105
105
 
106
106
  这里请写精确版本号。新版本发布后的 24 小时内,只写包名(或 `@latest`)会被静默解析到上一个版本 —— 安装命令照样成功,但 profile 里拿到的是旧版本,结果就是全新的终端安装里既没有 **`/settings` → Command Code** 页面,也看不到任何 `commandcode` 模型。
@@ -154,7 +154,7 @@ cmd login # 写入 ~/.commandcode/auth.json
154
154
 
155
155
  有多个 Command Code 订阅时,插件可以在一个账户达到用量限额后**自动切换到下一个账户**:
156
156
 
157
- - **配置**:在 **设置 → Command Code** 的「多账户轮换」卡片添加账户并填写备注名与 API key;顶层 key 始终是第一顺位的 `default` 账户。
157
+ - **配置**:在 **设置 → Command Code** 的「多账户轮换」卡片添加账户并填写备注名。可直接填写 API key,或先点击页面底部的全局「保存」,再在该账户行点击「登录 Command Code」并打开出现的授权链接,在新网页授权获取 key;全局「放弃 / 保存」操作栏在设置面板内部随滚动吸附于底部。顶层 key 始终是第一顺位的 `default` 账户。
158
158
  - **手动切换**:卡片上的「当前使用账户」下拉框可指定优先账户;所选账户耗尽时自动回落到其他账户,窗口重置后自动恢复。
159
159
  - **按模型切换账户**:在「按模型切换账户」卡片从实时模型目录**多选**模型并固定到某个账户。请求的模型在规则列表中且该账户可用时使用该账户;账户耗尽或密钥失效时自动回落到常规轮换。规则按列表顺序匹配,第一条命中生效。
160
160
  - **只显示常用模型**:在「模型白名单」卡片勾选要保留的模型,模型选择器只列出这些;不勾选则显示全部(默认行为不变)。
package/lib/client.js CHANGED
@@ -1803,6 +1803,21 @@ window.__ModuleLoader__.load({
1803
1803
  };
1804
1804
  //#endregion
1805
1805
  //#region src/client/login.ts
1806
+ /** Keep one account's login status out of every other account's row. */
1807
+ function loginStateForTarget(state, targetRef) {
1808
+ if (state.targetRef === targetRef) return {
1809
+ visible: state,
1810
+ busyElsewhere: false
1811
+ };
1812
+ return {
1813
+ visible: {
1814
+ ...state,
1815
+ phase: "idle",
1816
+ authUrl: void 0
1817
+ },
1818
+ busyElsewhere: state.phase === "starting" || state.phase === "waiting"
1819
+ };
1820
+ }
1806
1821
  /** How often a live attempt is polled. */
1807
1822
  const POLL_INTERVAL_MS = 1e3;
1808
1823
  /**
@@ -1817,6 +1832,7 @@ window.__ModuleLoader__.load({
1817
1832
  generation = 0;
1818
1833
  disposed = false;
1819
1834
  phase = "idle";
1835
+ targetRef;
1820
1836
  authUrl;
1821
1837
  userName;
1822
1838
  keyName;
@@ -1835,6 +1851,7 @@ window.__ModuleLoader__.load({
1835
1851
  state() {
1836
1852
  return {
1837
1853
  phase: this.phase,
1854
+ ...this.targetRef === void 0 ? {} : { targetRef: this.targetRef },
1838
1855
  authUrl: this.authUrl,
1839
1856
  userName: this.userName,
1840
1857
  keyName: this.keyName,
@@ -1843,9 +1860,10 @@ window.__ModuleLoader__.load({
1843
1860
  };
1844
1861
  }
1845
1862
  /** Start (or rejoin) a login attempt and begin polling its status. */
1846
- async begin() {
1863
+ async begin(targetRef) {
1847
1864
  if (this.disposed || this.phase === "starting" || this.phase === "waiting") return;
1848
1865
  const generation = ++this.generation;
1866
+ this.targetRef = targetRef;
1849
1867
  this.set({
1850
1868
  phase: "starting",
1851
1869
  authUrl: void 0,
@@ -1868,7 +1886,7 @@ window.__ModuleLoader__.load({
1868
1886
  }
1869
1887
  let result;
1870
1888
  try {
1871
- result = await remote.loginBegin();
1889
+ result = await remote.loginBegin(targetRef);
1872
1890
  } catch (error) {
1873
1891
  result = {
1874
1892
  ok: false,
@@ -2106,6 +2124,15 @@ window.__ModuleLoader__.load({
2106
2124
  booleanField
2107
2125
  };
2108
2126
  }
2127
+ /** Use the same strict-codec shape for invocation parameters and results. */
2128
+ function makeStrictCodec(typeSymbol, schema) {
2129
+ return {
2130
+ mode: "strict",
2131
+ typeSymbol,
2132
+ schema,
2133
+ create: () => schema
2134
+ };
2135
+ }
2109
2136
  /**
2110
2137
  * Build one strict invocation descriptor. Every Command Code Remote shares the
2111
2138
  * `commandcode` namespace, the `commandcodeUsage` service, and a strict
@@ -2114,12 +2141,7 @@ window.__ModuleLoader__.load({
2114
2141
  * only its own facts.
2115
2142
  */
2116
2143
  function makeRemoteDescriptor(endpoint, method, typeSymbol, schema) {
2117
- const result = {
2118
- mode: "strict",
2119
- typeSymbol,
2120
- schema,
2121
- create: () => schema
2122
- };
2144
+ const result = makeStrictCodec(typeSymbol, schema);
2123
2145
  return {
2124
2146
  id: `${REMOTE_PACKAGE}#${endpoint}`,
2125
2147
  service: REMOTE_SERVICE,
@@ -2172,7 +2194,7 @@ window.__ModuleLoader__.load({
2172
2194
  const report = { failures };
2173
2195
  if (source.blocked !== void 0) {
2174
2196
  const blocked = source.blocked;
2175
- if (blocked === "invalid-key" || blocked === "service-unavailable" || blocked === "network") report.blocked = blocked;
2197
+ if (blocked === "invalid-key" || blocked === "service-unavailable" || blocked === "invalid-response" || blocked === "network") report.blocked = blocked;
2176
2198
  else reject$1("blocked");
2177
2199
  }
2178
2200
  if (source.account !== void 0) {
@@ -2388,9 +2410,24 @@ window.__ModuleLoader__.load({
2388
2410
  }
2389
2411
  /** The strict result codec shared by all three login endpoints. */
2390
2412
  const loginStatusSchema = { parse: parseLoginStatus };
2391
- /** Build one login invocation descriptor (uniform result, no parameters). */
2413
+ /** Optional account target; the Host still checks it against saved slots. */
2414
+ const loginTargetSchema = { parse(value) {
2415
+ if (value === void 0 || typeof value === "string") return value;
2416
+ return reject("targetRef");
2417
+ } };
2418
+ /** Build one login invocation descriptor. Only begin accepts an account ref. */
2392
2419
  function loginDescriptor(endpoint, method) {
2393
- return makeRemoteDescriptor(endpoint, method, `${REMOTE_PACKAGE}#CommandCodeLoginStatus`, loginStatusSchema);
2420
+ const descriptor = makeRemoteDescriptor(endpoint, method, `${REMOTE_PACKAGE}#CommandCodeLoginStatus`, loginStatusSchema);
2421
+ return method === "loginBegin" ? {
2422
+ ...descriptor,
2423
+ parameters: [{
2424
+ name: "targetRef",
2425
+ wire: "targetRef",
2426
+ source: "json",
2427
+ codec: makeStrictCodec(`${REMOTE_PACKAGE}#CommandCodeLoginTargetRef`, loginTargetSchema),
2428
+ acceptsUndefined: true
2429
+ }]
2430
+ } : descriptor;
2394
2431
  }
2395
2432
  /** The Client-face contribution fragment mounted on `ctx.remote`. */
2396
2433
  const LOGIN_REMOTE_CONTRIBUTION = {
@@ -2404,9 +2441,10 @@ window.__ModuleLoader__.load({
2404
2441
  //#endregion
2405
2442
  //#region src/client/login-row.tsx
2406
2443
  /** The sign-in alternative to pasting a key (settings page + Models card). */
2407
- function LoginRow({ state, disabled, t, onBegin, onCancel }) {
2408
- const busy = state.phase === "starting" || state.phase === "waiting";
2409
- const hint = loginHint(state, t);
2444
+ function LoginRow({ state, targetRef, disabled, disabledHint, t, onBegin, onCancel }) {
2445
+ const { visible: visibleState, busyElsewhere } = loginStateForTarget(state, targetRef);
2446
+ const busy = visibleState.phase === "starting" || visibleState.phase === "waiting";
2447
+ const hint = loginHint(visibleState, t);
2410
2448
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2411
2449
  className: "cc-field",
2412
2450
  children: [
@@ -2425,17 +2463,17 @@ window.__ModuleLoader__.load({
2425
2463
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2426
2464
  type: "button",
2427
2465
  className: "cc-reset",
2428
- disabled,
2466
+ disabled: disabled || busyElsewhere,
2429
2467
  onClick: onBegin,
2430
2468
  children: t("loginButton")
2431
2469
  })
2432
2470
  })]
2433
2471
  }),
2434
- state.authUrl !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2472
+ visibleState.authUrl !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2435
2473
  className: "cc-hint",
2436
2474
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
2437
2475
  className: "cc-loginLink",
2438
- href: state.authUrl,
2476
+ href: visibleState.authUrl,
2439
2477
  target: "_blank",
2440
2478
  rel: "noreferrer",
2441
2479
  children: t("loginOpenLink")
@@ -2444,7 +2482,7 @@ window.__ModuleLoader__.load({
2444
2482
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2445
2483
  className: hint.className,
2446
2484
  title: hint.title,
2447
- children: hint.text
2485
+ children: disabled && !busy && disabledHint !== void 0 ? disabledHint : hint.text
2448
2486
  })
2449
2487
  ]
2450
2488
  });
@@ -2593,7 +2631,7 @@ window.__ModuleLoader__.load({
2593
2631
  }
2594
2632
  //#endregion
2595
2633
  //#region package.json
2596
- var version = "0.11.10";
2634
+ var version = "0.11.11";
2597
2635
  var repository = {
2598
2636
  "type": "git",
2599
2637
  "url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
@@ -3398,12 +3436,14 @@ window.__ModuleLoader__.load({
3398
3436
  function blockedTitle(reason, t) {
3399
3437
  if (reason === "invalid-key") return t("usageKeyInvalid");
3400
3438
  if (reason === "service-unavailable") return t("usageServiceUnavailable");
3439
+ if (reason === "invalid-response") return t("usageInvalidResponse");
3401
3440
  return t("usageNetworkError");
3402
3441
  }
3403
3442
  /** The actionable hint under a blocked report's headline. */
3404
3443
  function blockedHint(reason, t) {
3405
3444
  if (reason === "invalid-key") return t("usageKeyInvalidHint");
3406
3445
  if (reason === "service-unavailable") return t("usageServiceUnavailableHint");
3446
+ if (reason === "invalid-response") return t("usageInvalidResponseHint");
3407
3447
  return t("usageNetworkHint");
3408
3448
  }
3409
3449
  /** The status dot on an account tab: cooling/invalid warn, everything else ok. */
@@ -3594,7 +3634,7 @@ window.__ModuleLoader__.load({
3594
3634
  });
3595
3635
  }
3596
3636
  /** The multi-account card: the active-account selector + extra accounts in rotation order + add button. */
3597
- function AccountsCard({ t, state, disabled, onAdd, onRemove, onLabel, onKey, onToggleClear, onActive, onActiveReset }) {
3637
+ function AccountsCard({ t, state, login, disabled, onAdd, onRemove, onLabel, onKey, onToggleClear, onActive, onActiveReset, onBeginLogin, onCancelLogin }) {
3598
3638
  const active = state.activeAccount;
3599
3639
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3600
3640
  className: "cc-card",
@@ -3672,14 +3712,31 @@ window.__ModuleLoader__.load({
3672
3712
  })
3673
3713
  ]
3674
3714
  }),
3675
- state.accounts.map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountRow, {
3676
- account,
3677
- disabled,
3678
- t,
3679
- onLabel: (text) => onLabel(account.id, text),
3680
- onKey: (text) => onKey(account.id, text),
3681
- onToggleClear: () => onToggleClear(account.id),
3682
- onRemove: () => onRemove(account.id)
3715
+ state.accounts.map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3716
+ className: "cc-accountEditor",
3717
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountRow, {
3718
+ account,
3719
+ disabled: disabled || login.targetRef === account.ref && (login.phase === "starting" || login.phase === "waiting"),
3720
+ t,
3721
+ onLabel: (text) => onLabel(account.id, text),
3722
+ onKey: (text) => onKey(account.id, text),
3723
+ onToggleClear: () => onToggleClear(account.id),
3724
+ onRemove: () => onRemove(account.id)
3725
+ }), account.added ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3726
+ className: "cc-accountLoginHint",
3727
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3728
+ className: "cc-hint",
3729
+ children: t("accountLoginAfterSave")
3730
+ })
3731
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginRow, {
3732
+ state: login,
3733
+ targetRef: account.ref,
3734
+ disabled: disabled || !account.writable || state.dirty,
3735
+ disabledHint: state.dirty ? t("loginSaveBefore") : void 0,
3736
+ t,
3737
+ onBegin: () => onBeginLogin(account.ref),
3738
+ onCancel: onCancelLogin
3739
+ })]
3683
3740
  }, account.id))
3684
3741
  ]
3685
3742
  });
@@ -3991,6 +4048,8 @@ window.__ModuleLoader__.load({
3991
4048
  /** The settings page body: connection facts for the Command Code provider. */
3992
4049
  function CommandCodeSettingsPage(props) {
3993
4050
  const { t } = props;
4051
+ const footerMarker = (0, react.useRef)(null);
4052
+ const [footerFloating, setFooterFloating] = (0, react.useState)(false);
3994
4053
  const state = props.useCommandCodeSettings((snapshot) => snapshot);
3995
4054
  const usage = props.useCommandCodeUsage((snapshot) => snapshot);
3996
4055
  const login = props.useCommandCodeLogin((snapshot) => snapshot);
@@ -3998,6 +4057,15 @@ window.__ModuleLoader__.load({
3998
4057
  const keyLocked = !state.apiKeyWritable;
3999
4058
  const savedVisible = useSavedFlash(state.savedCount);
4000
4059
  const updateVersion = usePluginUpdate();
4060
+ (0, react.useEffect)(() => {
4061
+ const marker = footerMarker.current;
4062
+ if (!marker || typeof IntersectionObserver === "undefined") return;
4063
+ const observer = new IntersectionObserver(([entry]) => {
4064
+ if (entry) setFooterFloating(!entry.isIntersecting);
4065
+ });
4066
+ observer.observe(marker);
4067
+ return () => observer.disconnect();
4068
+ }, []);
4001
4069
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4002
4070
  className: "cc-section",
4003
4071
  "aria-label": t("title"),
@@ -4027,6 +4095,7 @@ window.__ModuleLoader__.load({
4027
4095
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountsCard, {
4028
4096
  t,
4029
4097
  state,
4098
+ login,
4030
4099
  disabled,
4031
4100
  onAdd: props.addAccount,
4032
4101
  onRemove: props.removeAccount,
@@ -4034,7 +4103,9 @@ window.__ModuleLoader__.load({
4034
4103
  onKey: props.editAccountKey,
4035
4104
  onToggleClear: (id) => props.toggleKeyClear(id),
4036
4105
  onActive: (text) => props.edit("activeAccount", text),
4037
- onActiveReset: () => props.resetField("activeAccount")
4106
+ onActiveReset: () => props.resetField("activeAccount"),
4107
+ onBeginLogin: props.beginLogin,
4108
+ onCancelLogin: props.cancelLogin
4038
4109
  }),
4039
4110
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RulesCard, {
4040
4111
  t,
@@ -4058,7 +4129,7 @@ window.__ModuleLoader__.load({
4058
4129
  label: t("apiKey"),
4059
4130
  hint: keyLocked ? t("apiKeyLocked") : t("apiKeyHint"),
4060
4131
  state: state.apiKey,
4061
- disabled: disabled || keyLocked,
4132
+ disabled: disabled || keyLocked || login.targetRef === void 0 && (login.phase === "starting" || login.phase === "waiting"),
4062
4133
  configured: state.apiKeyConfigured,
4063
4134
  configuredLabel: t("apiKeySet"),
4064
4135
  unconfiguredLabel: t("apiKeyUnset"),
@@ -4072,7 +4143,8 @@ window.__ModuleLoader__.load({
4072
4143
  onToggleClear: () => props.toggleKeyClear("default")
4073
4144
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginRow, {
4074
4145
  state: login,
4075
- disabled: disabled || keyLocked,
4146
+ disabled: disabled || keyLocked || state.dirty,
4147
+ disabledHint: state.dirty ? t("loginSaveBefore") : void 0,
4076
4148
  t,
4077
4149
  onBegin: props.beginLogin,
4078
4150
  onCancel: props.cancelLogin
@@ -4085,34 +4157,42 @@ window.__ModuleLoader__.load({
4085
4157
  onEdit: props.edit,
4086
4158
  onReset: props.resetField
4087
4159
  }),
4088
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4089
- className: "cc-footer",
4090
- children: [
4091
- state.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4092
- className: "cc-failed",
4093
- role: "status",
4094
- children: t("saveFailed")
4095
- }) : null,
4096
- savedVisible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4097
- className: "cc-saved",
4098
- role: "status",
4099
- children: t("saved")
4100
- }) : null,
4101
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4102
- variant: "ghost",
4103
- size: "sm",
4104
- disabled: !state.dirty || state.saving,
4105
- onClick: props.discard,
4106
- children: t("discard")
4107
- }),
4108
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4109
- variant: "primary",
4110
- size: "sm",
4111
- disabled: !state.dirty || state.invalid || state.saving,
4112
- onClick: props.save,
4113
- children: t(state.saving ? "saving" : "save")
4114
- })
4115
- ]
4160
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4161
+ ref: footerMarker,
4162
+ className: "cc-footerMarker",
4163
+ "aria-hidden": "true"
4164
+ }),
4165
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4166
+ className: `cc-footer cc-footerSticky${footerFloating ? " cc-footerFloating" : ""}`,
4167
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4168
+ className: "cc-footerActions",
4169
+ children: [
4170
+ state.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4171
+ className: "cc-failed",
4172
+ role: "status",
4173
+ children: t("saveFailed")
4174
+ }) : null,
4175
+ savedVisible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4176
+ className: "cc-saved",
4177
+ role: "status",
4178
+ children: t("saved")
4179
+ }) : null,
4180
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4181
+ variant: "ghost",
4182
+ size: "sm",
4183
+ disabled: !state.dirty || state.saving,
4184
+ onClick: props.discard,
4185
+ children: t("discard")
4186
+ }),
4187
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4188
+ variant: "primary",
4189
+ size: "sm",
4190
+ disabled: !state.dirty || state.invalid || state.saving,
4191
+ onClick: props.save,
4192
+ children: t(state.saving ? "saving" : "save")
4193
+ })
4194
+ ]
4195
+ })
4116
4196
  }),
4117
4197
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
4118
4198
  className: "cc-version",
@@ -4399,6 +4479,8 @@ window.__ModuleLoader__.load({
4399
4479
  errorServiceUnavailableHint: "服务端返回错误(5xx),请稍后点击刷新重试。",
4400
4480
  errorNetwork: "无法连接 Command Code 服务",
4401
4481
  errorNetworkHint: "所有请求都没有到达服务端。请检查网络连接或 API 地址设置。",
4482
+ errorInvalidResponse: "Command Code 响应无法读取",
4483
+ errorInvalidResponseHint: "请求已收到响应,但内容无法解析。请检查宿主的 HTTP 代理或响应解压配置。",
4402
4484
  errorGeneric: "用量获取失败"
4403
4485
  };
4404
4486
  /** The panel's English string table (must mirror {@link PANEL_COPY_ZH}). */
@@ -4451,6 +4533,8 @@ window.__ModuleLoader__.load({
4451
4533
  errorServiceUnavailableHint: "The server returned errors (5xx). Try Refresh again in a moment.",
4452
4534
  errorNetwork: "Could not reach the Command Code service",
4453
4535
  errorNetworkHint: "No request reached the server. Check your network connection or the API base setting.",
4536
+ errorInvalidResponse: "Could not read the Command Code response",
4537
+ errorInvalidResponseHint: "The requests received responses, but their bodies could not be parsed. Check the host HTTP proxy or response decoding.",
4454
4538
  errorGeneric: "Could not fetch account usage"
4455
4539
  };
4456
4540
  /** Every panel key, in the English table's declaration order. */
@@ -4631,6 +4715,11 @@ window.__ModuleLoader__.load({
4631
4715
  hint: "errorNetworkHint",
4632
4716
  detail
4633
4717
  };
4718
+ if (blocked === "invalid-response") return {
4719
+ title: "errorInvalidResponse",
4720
+ hint: "errorInvalidResponseHint",
4721
+ detail
4722
+ };
4634
4723
  if (state.status === "error" && state.report === void 0) return {
4635
4724
  title: "errorGeneric",
4636
4725
  hint: "errorGeneric",
@@ -6235,6 +6324,13 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6235
6324
  .cc-advancedBody>.cc-field:first-of-type{border-top:1px solid var(--dsw-alias-border-l2)}
6236
6325
  .cc-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
6237
6326
  .cc-footer{justify-content:flex-end;align-items:center;gap:8px;display:flex}
6327
+ /* Keep the original footer in flow and inside the settings scrollport. */
6328
+ .cc-footerMarker{height:1px;margin:-6px 0 -7px;pointer-events:none}
6329
+ .cc-footerSticky{position:sticky;bottom:0;z-index:2;flex-shrink:0;min-height:48px;pointer-events:none}
6330
+ .cc-footerActions{align-items:center;gap:8px;display:flex;pointer-events:auto}
6331
+ .cc-footerFloating .cc-footerActions{padding:6px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-layer-2));box-shadow:0 8px 24px rgba(0,0,0,.12)}
6332
+ .cc-accountEditor{border-top:1px solid var(--dsw-alias-border-l2)}
6333
+ .cc-accountLoginHint{padding:0 0 12px}
6238
6334
  .cc-toggleRow{align-items:center;gap:8px;cursor:pointer;display:flex}
6239
6335
  .cc-toggleRow:has(.cc-toggle:disabled){cursor:default}
6240
6336
  .cc-toggle{appearance:none;flex-shrink:0;background:var(--dsw-alias-border-l2);border-radius:999px;width:30px;height:18px;margin:0;cursor:pointer;position:relative;transition:background .15s ease}
@@ -6526,6 +6622,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6526
6622
  accountLabel: "账户备注名",
6527
6623
  accountKey: "API 密钥",
6528
6624
  accountKeyHint: "该账户的 API 密钥。留空保存不会覆盖已存储的密钥。",
6625
+ accountLoginAfterSave: "先点击页面底部的「保存」,再点击该账户的「登录 Command Code」通过网页获取密钥。",
6529
6626
  accountDefault: "默认账户",
6530
6627
  activeAccount: "当前使用账户",
6531
6628
  activeAccountAuto: "自动(第一个可用账户)",
@@ -6596,6 +6693,8 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6596
6693
  usageServiceUnavailableHint: "服务端返回了错误(5xx),稍后点击刷新重试。",
6597
6694
  usageNetworkError: "无法连接 Command Code 服务",
6598
6695
  usageNetworkHint: "所有请求都没有到达服务端。请检查网络连接或 API 地址设置。",
6696
+ usageInvalidResponse: "Command Code 响应无法读取",
6697
+ usageInvalidResponseHint: "请求已收到响应,但内容无法解析。请检查宿主的 HTTP 代理或响应解压配置。",
6599
6698
  usageUpdated: "更新于",
6600
6699
  usagePeriodEnd: "账期截止",
6601
6700
  usageActive: "当前使用",
@@ -6606,6 +6705,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6606
6705
  updateHint: "已发布新版本,点击查看发布说明;更新插件后刷新本页,提示会自动消失。",
6607
6706
  loginTitle: "通过官方登录获取密钥",
6608
6707
  loginHintIdle: "不想手动创建密钥?点击登录后浏览器会打开 commandcode.ai 授权页,完成后密钥自动写入本机凭据服务,下次请求即生效。",
6708
+ loginSaveBefore: "请先保存本页更改,再通过网页登录此账户。",
6609
6709
  loginButton: "登录 Command Code",
6610
6710
  loginStarting: "正在启动本地回调服务…",
6611
6711
  loginWaiting: "等待在浏览器中完成授权…",
@@ -6670,6 +6770,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6670
6770
  accountLabel: "Account label",
6671
6771
  accountKey: "API key",
6672
6772
  accountKeyHint: "This account’s API key. Saving with the field blank keeps the stored key.",
6773
+ accountLoginAfterSave: "Use Save at the bottom of the page, then sign in to this account in your browser to obtain a key.",
6673
6774
  accountDefault: "Default account",
6674
6775
  activeAccount: "Active account",
6675
6776
  activeAccountAuto: "Auto (first usable account)",
@@ -6740,6 +6841,8 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6740
6841
  usageServiceUnavailableHint: "The server returned errors (5xx); try Refresh again later.",
6741
6842
  usageNetworkError: "Could not reach the Command Code service",
6742
6843
  usageNetworkHint: "No request reached the server. Check your network connection or the API base setting.",
6844
+ usageInvalidResponse: "Could not read the Command Code response",
6845
+ usageInvalidResponseHint: "The requests received responses, but their bodies could not be parsed. Check the host HTTP proxy or response decoding.",
6743
6846
  usageUpdated: "Updated",
6744
6847
  usagePeriodEnd: "Period ends",
6745
6848
  usageActive: "Active",
@@ -6750,6 +6853,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6750
6853
  updateHint: "A newer version has been published; click for release notes. The notice disappears once the plugin is updated.",
6751
6854
  loginTitle: "Sign in to fetch a key",
6752
6855
  loginHintIdle: "Rather not create a key by hand? Sign in and your browser opens the commandcode.ai authorization page; the approved key is stored in the local credential service and applies to the next request.",
6856
+ loginSaveBefore: "Save page changes before signing in to this account.",
6753
6857
  loginButton: "Sign in to Command Code",
6754
6858
  loginStarting: "Starting the local callback server…",
6755
6859
  loginWaiting: "Waiting for authorization in your browser…",
@@ -6964,12 +7068,12 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6964
7068
  const pricesStore = createSnapshotStore(pricesController.state());
6965
7069
  pricesController.subscribe(() => pricesStore.set(pricesController.state()));
6966
7070
  const loginRemote = {
6967
- loginBegin: async () => {
7071
+ loginBegin: async (targetRef) => {
6968
7072
  if (usageNamespace === void 0) return {
6969
7073
  ok: false,
6970
7074
  error: { message: usageMountError ?? "commandcode remote is not mounted" }
6971
7075
  };
6972
- return usageNamespace.loginBegin();
7076
+ return usageNamespace.loginBegin(targetRef);
6973
7077
  },
6974
7078
  loginStatus: async () => {
6975
7079
  if (usageNamespace === void 0) return {
@@ -7013,7 +7117,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
7013
7117
  }),
7014
7118
  discard: () => controller.discard(),
7015
7119
  refreshUsage: () => void usageController.refresh(),
7016
- beginLogin: () => void loginController.begin(),
7120
+ beginLogin: (targetRef) => void loginController.begin(targetRef),
7017
7121
  cancelLogin: () => void loginController.cancel(),
7018
7122
  addAccount: () => controller.addAccount(),
7019
7123
  removeAccount: (id) => controller.removeAccount(id),