@mars-sea/dsh-commandcode-provider 0.6.0 → 0.6.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
@@ -4,6 +4,20 @@ 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.6.1] - 2026-08-22
8
+
9
+ ### Added
10
+
11
+ - **New model: Ox Alpha (`stealth/ox-alpha`, command-code@1.31.0).** A free 1M-context reasoning model with vision input, included on every plan: it appears in the picker under the Go tier with `FREE` · `Image` · `1M` markers, is whitelisted as vision-capable and auto-thinking (it reasons automatically with no selectable effort levels), and carries a `FREE` deal while the stealth preview lasts.
12
+ - **The settings page now shows the plugin version** as a muted footer line ("Command Code Provider v0.6.1"), read from package.json at build time so it always matches the published release.
13
+ - **Settings-page input polish:** a brief "Saved ✓" confirmation flashes after each accepted save (failures already showed an error); the two millisecond timeout fields now validate their range while typing with specific messages ("must be at least 1" / "above the allowed maximum") instead of a generic save-time failure; and the API-key fields (default + every account) gained a Show/Hide toggle so a pasted key can be spot-checked.
14
+
15
+ ### Changed
16
+
17
+ - **Retries are now persistent (opencode-style) instead of two attempts.** Transient failures — rate limits, server errors, timeouts, transport drops, empty responses — retry up to 1000 times at the agent-step boundary, with waits doubling from 500 ms and capping at 15 minutes (±10% jitter). Permanent failures (an invalid key, unsupported content, plan rejections) still fail fast on the first attempt. Waits are smart: a 429's `Retry-After` header and the rotation pool's earliest known window-reset time are honored verbatim up to the 15-minute cap, so when every account's usage window is exhausted the session sleeps through the window and recovers in place instead of dying after two quick tries.
18
+ - **Synced with the official command-code@1.31.0 CLI** (upstream moved 1.28.4 → 1.29.0 → 1.30.0 → 1.30.1 → 1.31.0; the intermediate releases shipped BYOK provider support, model-traffic routing, and CLI UI fixes — none touch the Provider API). `COMMAND_CODE_CLI_VERSION` is now `1.31.0`. The only snapshot change across those releases is the new Ox Alpha model above; wire protocol, endpoints, effort map, plan maps, deals, and peak pricing are all unchanged.
19
+ - **Verified compatibility with DeepSeek Harness 0.1.0-rc.7 / 0.1.0-rc.8 / 0.1.1-rc.1**, including rc.8's reworked client boot protocol: the client bundle now declares its module-graph dependency (`dsh.client.external`), so client-module load order no longer relies on the platform seed table. The supported peer range widened to `^0.1.0-rc.6 || ^0.1.1-rc.1`, so 0.1.1 hosts install without unmet-peer-dependency warnings.
20
+
7
21
  ## [0.6.0] - 2026-08-19
8
22
 
9
23
  ### Changed
package/lib/client.js CHANGED
@@ -59,8 +59,15 @@ window.__ModuleLoader__.load({
59
59
  }
60
60
  };
61
61
  }
62
- /** A whole-number field; an empty draft clears it, anything non-numeric blocks save. */
63
- function numberField$1(field) {
62
+ /**
63
+ * A numeric field; an empty draft clears it, anything non-numeric blocks
64
+ * save, and an optional inclusive `bounds` range rejects out-of-range values
65
+ * with a specific reason (the Host schema would reject them at save time with
66
+ * only a generic failure — catching it here names the problem while typing).
67
+ * Decimals pass: the Host schema is `z.number()` too, and a fractional
68
+ * millisecond value is harmless even if pointless.
69
+ */
70
+ function numberField$1(field, bounds) {
64
71
  return {
65
72
  field,
66
73
  format: (value) => typeof value === "number" ? String(value) : "",
@@ -68,10 +75,22 @@ window.__ModuleLoader__.load({
68
75
  const trimmed = text.trim();
69
76
  if (trimmed === "") return { kind: "clear" };
70
77
  const parsed = Number(trimmed);
71
- return Number.isFinite(parsed) ? {
78
+ if (!Number.isFinite(parsed)) return {
79
+ kind: "invalid",
80
+ reason: "format"
81
+ };
82
+ if (bounds?.min !== void 0 && parsed < bounds.min) return {
83
+ kind: "invalid",
84
+ reason: "tooSmall"
85
+ };
86
+ if (bounds?.max !== void 0 && parsed > bounds.max) return {
87
+ kind: "invalid",
88
+ reason: "tooLarge"
89
+ };
90
+ return {
72
91
  kind: "set",
73
92
  value: parsed
74
- } : { kind: "invalid" };
93
+ };
75
94
  }
76
95
  };
77
96
  }
@@ -95,16 +114,26 @@ window.__ModuleLoader__.load({
95
114
  kind: "set",
96
115
  value: false
97
116
  };
98
- return { kind: "invalid" };
117
+ return {
118
+ kind: "invalid",
119
+ reason: "format"
120
+ };
99
121
  }
100
122
  };
101
123
  }
