dsh-cursor-dev 0.12.4 → 0.12.5

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.md CHANGED
@@ -55,9 +55,9 @@ flowchart LR
55
55
  3. 按 DSH 提示完成加载;涉及后台代码更新时重启 DSH,然后刷新页面。在 Cursor 面板中选择模型并保存。
56
56
  4. 明确告诉父模型使用 `cursor_code`,或者用下面的斜杠命令派发第一个任务。
57
57
 
58
- **版本说明(0.12.4):** 本版把两个原本要手改配置才能调的开关搬进了面板的模型卡片。`maxChildDepth` 默认值从 `1` 改为 `2`——Cursor 是叶节点(CLI 里没有 DSH 工具,不可能再往下派),`2` 就是实际上限,旧的默认值只是把「子智能体派 Cursor」这件事拒掉了;该值改为每次调用现读,面板「子代理深度」的选择优先于配置,下一次调用生效、不用重载插件,越界的值(如手改成 `9`)当作「没设」回落到配置而不是被截断。新增「Cursor 署名」开关:Cursor 会给子智能体做的 commit 追加 `Co-authored-by: Cursor` 并在它开的 PR 上署名,而这个开关没有 CLI 参数也没有环境变量,只存在于 Cursor 自己的 `cli-config.json`,所以插件替你写那个文件——只改 `attribution` 两项、其余键原样保留、临时文件加改名、解析不了的文件绝不覆盖,默认关闭;保存后立即写入并在每次启动 CLI 前再确认一次(CLI 只在进程启动时读一次配置)。面板布局调整为模型选择器与保存按钮置顶、代理输入框移到它所解释的状态行下方,并删掉「来源与出口」折叠、并入「模型目录、搜索与来源」。保留额度耗尽保护:调用 `cursor_code` 或通过原生子智能体派发之前先检查 Cursor 用量,总、Auto、API 三个额度都到上限时直接拒绝并说明原因,不再把任务派出去后中途失败;用量接口暂时取不到时不拦截调用。`cursor_subagent` 也会生成可点击的对话子会话,面板里点运行中或已完成的行都能查看过程,不再报「没有可恢复的会话」。DSH 兼容性不变,仍显式声明支持 `0.2.0-rc.1`、`0.2.0-rc.2`、`0.2.1-rc.1`、`0.2.1-rc.2` 以及 `>=0.2.1 <0.3.0` 的稳定版本,开发依赖固定为 `0.2.1-rc.2`。插件市场已收录本插件,卡片版本随目录探测更新;npm 包名为 `dsh-cursor-dev`,实际已发布版本以 npm 的 `latest` 标签为准。要跟进后续改动请用 Git 来源;`.tgz` 是某一刻的固定快照。详见[安装来源与版本](docs/INSTALL.md#2-选择安装来源)。
58
+ **版本说明(0.12.5):** 本版修掉模型卡片上「选了又被自己打回原样」的一类问题。子代理深度、锁定面板模型、代理输入框、Cursor 署名这四个控件以前只在点「保存」的那一刻从页面读值,而卡片被点标签页、拨模型开关、选模型、点刷新任意一次重绘时都会按存档值重画——于是刚选的深度会变回 `2`、刚勾的锁定会掉勾、刚输入的代理会消失、刚选的署名会弹回,看起来像面板自己拒绝了这个改动。现在四个控件各自记住未保存的选择(草稿),重绘一律以草稿为准,只有保存成功且服务端回显了同一个值才清掉草稿;被服务端拒掉的值会留在控件里并继续标「有改动未保存」,保存成功也不再无条件写「已保存」把这一提示盖掉。DSH 兼容性方面,开发依赖 `@deepseek-ai/dsh` 更新为 `0.2.1-rc.3`,显式支持列表加入 `0.2.1-rc.3`,`>=0.2.1-rc.2 <0.3.0-0` 的范围与 `>=0.2.1 <0.3.0` 的稳定版本支持不变。保留额度耗尽保护:调用 `cursor_code` 或通过原生子智能体派发之前先检查 Cursor 用量,总、Auto、API 三个额度都到上限时直接拒绝并说明原因,不再把任务派出去后中途失败;用量接口暂时取不到时不拦截调用。`cursor_subagent` 也会生成可点击的对话子会话,面板里点运行中或已完成的行都能查看过程,不再报「没有可恢复的会话」。插件市场已收录本插件,卡片版本随目录探测更新;npm 包名为 `dsh-cursor-dev`,实际已发布版本以 npm 的 `latest` 标签为准。要跟进后续改动请用 Git 来源;`.tgz` 是某一刻的固定快照。详见[安装来源与版本](docs/INSTALL.md#2-选择安装来源)。
59
59
 
60
- 运行要求:DSH `0.2.0-rc.1`、`0.2.0-rc.2`、`0.2.1-rc.1`、`0.2.1-rc.2` 或 `>=0.2.1 <0.3.0` 的稳定版本(已在 `0.2.1-rc.2` 上验证);Node.js `^22.19.0 || >=24.0.0`;已登录且可访问服务的 Cursor CLI。原生子智能体功能需要 DSH 的 `subagents`、`agents` 服务,后台任务还需要对应任务服务,建议使用当前 DSH 桌面版。CLI 安装方法见 [Cursor 官方文档](https://cursor.com/docs/cli)。
60
+ 运行要求:DSH `0.2.0-rc.1`、`0.2.0-rc.2`、`0.2.1-rc.1`、`0.2.1-rc.2`、`0.2.1-rc.3` 或 `>=0.2.1 <0.3.0` 的稳定版本(当前桌面版运行时为 `0.2.1-rc.3`);Node.js `^22.19.0 || >=24.0.0`;已登录且可访问服务的 Cursor CLI。原生子智能体功能需要 DSH 的 `subagents`、`agents` 服务,后台任务还需要对应任务服务,建议使用当前 DSH 桌面版。CLI 安装方法见 [Cursor 官方文档](https://cursor.com/docs/cli)。
61
61
 
62
62
  ### 三个入口
63
63
 
@@ -240,9 +240,9 @@ See the [installation guide](docs/INSTALL.md) for detailed setup and troubleshoo
240
240
  3. Restart DSH when backend changes require it, then refresh the page. Open the Cursor panel under `/plugins/dsh-cursor-dev/`, select a model and save it. The history list keeps one row per run forever, so it sits in a height-capped scroll box and folds away when you click its heading (the choice is remembered); a row's **Delete** link clears that run's index record, journal and mirrored child — the same chain `resetSession` uses — and arms on the first click so the irreversible second click is deliberate.
241
241
  4. Ask the parent to use `cursor_code`, or send `/cursor-subagent` followed by a complete task. The command dispatches directly and hands a completion or failure summary back to the original parent.
242
242
 
243
- Version `0.12.4` moves two settings that used to need a hand-edited profile, or a hand-edited file belonging to another program, into the model card. `maxChildDepth` now defaults to `2` instead of `1`: a Cursor run is a leaf — the CLI has no DSH tools, so it can never delegate further, which makes `2` the real ceiling and the old default refused the delegation people actually try. The cap is read per call, the panel's **Subagent depth** picker wins over the config, it applies on the next call without a reload, and an out-of-range value (a hand-edited `9`) is treated as unset rather than clamped. A new **Cursor credit** switch exists because Cursor appends `Co-authored-by: Cursor` to the commits its agent makes and credits itself on the pull requests it opens, and that switch has no flag and no environment variable — it lives only in Cursor's own `cli-config.json`, so the plugin writes that file: exactly the two `attribution` keys, every other key preserved, through a temporary file and a rename, and a file it cannot parse is never replaced. It is off by default, applied on save and re-checked before every spawn, because the CLI reads its settings once at startup. The panel now leads the model card with the model picker and its save button, puts the proxy box under the status line it is about, and folds the former "sources" note into the catalogue fold. Quota exhaustion protection is retained: before `cursor_code` or a native subagent call starts, the plugin reads Cursor usage and refuses the call, explaining why, when the total, Auto and API buckets are all at their limit, instead of dispatching a run that later fails mid-way. A usage lookup that cannot be completed never blocks a call. `cursor_subagent` also creates a clickable transcript child, so rows for running and finished runs open in the panel instead of reporting that no durable conversation exists. DSH compatibility is unchanged: support is declared for `0.2.0-rc.1`, `0.2.0-rc.2`, `0.2.1-rc.1`, `0.2.1-rc.2` and stable versions `>=0.2.1 <0.3.0`, with the `@deepseek-ai/dsh` development dependency pinned to exactly `0.2.1-rc.2`. The dsh-market catalog already lists this plugin and updates its displayed version on subsequent probes. The public npm package is `dsh-cursor-dev`; its `latest` tag identifies the published version. Use the Git source to follow later changes; a `.tgz` is a fixed snapshot.
243
+ Version `0.12.5` fixes the family of "the pick snaps back by itself" bugs on the model card. The subagent-depth picker, the lock checkbox, the proxy box and the Cursor credit switch used to be read only at the moment **Save** was pressed, while every repaint (a tab click, a model switch, picking a model, a refresh) redrew them from the stored value — so a fresh depth choice fell back to `2`, a ticked lock unticked itself, a typed proxy vanished and a picked credit choice bounced back, which looked like the panel refusing the change. Each of the four now keeps an unsaved draft, every repaint prefers the draft, and the draft is cleared only when the save succeeds and the server echoes the same value back; a value the write refuses stays in the control and keeps reporting "unsaved", and a successful save no longer overwrites that line with "saved". On the DSH side the `@deepseek-ai/dsh` development dependency moves to `0.2.1-rc.3` and the explicit support list gains `0.2.1-rc.3`; the `>=0.2.1-rc.2 <0.3.0-0` range and stable support `>=0.2.1 <0.3.0` are unchanged. Quota exhaustion protection is retained: before `cursor_code` or a native subagent call starts, the plugin reads Cursor usage and refuses the call, explaining why, when the total, Auto and API buckets are all at their limit, instead of dispatching a run that later fails mid-way. A usage lookup that cannot be completed never blocks a call. `cursor_subagent` also creates a clickable transcript child, so rows for running and finished runs open in the panel instead of reporting that no durable conversation exists. The dsh-market catalog already lists this plugin and updates its displayed version on subsequent probes. The public npm package is `dsh-cursor-dev`; its `latest` tag identifies the published version. Use the Git source to follow later changes; a `.tgz` is a fixed snapshot.
244
244
 
245
- Requirements: DSH `0.2.0-rc.1`, `0.2.0-rc.2`, `0.2.1-rc.1`, `0.2.1-rc.2` or a stable version `>=0.2.1 <0.3.0` (verified against `0.2.1-rc.2`); Node.js `^22.19.0 || >=24.0.0`; an authenticated Cursor CLI account with service access. Native dispatch additionally needs DSH's `subagents` and `agents` services, and background runs need the host's job services. A current DSH desktop installation is recommended.
245
+ Requirements: DSH `0.2.0-rc.1`, `0.2.0-rc.2`, `0.2.1-rc.1`, `0.2.1-rc.2`, `0.2.1-rc.3` or a stable version `>=0.2.1 <0.3.0` (the current desktop runtime is `0.2.1-rc.3`); Node.js `^22.19.0 || >=24.0.0`; an authenticated Cursor CLI account with service access. Native dispatch additionally needs DSH's `subagents` and `agents` services, and background runs need the host's job services. A current DSH desktop installation is recommended.
246
246
 
247
247
  ### Parent-to-child contract
248
248
 
package/docs/INSTALL.md CHANGED
@@ -11,7 +11,7 @@
11
11
  - 可正常对话的 DSH,以及一个支持工具调用的父模型。
12
12
  - 插件使用的 Node.js 满足 `^22.19.0 || >=24.0.0`。桌面版应检查其自带运行时,终端里的 `node --version` 不一定是桌面程序使用的版本。
13
13
  - 当前版 Cursor CLI,已完成登录,并有执行任务所需的账号权限、额度和网络连接。
14
- - 原生子智能体入口需要 DSH 的 `subagents`、`agents` 服务;后台运行还需要宿主任务服务。包显式声明支持 DSH `0.2.0-rc.1`、`0.2.0-rc.2`、`0.2.1-rc.1`、`0.2.1-rc.2` 以及 `>=0.2.1 <0.3.0` 的稳定版本(已在 `0.2.1-rc.2` 上验证),建议使用当前桌面版及其标准 profile。
14
+ - 原生子智能体入口需要 DSH 的 `subagents`、`agents` 服务;后台运行还需要宿主任务服务。包显式声明支持 DSH `0.2.0-rc.1`、`0.2.0-rc.2`、`0.2.1-rc.1`、`0.2.1-rc.2`、`0.2.1-rc.3` 以及 `>=0.2.1 <0.3.0` 的稳定版本(当前桌面版运行时为 `0.2.1-rc.3`),建议使用当前桌面版及其标准 profile。
15
15
 
16
16
  父模型的 API 配置与 Cursor 登录分别管理。插件使用本机 CLI 的登录状态,无需再向插件填写一份 Cursor API Key。
17
17
 
@@ -38,7 +38,7 @@ agent status --format json
38
38
 
39
39
  ## 2. 选择安装来源
40
40
 
41
- **当前源码版本:** `0.12.4`,把两个原本要手改配置才能调的开关搬进了面板:`maxChildDepth` 默认值改为 `2`(Cursor 是叶节点,`2` 即实际上限),改为每次调用现读、面板「子代理深度」优先于配置;新增「Cursor 署名」开关,插件按你的选择写 Cursor 自己的 `cli-config.json`(只改 `attribution` 两项、其余键原样保留、解析不了的文件不覆盖),默认关闭。面板布局调整为模型选择器置顶、代理输入框在它所解释的状态行下方,「来源与出口」并入「模型目录、搜索与来源」。同时保留额度耗尽保护(三个额度都到上限时提前拒绝,用量取不到时不拦截)、可点击的子会话轨迹。DSH 兼容性不变,仍显式声明支持 `0.2.0-rc.1`、`0.2.0-rc.2`、`0.2.1-rc.1`、`0.2.1-rc.2` 以及 `>=0.2.1 <0.3.0` 的稳定版本。npm 包名为 `dsh-cursor-dev`,实际已发布版本以 npm 的 `latest` 标签为准。插件市场已收录本插件,卡片版本随目录探测更新。
41
+ **当前源码版本:** `0.12.5`,修掉模型卡片上「选了又被自己打回原样」的问题:子代理深度、锁定面板模型、代理输入框、Cursor 署名四个控件以前只在点「保存」时从页面读值,任何一次重绘(点标签页、拨模型开关、选模型、点刷新)都会按存档值重画,于是刚选的值会自己弹回去;现在四者各自保留未保存的草稿,重绘以草稿为准,保存成功且服务端回显同一个值才清草稿,被拒的值留在控件里继续标「有改动未保存」,保存成功也不再无条件写「已保存」。DSH 兼容性方面,开发依赖 `@deepseek-ai/dsh` 更新为 `0.2.1-rc.3`,显式支持列表加入 `0.2.1-rc.3`,`>=0.2.1-rc.2 <0.3.0-0` 与稳定版本 `>=0.2.1 <0.3.0` 的支持不变。同时保留额度耗尽保护(三个额度都到上限时提前拒绝,用量取不到时不拦截)、可点击的子会话轨迹。npm 包名为 `dsh-cursor-dev`,实际已发布版本以 npm 的 `latest` 标签为准。插件市场已收录本插件,卡片版本随目录探测更新。
42
42
 
43
43
  | 你要安装的内容 | 安装来源 |
44
44
  | --- | --- |
@@ -46,7 +46,7 @@ agent status --format json
46
46
  | npm 公共源上的已发布版本 | `dsh-cursor-dev`(`0.11.0` 起可用,按包内 `files` 白名单安装,版本以 npm 的 `latest` 标签为准) |
47
47
  | 公开仓库的当前版本(推荐) | `git+https://github.com/Dclef/dsh-cursor-dev.git` |
48
48
  | 你本地的源码目录 | 该目录的绝对路径,例如 `D:/plugins/dsh-cursor-dev` |
49
- | 维护者提供的源码快照或发布包 | `.tgz` 文件的绝对路径,例如 `D:/Downloads/dsh-cursor-dev-0.12.4.tgz` |
49
+ | 维护者提供的源码快照或发布包 | `.tgz` 文件的绝对路径,例如 `D:/Downloads/dsh-cursor-dev-0.12.5.tgz` |
50
50
 
51
51
  表中本地路径是示例,需要换成实际位置。npm 来源取 registry 上发布的最新版本;Git 来源会取得仓库默认分支的最新提交;`.tgz` 是某一刻的固定快照——要跟进后续改动就用 Git 来源。
52
52
 
@@ -88,7 +88,7 @@ dsh plugin add "/home/you/plugins/dsh-cursor-dev"
88
88
  安装收到的包:
89
89
 
90
90
  ```powershell
91
- dsh plugin add "D:/Downloads/dsh-cursor-dev-0.12.4.tgz"
91
+ dsh plugin add "D:/Downloads/dsh-cursor-dev-0.12.5.tgz"
92
92
  ```
93
93
 
94
94
  终端没有 `dsh` 命令时,使用桌面插件管理即可。不要把在任意项目目录执行 `npm install` 当成已经在 DSH 当前 profile 中启用插件。
package/lib/usage.js CHANGED
@@ -1680,6 +1680,18 @@ function dashboardHtml(state) {
1680
1680
  // panel never has to guess which of the two wins.
1681
1681
  var effectiveDepth = typeof boot.effectiveMaxChildDepth === 'number'
1682
1682
  ? boot.effectiveMaxChildDepth : 2;
1683
+ // The depth the operator picked but has not pressed 保存 for yet. The picker
1684
+ // is painted from the value in force, so an unsaved pick would otherwise be
1685
+ // wiped by the next repaint (a tab click, a switch, a save of the model) and
1686
+ // the card would look like it had snapped back on its own.
1687
+ var depthDraft = null;
1688
+ // The same shape, for the other three controls the card paints and 保存
1689
+ // commits: the lock checkbox, the proxy box and the attribution switch. Each
1690
+ // records what the operator chose so a repaint cannot silently undo it before
1691
+ // it is saved — the stored value only wins once the save echoes it back.
1692
+ var lockDraft = null;
1693
+ var proxyDraft = null;
1694
+ var attributionDraft = null;
1683
1695
  // The depth picker's choices: the value the tool compares against, then the
1684
1696
  // dictionary key for its label. Ordered shallowest first.
1685
1697
  var DEPTH_CHOICES = [['0', 'depthNone'], ['1', 'depthTop'], ['2', 'depthAll']];
@@ -1913,6 +1925,21 @@ function dashboardHtml(state) {
1913
1925
  return { key: tab.key, label: t(tab.label), count: counts[tab.key] };
1914
1926
  });
1915
1927
  }
1928
+ /**
1929
+ * Whether any control on the model card holds a change 保存 has not written.
1930
+ *
1931
+ * The draft fields are not "unsaved" by existing: a draft equal to the value
1932
+ * the file already holds (a re-pick of the same row) has nothing to write.
1933
+ */
1934
+ function unsaved() {
1935
+ var savedProxy = settings.proxy == null ? '' : String(settings.proxy);
1936
+ return (draftModel !== null && canonicalModel(draftModel) !== canonicalModel(settings.model))
1937
+ || switchesDirty()
1938
+ || (depthDraft !== null && depthDraft !== effectiveDepth)
1939
+ || (lockDraft !== null && lockDraft !== (settings.modelPolicy === 'panel'))
1940
+ || (proxyDraft !== null && proxyDraft.trim() !== savedProxy)
1941
+ || (attributionDraft !== null && attributionDraft !== attributionChoice);
1942
+ }
1916
1943
  function renderModel() {
1917
1944
  var select = $('model');
1918
1945
  select.textContent = '';
@@ -1926,10 +1953,12 @@ function dashboardHtml(state) {
1926
1953
  if (proxyBox) {
1927
1954
  proxyBox.placeholder = t('proxyPlaceholder');
1928
1955
  proxyBox.title = t('proxyPlaceholder');
1929
- // Never clobber a half-typed value: the panel repaints on every poll.
1956
+ // Never clobber what the box holds: the panel repaints on tab clicks, model
1957
+ // picks and 保存, and the box keeps the operator's text through all of them.
1930
1958
  var configuredProxy = settings.proxy == null ? '' : String(settings.proxy);
1931
- if (proxyBox !== document.activeElement && proxyBox.value !== configuredProxy) {
1932
- proxyBox.value = configuredProxy;
1959
+ if (proxyBox !== document.activeElement) {
1960
+ var proxyShown = proxyDraft === null ? configuredProxy : proxyDraft;
1961
+ if (proxyBox.value !== proxyShown) proxyBox.value = proxyShown;
1933
1962
  }
1934
1963
  }
1935
1964
  var depthBox = $('depth');
@@ -1946,9 +1975,12 @@ function dashboardHtml(state) {
1946
1975
  for (var di = 0; di < depthBox.options.length; di += 1) {
1947
1976
  if (DEPTH_CHOICES[di]) depthBox.options[di].textContent = t(DEPTH_CHOICES[di][1]);
1948
1977
  }
1949
- // Same rule as the proxy box: a value the operator just picked survives
1950
- // the poll that arrives before they press 保存.
1951
- if (depthBox !== document.activeElement) depthBox.value = String(effectiveDepth);
1978
+ // What the operator picked but has not saved yet wins over the enforced
1979
+ // value: the picker is repainted by every tab click and every save, and a
1980
+ // paint that ignored the pick would look like the panel refused it.
1981
+ if (depthBox !== document.activeElement) {
1982
+ depthBox.value = String(depthDraft === null ? effectiveDepth : depthDraft);
1983
+ }
1952
1984
  }
1953
1985
  var attributionBox = $('attribution');
1954
1986
  if (attributionBox && attributionBox.options) {
@@ -1962,7 +1994,9 @@ function dashboardHtml(state) {
1962
1994
  for (var ai = 0; ai < attributionBox.options.length; ai += 1) {
1963
1995
  if (ATTRIBUTION_CHOICES[ai]) attributionBox.options[ai].textContent = t(ATTRIBUTION_CHOICES[ai][1]);
1964
1996
  }
1965
- if (attributionBox !== document.activeElement) attributionBox.value = String(attributionChoice);
1997
+ if (attributionBox !== document.activeElement) {
1998
+ attributionBox.value = String(attributionDraft === null ? attributionChoice : attributionDraft);
1999
+ }
1966
2000
  }
1967
2001
  // The choice is ours to store; whether it took effect is Cursor's answer, so
1968
2002
  // the line under the picker reports the file rather than repeating the pick.
@@ -2018,7 +2052,7 @@ function dashboardHtml(state) {
2018
2052
  select.appendChild(kept);
2019
2053
  select.value = selected;
2020
2054
  }
2021
- $('model-lock').checked = settings.modelPolicy === 'panel';
2055
+ $('model-lock').checked = lockDraft === null ? settings.modelPolicy === 'panel' : lockDraft === true;
2022
2056
  var bits = [];
2023
2057
  bits.push(t('inEffect') + modelLabel(inEffectNow));
2024
2058
  bits.push(t(settings.modelPolicy === 'panel' && inEffectNow ? 'modelLocked' : 'modelOverridable'));
@@ -2039,7 +2073,7 @@ function dashboardHtml(state) {
2039
2073
  if (proxyError) bits.push(t('proxyFailed') + proxyError);
2040
2074
  if (modelsError) bits.push(t('modelsFailed') + modelsError);
2041
2075
  text($('model-state'), bits.join(' · '));
2042
- var dirty = (draftModel !== null && canonicalModel(draftModel) !== canonicalModel(settings.model)) || switchesDirty();
2076
+ var dirty = unsaved();
2043
2077
  var status = [t('inEffect') + modelLabel(inEffectNow),
2044
2078
  t(settings.modelPolicy === 'panel' && inEffectNow ? 'modelLocked' : 'modelOverridable'),
2045
2079
  t(dirty ? 'unsavedShort' : 'appliesNext')];
@@ -2745,6 +2779,14 @@ function dashboardHtml(state) {
2745
2779
  // the line under the picker reports the result rather than the intent.
2746
2780
  applyAttribution(data);
2747
2781
  applyProxy(data);
2782
+ // The response is computed after the write, so it is the only place the
2783
+ // new cap is known. Skipping it would leave the picker painting the
2784
+ // value read at page load — the save would look like it was refused
2785
+ // even though the file holds the new number.
2786
+ if (data && typeof data.effectiveMaxChildDepth === 'number') effectiveDepth = data.effectiveMaxChildDepth;
2787
+ // 保存 always sends the picker, so a save that carried it has just
2788
+ // written the draft; one that did not must not discard an unsaved pick.
2789
+ if (patch !== null && typeof patch === 'object' && patch.maxChildDepth !== undefined) depthDraft = null;
2748
2790
  renderModel();
2749
2791
  return data;
2750
2792
  })
@@ -2851,6 +2893,41 @@ function dashboardHtml(state) {
2851
2893
  });
2852
2894
  }
2853
2895
  catch (error) { /* a stubbed DOM in the tests has no heading */ }
2896
+ // Picking a depth is a draft until 保存, exactly like picking a model row: the
2897
+ // card is repainted by unrelated clicks (a tab, a switch, a model pick), and a
2898
+ // paint that dropped the pick would make the panel look like it had refused
2899
+ // the choice on its own.
2900
+ var depthPicker = $('depth');
2901
+ if (depthPicker && typeof depthPicker.addEventListener === 'function') {
2902
+ depthPicker.addEventListener('change', function () {
2903
+ var picked = depthPicker.value === '' ? NaN : Number(depthPicker.value);
2904
+ depthDraft = Number.isFinite(picked) ? picked : null;
2905
+ renderModel();
2906
+ });
2907
+ }
2908
+ // The lock, the proxy box and the attribution switch follow the same rule: a
2909
+ // repaint must not drop a change the operator made but has not saved yet.
2910
+ var lockBox = $('model-lock');
2911
+ if (lockBox && typeof lockBox.addEventListener === 'function') {
2912
+ lockBox.addEventListener('change', function () {
2913
+ lockDraft = lockBox.checked === true;
2914
+ renderModel();
2915
+ });
2916
+ }
2917
+ var proxyField = $('proxy');
2918
+ if (proxyField && typeof proxyField.addEventListener === 'function') {
2919
+ proxyField.addEventListener('input', function () {
2920
+ proxyDraft = String(proxyField.value);
2921
+ renderModel();
2922
+ });
2923
+ }
2924
+ var attributionPicker = $('attribution');
2925
+ if (attributionPicker && typeof attributionPicker.addEventListener === 'function') {
2926
+ attributionPicker.addEventListener('change', function () {
2927
+ attributionDraft = String(attributionPicker.value);
2928
+ renderModel();
2929
+ });
2930
+ }
2854
2931
  $('model-save').addEventListener('click', function () {
2855
2932
  var value = $('model').value;
2856
2933
  var proxyBox = $('proxy');
@@ -2880,11 +2957,27 @@ function dashboardHtml(state) {
2880
2957
  // shadow and the next paint must read the saved list again.
2881
2958
  draftModel = null;
2882
2959
  draftDisabled = null;
2960
+ // Same rule for the other controls 保存 just carried: a draft the answer
2961
+ // echoes back is gone, and one it does not echo is a value the write
2962
+ // refused — keep it flagged as unsaved instead of pretending it landed.
2963
+ if (lockDraft !== null && (data.settings.modelPolicy === 'panel') === lockDraft) lockDraft = null;
2964
+ if (proxyDraft !== null && proxyDraft.trim() === String(data.settings.proxy == null ? '' : data.settings.proxy)) {
2965
+ proxyDraft = null;
2966
+ }
2967
+ if (attributionDraft !== null && data.attribution && data.attribution.choice === attributionDraft) {
2968
+ attributionDraft = null;
2969
+ }
2970
+ if (depthDraft !== null && data.effectiveMaxChildDepth === depthDraft) depthDraft = null;
2971
+ renderModel();
2883
2972
  renderLive(lastPayload);
2884
2973
  var announce = function () {
2885
2974
  var lock = t(settings.modelPolicy === 'panel' && data.effectiveModel ? 'modelLocked' : 'modelOverridable');
2886
2975
  text($('model-state'), t('saved') + ' · ' + t('inEffect') + modelLabel(data.effectiveModel) + ' · ' + lock);
2887
- text($('model-status'), t('saved') + ' · ' + modelLabel(data.effectiveModel) + ' · ' + lock + ' · ' + t('appliesNext'));
2976
+ // "已保存" must not talk over a card that still shows an unsaved value:
2977
+ // the paint from renderModel is the honest status and is left in place.
2978
+ if (!unsaved()) {
2979
+ text($('model-status'), t('saved') + ' · ' + modelLabel(data.effectiveModel) + ' · ' + lock + ' · ' + t('appliesNext'));
2980
+ }
2888
2981
  };
2889
2982
  // A new route reaches a different catalogue, which makes the list on
2890
2983
  // screen a fact about the old one — so re-read it before saying "saved".
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-cursor-dev",
3
3
  "displayName": "Cursor 子智能体",
4
4
  "description": "把 Cursor CLI 当作子编码智能体来写业务代码:cursor_code 与 cursor_subagent 都提供可从面板点开的实时子对话,原生任务保留宿主的 External 运行记录;可按任务选模型、会话在 DSH 重启后仍能续聊、返回逐步工具时间线与真实文件 diff,并自带额度/模型/会话面板。",
5
- "version": "0.12.4",
5
+ "version": "0.12.5",
6
6
  "license": "MIT",
7
7
  "type": "module",
8
8
  "main": "lib/index.js",
@@ -57,7 +57,7 @@
57
57
  "@agentclientprotocol/sdk": "^1.4.0"
58
58
  },
59
59
  "devDependencies": {
60
- "@deepseek-ai/dsh": "0.2.1-rc.2"
60
+ "@deepseek-ai/dsh": "0.2.1-rc.3"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "@deepseek-ai/schemastery": "^3.18.1"