124
+ const MAX_TIMEOUT_MS = 2147483647;
102
125
  /** The fields this page edits inside the `llm-commandcode` namespace. */
103
126
  const SECTION_FIELDS = [
104
127
  textField("apiBase"),
105
128
  textField("workingDir"),
106
- numberField$1("requestTimeoutMs"),
107
- numberField$1("streamIdleTimeoutMs"),
129
+ numberField$1("requestTimeoutMs", {
130
+ min: 1,
131
+ max: MAX_TIMEOUT_MS
132
+ }),
133
+ numberField$1("streamIdleTimeoutMs", {
134
+ min: 1,
135
+ max: MAX_TIMEOUT_MS
136
+ }),
108
137
  booleanField$1("filterModelsByPlan"),
109
138
  textField("activeAccount")
110
139
  ];
@@ -136,6 +165,7 @@ window.__ModuleLoader__.load({
136
165
  keyDrafts = /* @__PURE__ */ new Map();
137
166
  saving = false;
138
167
  failed = false;
168
+ savedCount = 0;
139
169
  /**
140
170
  * @param scope - bound scope for the `llm-commandcode` namespace.
141
171
  * @param api - credentials wire face.
@@ -206,7 +236,8 @@ window.__ModuleLoader__.load({
206
236
  text: this.staged.get("apiKey")?.text ?? "",
207
237
  clear: false,
208
238
  overridden: false,
209
- invalid: false
239
+ invalid: false,
240
+ invalidReason: void 0
210
241
  },
211
242
  apiBase: this.field("apiBase"),
212
243
  workingDir: this.field("workingDir"),
@@ -220,7 +251,8 @@ window.__ModuleLoader__.load({
220
251
  dirty: plan.length > 0 || this.accountsDirty(),
221
252
  invalid: plan.some((item) => item.run === void 0),
222
253
  saving: this.saving,
223
- failed: this.failed
254
+ failed: this.failed,
255
+ savedCount: this.savedCount
224
256
  };
225
257
  }
226
258
  /** Stage a new extra account (saved on the next `save()`). */
@@ -322,6 +354,7 @@ window.__ModuleLoader__.load({
322
354
  this.saving = false;
323
355
  this.failed = !landed;
324
356
  if (landed) {
357
+ this.savedCount += 1;
325
358
  this.staged.clear();
326
359
  this.clearAccountStaging();
327
360
  } else this.reconcileAccountStaging();
@@ -356,14 +389,16 @@ window.__ModuleLoader__.load({
356
389
  text: spec.format(this.sectionValue(field)),
357
390
  clear: false,
358
391
  overridden: this.stored(field),
359
- invalid: false
392
+ invalid: false,
393
+ invalidReason: void 0
360
394
  };
361
395
  const parsed = staged.clear ? { kind: "clear" } : spec.parse(staged.text);
362
396
  return {
363
397
  text: staged.text,
364
398
  clear: staged.clear,
365
399
  overridden: parsed.kind === "set",
366
- invalid: parsed.kind === "invalid"
400
+ invalid: parsed.kind === "invalid",
401
+ invalidReason: parsed.kind === "invalid" ? parsed.reason : void 0
367
402
  };
368
403
  }
369
404
  sectionValue(field) {
@@ -809,6 +844,21 @@ window.__ModuleLoader__.load({
809
844
  }]
810
845
  };
811
846
  //#endregion
847
+ //#region src/client/version.ts
848
+ /**
849
+ * The plugin's own version, read from package.json at build time.
850
+ *
851
+ * The client bundle inlines the JSON import (rolldown resolves it during the
852
+ * tsdown build; node tests read it through tsx), so the rendered value always
853
+ * matches the published package version with no second constant to keep in
854
+ * sync. Rendered as a muted footer line on the settings page so a user can
855
+ * report the exact build they run.
856
+ *
857
+ * @module dsh-commandcode-provider/client/version
858
+ */
859
+ /** The published package version (e.g. `'0.6.0'`). */
860
+ const PLUGIN_VERSION = "0.6.1";
861
+ //#endregion
812
862
  //#region src/client/section.tsx
813
863
  /**
814
864
  * React component for the "Command Code" settings page (browser half).
@@ -861,11 +911,17 @@ window.__ModuleLoader__.load({
861
911
  }),
862
912
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
863
913
  className: state.invalid ? "cc-invalid" : "cc-hint",
864
- children: state.invalid ? t("invalidNumber") : hint
914
+ children: state.invalid ? invalidCopy(state.invalidReason, t) : hint
865
915
  })
866
916
  ]
867
917
  });
868
918
  }
919
+ /** The per-field error copy for a staged draft's failure reason. */
920
+ function invalidCopy(reason, t) {
921
+ if (reason === "tooSmall") return t("numberTooSmall");
922
+ if (reason === "tooLarge") return t("numberTooLarge");
923
+ return t("invalidNumber");
924
+ }
869
925
  /**
870
926
  * One boolean field row rendered as a toggle. The staged text is `'true'` /
871
927
  * `'false'` / `''` (unset → `defaultChecked`); toggling stages the string the
@@ -911,8 +967,13 @@ window.__ModuleLoader__.load({
911
967
  })]
912
968
  });
913
969
  }
914
- /** The API-key control: write-only, reports configured state, never echoes the key. */
915
- function SecretKeyField({ label, hint, state, disabled, configured, configuredLabel, unconfiguredLabel, onEdit }) {
970
+ /**
971
+ * The API-key control: write-only, reports configured state, never echoes the
972
+ * key. The input is masked by default with a Show/Hide toggle so a pasted key
973
+ * can be spot-checked without leaving the field.
974
+ */
975
+ function SecretKeyField({ label, hint, state, disabled, configured, configuredLabel, unconfiguredLabel, showLabel, hideLabel, onEdit }) {
976
+ const [visible, setVisible] = (0, react.useState)(false);
916
977
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
917
978
  className: "cc-field",
918
979
  children: [
@@ -922,19 +983,26 @@ window.__ModuleLoader__.load({
922
983
  className: "cc-label",
923
984
  htmlFor: "cc-api-key",
924
985
  children: label
925
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
986
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
926
987
  className: "cc-badges",
927
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
988
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
928
989
  className: configured ? "cc-badge" : "cc-badgeMuted",
929
990
  children: configured ? configuredLabel : unconfiguredLabel
930
- })
991
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
992
+ type: "button",
993
+ className: "cc-reset",
994
+ disabled,
995
+ onClick: () => setVisible((value) => !value),
996
+ children: visible ? hideLabel : showLabel
997
+ })]
931
998
  })]
932
999
  }),
933
1000
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
934
1001
  id: "cc-api-key",
935
1002
  className: "cc-input",
936
- type: "password",
1003
+ type: visible ? "text" : "password",
937
1004
  autoComplete: "off",
1005
+ spellCheck: false,
938
1006
  value: state.text,
939
1007
  disabled,
940
1008
  onChange: (event) => onEdit(event.target.value)
@@ -1287,6 +1355,7 @@ window.__ModuleLoader__.load({
1287
1355
  */
1288
1356
  function AccountRow({ account, disabled, t, onLabel, onKey, onRemove }) {
1289
1357
  const locked = !account.writable;
1358
+ const [keyVisible, setKeyVisible] = (0, react.useState)(false);
1290
1359
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1291
1360
  className: "cc-field",
1292
1361
  children: [
@@ -1307,6 +1376,13 @@ window.__ModuleLoader__.load({
1307
1376
  className: account.configured ? "cc-badge" : "cc-badgeMuted",
1308
1377
  children: account.configured ? t("apiKeySet") : t("apiKeyUnset")
1309
1378
  }),
1379
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1380
+ type: "button",
1381
+ className: "cc-reset",
1382
+ disabled,
1383
+ onClick: () => setKeyVisible((value) => !value),
1384
+ children: keyVisible ? t("hide") : t("show")
1385
+ }),
1310
1386
  account.added ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1311
1387
  type: "button",
1312
1388
  className: "cc-reset",
@@ -1328,8 +1404,9 @@ window.__ModuleLoader__.load({
1328
1404
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1329
1405
  id: `cc-account-key-${account.id}`,
1330
1406
  className: "cc-input",
1331
- type: "password",
1407
+ type: keyVisible ? "text" : "password",
1332
1408
  autoComplete: "off",
1409
+ spellCheck: false,
1333
1410
  placeholder: t("accountKey"),
1334
1411
  value: account.keyText,
1335
1412
  disabled: disabled || locked,
@@ -1432,6 +1509,21 @@ window.__ModuleLoader__.load({
1432
1509
  ]
1433
1510
  });
1434
1511
  }
1512
+ /**
1513
+ * Show the "Saved ✓" affordance for a short window after each accepted save.
1514
+ * The controller only counts saves (`savedCount`); the flash timing lives
1515
+ * here so the state machine stays timer-free.
1516
+ */
1517
+ function useSavedFlash(tick) {
1518
+ const [visible, setVisible] = (0, react.useState)(false);
1519
+ (0, react.useEffect)(() => {
1520
+ if (tick === 0) return;
1521
+ setVisible(true);
1522
+ const timer = setTimeout(() => setVisible(false), 2500);
1523
+ return () => clearTimeout(timer);
1524
+ }, [tick]);
1525
+ return visible;
1526
+ }
1435
1527
  /** The settings page body: connection facts for the Command Code provider. */
1436
1528
  function CommandCodeSettingsPage(props) {
1437
1529
  const { t } = props;
@@ -1439,6 +1531,7 @@ window.__ModuleLoader__.load({
1439
1531
  const usage = props.useCommandCodeUsage((snapshot) => snapshot);
1440
1532
  const disabled = !state.writable;
1441
1533
  const keyLocked = !state.apiKeyWritable;
1534
+ const savedVisible = useSavedFlash(state.savedCount);
1442
1535
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1443
1536
  className: "cc-section",
1444
1537
  "aria-label": t("title"),
@@ -1488,6 +1581,8 @@ window.__ModuleLoader__.load({
1488
1581
  configured: state.apiKeyConfigured,
1489
1582
  configuredLabel: t("apiKeySet"),
1490
1583
  unconfiguredLabel: t("apiKeyUnset"),
1584
+ showLabel: t("show"),
1585
+ hideLabel: t("hide"),
1491
1586
  onEdit: (text) => props.edit("apiKey", text)
1492
1587
  }),
1493
1588
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
@@ -1554,6 +1649,11 @@ window.__ModuleLoader__.load({
1554
1649
  role: "status",
1555
1650
  children: t("saveFailed")
1556
1651
  }) : null,
1652
+ savedVisible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1653
+ className: "cc-saved",
1654
+ role: "status",
1655
+ children: t("saved")
1656
+ }) : null,
1557
1657
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1558
1658
  variant: "ghost",
1559
1659
  size: "sm",
@@ -1569,6 +1669,10 @@ window.__ModuleLoader__.load({
1569
1669
  children: t(state.saving ? "saving" : "save")
1570
1670
  })
1571
1671
  ]
1672
+ }),
1673
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1674
+ className: "cc-version",
1675
+ children: ["Command Code Provider v", PLUGIN_VERSION]
1572
1676
  })
1573
1677
  ]
1574
1678
  });
@@ -1608,13 +1712,18 @@ window.__ModuleLoader__.load({
1608
1712
  overridden: "已覆盖",
1609
1713
  reset: "重置",
1610
1714
  invalidNumber: "无效数字",
1715
+ numberTooSmall: "不能小于 1(毫秒)",
1716
+ numberTooLarge: "超出允许上限(2147483647 毫秒)",
1611
1717
  readOnly: "当前配置为只读。",
1612
1718
  unsaved: "未保存",
1613
1719
  save: "保存",
1614
1720
  saving: "保存中",
1721
+ saved: "已保存 ✓",
1615
1722
  saveFailed: "保存失败,请重试。",
1616
1723
  discard: "放弃",
1617
1724
  cancel: "取消",
1725
+ show: "显示",
1726
+ hide: "隐藏",
1618
1727
  usageTitle: "账户用量",
1619
1728
  usageRefresh: "刷新",
1620
1729
  usageRefreshing: "刷新中…",
@@ -1676,13 +1785,18 @@ window.__ModuleLoader__.load({
1676
1785
  overridden: "Overridden",
1677
1786
  reset: "Reset",
1678
1787
  invalidNumber: "Invalid number",
1788
+ numberTooSmall: "Must be at least 1 (ms)",
1789
+ numberTooLarge: "Above the allowed maximum (2147483647 ms)",
1679
1790
  readOnly: "Settings are read-only.",
1680
1791
  unsaved: "Unsaved",
1681
1792
  save: "Save",
1682
1793
  saving: "Saving",
1794
+ saved: "Saved ✓",
1683
1795
  saveFailed: "Save failed, please retry.",
1684
1796
  discard: "Discard",
1685
1797
  cancel: "Cancel",
1798
+ show: "Show",
1799
+ hide: "Hide",
1686
1800
  usageTitle: "Account usage",
1687
1801
  usageRefresh: "Refresh",
1688
1802
  usageRefreshing: "Refreshing…",
@@ -1783,6 +1897,8 @@ window.__ModuleLoader__.load({
1783
1897
  .cc-usageMetaSpacer{flex:1}
1784
1898
  .cc-usageUpdated{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
1785
1899
  .cc-usagePartial{color:var(--dsw-alias-label-error);margin:0;font-size:11px;line-height:1.5}
1900
+ .cc-version{margin:4px 0 0;text-align:center;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
1901
+ .cc-saved{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary));margin:0;font-size:12px;font-weight:500;line-height:1.5}
1786
1902
  `;
1787
1903
  /** Inject the page stylesheet once (idempotent per tag). */
1788
1904
  function injectPageCss() {