dsh-plugin-effort-declare 0.1.0 → 0.1.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.
@@ -38,7 +38,7 @@ pnpm build
38
38
 
39
39
  Before sending a change, say whether it is a bugfix, UI improvement, or an alignment with a newer DSH / pi-ai schema. Update zh and en docs together. The root README and [INSTALL.en.md](./INSTALL.en.md) are for users; this file and the READMEs under `src/` and `tests/` are for people changing code.
40
40
 
41
- Canonical level names and `thinkingFormat` enums come from the installed `llm-pi-ai` schema / catalog. The repo keeps a fallback whitelist pinned by tests; if the schema drifts, update the tests first, then the UI.
41
+ Canonical level names and `thinkingFormat` enums come from the installed `llm-pi-ai` schema / catalog. The repo keeps a whitelist pinned by tests; if the schema drifts, update the tests first, then UI copy. Do not offer that list as writable dropdown choices.
42
42
 
43
43
  ## Publishing to npm
44
44
 
@@ -52,6 +52,8 @@ pnpm publish
52
52
 
53
53
  `pnpm publish` fails on an unclean Git tree. Commit first (and preferably push). Do not pass `--no-git-checks`.
54
54
 
55
+ After publish, users update with `dsh plugin --profile web update dsh-plugin-effort-declare` or `add dsh-plugin-effort-declare@latest`. Authors do not implement an update command; official `dsh plugin` forwards the rest of the line to pnpm.
56
+
55
57
  ## Security
56
58
 
57
59
  Do not paste API keys or `.credentials.yaml` values into logs, screenshots, docs, or issues.
package/CONTRIBUTING.md CHANGED
@@ -38,7 +38,7 @@ pnpm build
38
38
 
39
39
  提交前请说明改动是修 bug、增强 UI,还是对齐新的 DSH / pi-ai schema。中英文档一起改。根 README 和 [INSTALL.md](./INSTALL.md) 面向使用者;本文件与 `src/`、`tests/` 的 README 面向改代码的人。
40
40
 
41
- 上游档位名与 `thinkingFormat` 枚举以 DSH 安装里 `llm-pi-ai` 的 schema / catalog 为准。仓库内有一份回退白名单,并由测试钉住;schema 漂移时应先改测试再改 UI
41
+ 上游档位名与 `thinkingFormat` 枚举以 DSH 安装里 `llm-pi-ai` 的 schema / catalog 为准。仓库内有一份测试钉白名单;schema 漂移时应先改测试再改 UI 文案。不要把这份名单当作可写入下拉选项。
42
42
 
43
43
  ## 发布到 npm
44
44
 
@@ -52,6 +52,8 @@ pnpm publish
52
52
 
53
53
  `pnpm publish` 在 Git 有未提交改动时会失败。应先 commit(建议再 push),不要用 `--no-git-checks` 绕过。
54
54
 
55
+ 发布后用户用 `dsh plugin --profile web update dsh-plugin-effort-declare` 或 `add dsh-plugin-effort-declare@latest` 更新。作者无需在插件里实现更新命令;官方 `dsh plugin` 会把参数转给 pnpm。
56
+
55
57
  ## 安全
56
58
 
57
59
  不要在日志、截图、文档或 issue 中粘贴 API 密钥或 `.credentials.yaml` 的值。
package/INSTALL.en.md CHANGED
@@ -9,14 +9,18 @@
9
9
 
10
10
  Back to the [README](./README.en.md). Requires DeepSeek Harness **0.1.0-rc.8 or newer**. Do not use `npm install`; use `dsh plugin` below.
11
11
 
12
- ## Desktop users: do this first
12
+ ## Third-party DSH: set DSH_HOME first
13
13
 
14
- `dsh plugin` installs into the **current** `DSH_HOME`. If unset, that is `.dsh` in your user folder **not** Desktop’s data directory.
14
+ `dsh plugin` installs into the **current** `DSH_HOME`. If unset, that is `.dsh` in your user folder. Third-party distributions often use a different home; point it at the home your DSH actually uses.
15
15
 
16
- PowerShell:
16
+ ```bash
17
+ # Linux / macOS
18
+ export DSH_HOME=/path/to/your/dsh-home
19
+ ```
17
20
 
18
21
  ```powershell
19
- $env:DSH_HOME = "$env:APPDATA\io.github.hairyf.deepseek-harness-desktop\data\dsh"
22
+ # Windows PowerShell
23
+ $env:DSH_HOME = "X:\path\to\your\dsh-home"
20
24
  ```
21
25
 
22
26
  Set this before the install command. The profile name is usually `web`.
@@ -35,6 +39,32 @@ dsh --profile web --dump-config
35
39
 
36
40
  The output should mention `dsh-plugin-effort-declare`.
37
41
 
42
+ ## Update
43
+
44
+ The plugin is published to npm. Updates also go through `dsh plugin` (the official CLI forwards the rest of the line to pnpm). Do not use `npm update`.
45
+
46
+ Third-party DSH still needs `DSH_HOME` set first, same as install.
47
+
48
+ Already installed from npm, upgrade to the newest version in the existing semver range:
49
+
50
+ ```bash
51
+ dsh plugin --profile web update dsh-plugin-effort-declare
52
+ ```
53
+
54
+ Latest release, including rewriting the dependency range:
55
+
56
+ ```bash
57
+ dsh plugin --profile web add dsh-plugin-effort-declare@latest
58
+ ```
59
+
60
+ Then restart DSH. Use `dsh --profile web --dump-config` to confirm the layer is still present.
61
+
62
+ Installed from GitHub: run `add` again, or pin a commit:
63
+
64
+ ```bash
65
+ dsh plugin --profile web add github:zimodzh/dsh-plugin-effort-declare
66
+ ```
67
+
38
68
  ## Other methods
39
69
 
40
70
  GitHub:
@@ -53,7 +83,7 @@ dsh plugin --profile web add file:/absolute/path/to/dsh-plugin-effort-declare
53
83
 
54
84
  ## After install
55
85
 
56
- Restart DSH (on Desktop, restart its backend). Open **Settings → Reasoning efforts**. Add a custom provider on the Models page first; otherwise this page has nothing to edit.
86
+ Restart DSH. Open **Settings → Reasoning efforts**. Add a custom provider on the Models page first; otherwise this page has nothing to edit.
57
87
 
58
88
  ## Uninstall
59
89
 
package/INSTALL.md CHANGED
@@ -9,14 +9,18 @@
9
9
 
10
10
  回到 [README](./README.md)。需要 DeepSeek Harness **0.1.0-rc.8 或更新**。不要用 `npm install`,必须用下面的 `dsh plugin` 命令。
11
11
 
12
- ## Desktop 用户请先做这一步
12
+ ## 第三方 DSH:先设置 DSH_HOME
13
13
 
14
- `dsh plugin` 会装进**当前** `DSH_HOME`。没设置时默认是用户目录下的 `.dsh`,**不是** Desktop 自己的数据目录。
14
+ `dsh plugin` 会装进**当前** `DSH_HOME`。没设置时默认是用户目录下的 `.dsh`。第三方发行版的数据目录往往不是这条路径,需要改成你这份 DSH 实际使用的 home。
15
15
 
16
- PowerShell:
16
+ ```bash
17
+ # Linux / macOS
18
+ export DSH_HOME=/path/to/your/dsh-home
19
+ ```
17
20
 
18
21
  ```powershell
19
- $env:DSH_HOME = "$env:APPDATA\io.github.hairyf.deepseek-harness-desktop\data\dsh"
22
+ # Windows PowerShell
23
+ $env:DSH_HOME = "X:\path\to\your\dsh-home"
20
24
  ```
21
25
 
22
26
  设好后再跑安装命令。配置档名称一般是 `web`。
@@ -35,6 +39,32 @@ dsh --profile web --dump-config
35
39
 
36
40
  输出里应出现 `dsh-plugin-effort-declare`。
37
41
 
42
+ ## 更新
43
+
44
+ 插件已发布到 npm。更新也走 `dsh plugin`(官方会把参数转给 pnpm),不要用 `npm update`。
45
+
46
+ 第三方 DSH 仍须先设置 `DSH_HOME`,与安装相同。
47
+
48
+ 已从 npm 安装过,升级到 semver 范围内的新版本:
49
+
50
+ ```bash
51
+ dsh plugin --profile web update dsh-plugin-effort-declare
52
+ ```
53
+
54
+ 要最新正式版(含改依赖范围):
55
+
56
+ ```bash
57
+ dsh plugin --profile web add dsh-plugin-effort-declare@latest
58
+ ```
59
+
60
+ 然后重启 DSH。可用 `dsh --profile web --dump-config` 确认层还在。
61
+
62
+ 从 GitHub 装的:再执行一次下面的命令,或使用带 commit 的规格:
63
+
64
+ ```bash
65
+ dsh plugin --profile web add github:zimodzh/dsh-plugin-effort-declare
66
+ ```
67
+
38
68
  ## 其他方式
39
69
 
40
70
  GitHub:
@@ -53,7 +83,7 @@ dsh plugin --profile web add file:/绝对路径/dsh-plugin-effort-declare
53
83
 
54
84
  ## 装完之后
55
85
 
56
- 重启 DSH(Desktop 请重启其后端)。打开 **设置 → 推理档位**。先在「模型」页添加第三方提供方,本页才会出现可编辑的条目。
86
+ 重启 DSH。打开 **设置 → 推理档位**。先在「模型」页添加第三方提供方,本页才会出现可编辑的条目。
57
87
 
58
88
  ## 卸载
59
89
 
package/README.en.md CHANGED
@@ -7,13 +7,21 @@
7
7
  </samp>
8
8
  </p>
9
9
 
10
+ <p align="center">
11
+ <a href="https://www.npmjs.com/package/dsh-plugin-effort-declare"><img alt="npm" src="https://img.shields.io/npm/v/dsh-plugin-effort-declare"></a>
12
+ <a href="https://github.com/zimodzh/dsh-plugin-effort-declare/commits/master"><img alt="last commit" src="https://img.shields.io/github/last-commit/zimodzh/dsh-plugin-effort-declare"></a>
13
+ <a href="https://github.com/zimodzh/dsh-plugin-effort-declare"><img alt="repo size" src="https://img.shields.io/github/repo-size/zimodzh/dsh-plugin-effort-declare"></a>
14
+ <a href="./LICENSE"><img alt="license" src="https://img.shields.io/github/license/zimodzh/dsh-plugin-effort-declare"></a>
15
+ <a href="https://github.com/deepseek-ai/deepseek-harness"><img alt="Agent" src="https://img.shields.io/badge/Agent-Deepseek%20Harness-000000"></a>
16
+ </p>
17
+
10
18
  Add reasoning-effort options to OpenAI-compatible models you added yourself, so the Effort row shows up in the composer.
11
19
 
12
- This is a community plugin. It is not an official DeepSeek project and is not endorsed by DeepSeek. Requires DeepSeek Harness **0.1.0-rc.8 or newer**.
20
+ > This is a community plugin. It is not an official DeepSeek project and is not endorsed by DeepSeek. Requires DeepSeek Harness **0.1.0-rc.8 or later**.
13
21
 
14
22
  ## Why
15
23
 
16
- Custom providers you add on the Models page usually have no reasoning levels, so the composer hides Effort. This plugin adds a **Reasoning efforts** page in Settings: you pick which levels a model can offer. The per-turn choice and the HTTP request still go through DSH. This plugin only writes settings, not API keys.
24
+ Hand-added third-party providers on the Models page need a reasoning-level declaration before Effort appears in the composer. They usually do not declare it. This plugin therefore adds a **Reasoning efforts** page in Settings: you pick which levels a model can offer. The per-turn choice and the HTTP request still go through DSH. This plugin only writes settings, not API keys.
17
25
 
18
26
  - Does not change models you have not saved
19
27
  - Does not change the current or default effort
@@ -35,7 +43,13 @@ Do not use `npm install`. In a terminal:
35
43
  dsh plugin --profile web add dsh-plugin-effort-declare
36
44
  ```
37
45
 
38
- If you use **DeepSeek Harness Desktop**, set `DSH_HOME` first or the package lands in the wrong folder. See the [install guide](./INSTALL.en.md).
46
+ If you use third-party DSH, you must set `DSH_HOME` first, or the package lands in another directory. See the [install guide](./INSTALL.en.md). To update an npm install:
47
+
48
+ ```bash
49
+ dsh plugin --profile web update dsh-plugin-effort-declare
50
+ ```
51
+
52
+ Or `add dsh-plugin-effort-declare@latest`. Details: [install guide](./INSTALL.en.md) **Update**.
39
53
 
40
54
  Restart DSH, then open **Settings → Reasoning efforts**. Add a custom provider on the Models page first. GitHub, local folder, and uninstall: [install guide](./INSTALL.en.md).
41
55
 
package/README.md CHANGED
@@ -7,13 +7,21 @@
7
7
  </samp>
8
8
  </p>
9
9
 
10
+ <p align="center">
11
+ <a href="https://www.npmjs.com/package/dsh-plugin-effort-declare"><img alt="npm" src="https://img.shields.io/npm/v/dsh-plugin-effort-declare"></a>
12
+ <a href="https://github.com/zimodzh/dsh-plugin-effort-declare/commits/master"><img alt="last commit" src="https://img.shields.io/github/last-commit/zimodzh/dsh-plugin-effort-declare"></a>
13
+ <a href="https://github.com/zimodzh/dsh-plugin-effort-declare"><img alt="repo size" src="https://img.shields.io/github/repo-size/zimodzh/dsh-plugin-effort-declare"></a>
14
+ <a href="./LICENSE"><img alt="license" src="https://img.shields.io/github/license/zimodzh/dsh-plugin-effort-declare"></a>
15
+ <a href="https://github.com/deepseek-ai/deepseek-harness"><img alt="Agent" src="https://img.shields.io/badge/Agent-Deepseek%20Harness-000000"></a>
16
+ </p>
17
+
10
18
  给自己添加的 OpenAI 兼容模型补上「推理强度」选项,对话里的 Effort 行就会出现。
11
19
 
12
- 这是社区插件,不是 DeepSeek 官方项目,也未获官方背书。需要 DeepSeek Harness **0.1.0-rc.8 或更新**。
20
+ > 这是社区插件,不是 DeepSeek 官方项目,也未获官方背书。需要 DeepSeek Harness **0.1.0-rc.8 及以上**。
13
21
 
14
22
  ## 它解决什么
15
23
 
16
- 你在「模型」页手工加的第三方接口,通常没有推理档位,所以对话里看不到 Effort。装上本插件后,设置里会多一页 **推理档位**:勾选这个模型能提供哪些档。对话里怎么选、请求怎么发给接口,仍由 DSH 处理。本插件只改设置,不改密钥。
24
+ 在「模型」页手工加的第三方供应商,需要先声明能选哪些推理档,对话里才会出现 Effort。但通常它们不会声明。所以本插件在设置里加一页 **推理档位**:勾选这个模型能提供哪些档位。对话里怎么选、请求怎么发给接口,仍由 DSH 处理。本插件只改设置,不改密钥。
17
25
 
18
26
  - 不会自动改你没保存过的模型
19
27
  - 不会改你当前选中的档位,也不会改默认档位
@@ -35,7 +43,13 @@
35
43
  dsh plugin --profile web add dsh-plugin-effort-declare
36
44
  ```
37
45
 
38
- 若使用 **DeepSeek Harness Desktop**,必须先设置 `DSH_HOME`,否则会装到另一份目录。步骤见 [安装说明](./INSTALL.md)
46
+ 若使用第三方DSH,必须先设置 `DSH_HOME`,否则会装到另一份目录。步骤见 [安装说明](./INSTALL.md)。更新已安装的 npm 包:
47
+
48
+ ```bash
49
+ dsh plugin --profile web update dsh-plugin-effort-declare
50
+ ```
51
+
52
+ 或 `add dsh-plugin-effort-declare@latest`。详见 [安装说明](./INSTALL.md) 的「更新」。
39
53
 
40
54
  装完后重启 DSH,打开 **设置 → 推理档位**。请先在「模型」页添加过第三方提供方。GitHub、本地文件夹和卸载也在 [安装说明](./INSTALL.md)。
41
55
 
package/lib/client.js CHANGED
@@ -12,9 +12,9 @@ window.__ModuleLoader__.load({
12
12
  *
13
13
  * Levels match `@deepseek-ai/dsh-llm-pi-ai` catalog.ts `THINKING_LEVELS`.
14
14
  * Formats match `SUPPORTED_THINKING_FORMATS` in the same file (rc.8).
15
- * Runtime UI prefers the live settings schema union; these lists are the
16
- * fallback plus a test pin so a silent drift is a failing test, not a
17
- * second hand-maintained copy nobody notices.
15
+ * Tests pin these lists against llm-pi-ai. The settings page never offers
16
+ * the handwritten thinkingFormat list as writable choices only the live
17
+ * schema union, plus a stored value that the union omitted.
18
18
  */
19
19
  /** Selectable reasoning levels, in pi-ai escalation order. */
20
20
  const THINKING_LEVELS = [
@@ -28,22 +28,6 @@ window.__ModuleLoader__.load({
28
28
  ];
29
29
  /** Thinking levels other than Off; Off has its own tri-state control. */
30
30
  const THINKING_LEVELS_WITHOUT_OFF = THINKING_LEVELS.filter((level) => level !== "off");
31
- /**
32
- * openai-completions thinkingFormat values from llm-pi-ai catalog.ts rc.8.
33
- * Tests pin this list; the settings page prefers schema union choices.
34
- */
35
- const FALLBACK_THINKING_FORMATS = [
36
- "openai",
37
- "deepseek",
38
- "openrouter",
39
- "together",
40
- "zai",
41
- "qwen",
42
- "chat-template",
43
- "qwen-chat-template",
44
- "string-thinking",
45
- "ant-ling"
46
- ];
47
31
  /** Wire protocol this plugin's v1 editor supports. */
48
32
  const OPENAI_COMPLETIONS = "openai-completions";
49
33
  /** Settings namespace this plugin writes. */
@@ -434,25 +418,28 @@ window.__ModuleLoader__.load({
434
418
  /**
435
419
  * First paint: `ensure()` (reads only from idle). Never treat ensure as refresh.
436
420
  * Callers that must not apply a stale settlement compare generation themselves.
421
+ *
422
+ * `formats` is the live schema union only. Empty means the dropdown has no
423
+ * writable choices (stored values stay visible via `thinkingFormatChoices`).
437
424
  */
438
425
  async function loadDrafts(api, describe, schema) {
439
426
  await describe.ensure();
440
427
  const mirrored = describe.getSnapshot();
441
428
  if (mirrored.view === void 0) return {
442
429
  writable: false,
443
- formats: [...FALLBACK_THINKING_FORMATS],
430
+ formats: [],
444
431
  drafts: [],
445
432
  error: mirrored.error ?? void 0
446
433
  };
447
434
  const providersResponse = await api.llm.providers({});
448
435
  if (!providersResponse.result.ok) return {
449
436
  writable: mirrored.view.writable,
450
- formats: [...FALLBACK_THINKING_FORMATS],
437
+ formats: [],
451
438
  drafts: [],
452
439
  error: providersResponse.result.error.message
453
440
  };
454
441
  const pi = new Map(mirrored.view.namespaces.map((view) => [view.ns, view])).get(LLM_PI_AI_NS);
455
- let formats = [...FALLBACK_THINKING_FORMATS];
442
+ let formats = [];
456
443
  let schemaDefaultApi;
457
444
  if (pi !== void 0) try {
458
445
  const root = schema.rehydrate(pi.schema);
@@ -488,6 +475,36 @@ window.__ModuleLoader__.load({
488
475
  };
489
476
  }
490
477
  //#endregion
478
+ //#region src/client/schema-ops.ts
479
+ /** Wrap a live settingsSchema service as plain callbacks. */
480
+ function bindSchema(service) {
481
+ return {
482
+ rehydrate: (serialized) => service.rehydrate(serialized),
483
+ nodeAtPath: (root, path) => service.nodeAtPath(root, path),
484
+ getPath: (value, path) => service.getPath(value, path),
485
+ hasPath: (value, path) => service.hasPath(value, path),
486
+ validate: (node, draft) => service.validate(node, draft)
487
+ };
488
+ }
489
+ /**
490
+ * Pre-mutate schema check used by the settings page.
491
+ * A returned string means do not call `settings.mutate`.
492
+ */
493
+ function validateSaveDraft(schema, root, settingsPath, afterModels, afterCompat, willWriteCompat) {
494
+ const modelsNode = schema.nodeAtPath(root, [...settingsPath, "models"]);
495
+ if (modelsNode !== void 0) {
496
+ const error = schema.validate(modelsNode, afterModels);
497
+ if (error !== void 0) return error;
498
+ }
499
+ if (willWriteCompat) {
500
+ const compatNode = schema.nodeAtPath(root, [...settingsPath, "compat"]);
501
+ if (compatNode !== void 0) {
502
+ const error = schema.validate(compatNode, afterCompat);
503
+ if (error !== void 0) return error;
504
+ }
505
+ }
506
+ }
507
+ //#endregion
491
508
  //#region \0dsh-css:C:\Users\zimo\AppData\Roaming\io.github.hairyf.deepseek-harness-desktop\data\dsh\临时目录\dsh-plugin-effort-declare\src\client\effort-declare.module.css.mjs
492
509
  const cssText = ".hYEBUa_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}.hYEBUa_title{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;font-weight:500;line-height:24px}.hYEBUa_intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}.hYEBUa_notice{color:var(--dsw-alias-state-warn-label);margin:0;font-size:12px;line-height:18px}.hYEBUa_savedNotice{color:var(--dsw-alias-state-success-primary);margin:0;font-size:12px;line-height:18px}.hYEBUa_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}.hYEBUa_rows{flex-direction:column;gap:8px;margin:12px 0 0;padding:0;list-style:none;display:flex}.hYEBUa_rowCard{border:1px solid var(--dsw-alias-border-l2);border-radius:12px;flex-direction:column;gap:12px;padding:12px 14px;display:flex}.hYEBUa_rowHead{align-items:baseline;gap:8px;display:flex}.hYEBUa_rowName{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.hYEBUa_rowTag{border:1px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-secondary);border-radius:4px;flex:none;padding:1px 6px;font-size:11px;line-height:16px}.hYEBUa_compatSummary{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.hYEBUa_presetRow,.hYEBUa_actions{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.hYEBUa_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}.hYEBUa_primaryButton,.hYEBUa_secondaryButton{box-sizing:border-box;height:36px;font:inherit;cursor:pointer;border-radius:18px;justify-content:center;align-items:center;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.hYEBUa_primaryButton{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border:none}.hYEBUa_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.hYEBUa_secondaryButton{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0}.hYEBUa_secondaryButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.hYEBUa_primaryButton:disabled,.hYEBUa_secondaryButton:disabled,.hYEBUa_linkButton:disabled,.hYEBUa_input:disabled{opacity:.4;cursor:default}.hYEBUa_primaryButton:focus-visible,.hYEBUa_secondaryButton:focus-visible,.hYEBUa_linkButton:focus-visible,.hYEBUa_input:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}.hYEBUa_linkButton{box-sizing:border-box;height:28px;color:var(--dsw-alias-label-tertiary);font:inherit;cursor:pointer;background:0 0;border:none;border-radius:14px;align-items:center;padding:0 10px;font-size:12px;line-height:18px;display:inline-flex}.hYEBUa_linkButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.hYEBUa_modelEntry{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:8px;padding:10px 0;display:flex}.hYEBUa_modelHead{flex-wrap:wrap;align-items:baseline;gap:8px;display:flex}.hYEBUa_modelId{font-size:13px;font-weight:500;line-height:20px}.hYEBUa_modelName{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.hYEBUa_levels{flex-wrap:wrap;gap:10px 14px;display:flex}.hYEBUa_level{align-items:center;gap:6px;font-size:12px;line-height:18px;display:inline-flex}.hYEBUa_wireRow{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.hYEBUa_input,.hYEBUa_selectInput{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);height:32px;color:var(--dsw-alias-label-primary);font:inherit;border-radius:8px;padding:0 10px;font-size:13px}.hYEBUa_wireInput{width:7em}.hYEBUa_offGroup{flex-direction:column;gap:6px;display:flex}.hYEBUa_advanced{border-top:1px solid var(--dsw-alias-border-l2);padding-top:8px}.hYEBUa_advanced summary{cursor:pointer;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.hYEBUa_advancedBody{flex-direction:column;gap:10px;padding-top:10px;display:flex}.hYEBUa_check{align-items:flex-start;gap:8px;font-size:12px;line-height:18px;display:flex}";
493
510
  const cssTagId = "dsh-plugin-effort-declare/effort-declare.module.css";
@@ -877,7 +894,7 @@ window.__ModuleLoader__.load({
877
894
  loadDrafts(api, describe, schema).then((result) => {
878
895
  if (!generationIsCurrent(generationRef, generation)) return;
879
896
  setWritable(result.writable);
880
- if (result.formats.length > 0) setFormats(result.formats);
897
+ setFormats(result.formats);
881
898
  if (result.error !== void 0) {
882
899
  setStatus("error");
883
900
  setError(result.error);
@@ -885,10 +902,14 @@ window.__ModuleLoader__.load({
885
902
  }
886
903
  const merged = mergeLoadedDrafts(draftsRef.current, result.drafts, { preserveDirty });
887
904
  setDrafts(merged.drafts);
888
- if (merged.conflicted.length > 0) setNotices(Object.fromEntries(merged.conflicted.map((provider) => [provider, {
889
- kind: "conflict",
890
- text: t("dirtyConflict")
891
- }])));
905
+ if (merged.conflicted.length > 0) setNotices((current) => {
906
+ const next = { ...current };
907
+ for (const provider of merged.conflicted) next[provider] = {
908
+ kind: "conflict",
909
+ text: t("dirtyConflict")
910
+ };
911
+ return next;
912
+ });
892
913
  setStatus("ready");
893
914
  }, (failure) => {
894
915
  if (!generationIsCurrent(generationRef, generation)) return;
@@ -906,23 +927,31 @@ window.__ModuleLoader__.load({
906
927
  }, [reload]);
907
928
  (0, react.useEffect)(() => {
908
929
  if (props.subscribeInvalidate === void 0) return void 0;
909
- return props.subscribeInvalidate(() => {
910
- reload(true);
930
+ return props.subscribeInvalidate((source) => {
931
+ if (source === "settings" || source === "directory") reload(true);
911
932
  });
912
933
  }, [props.subscribeInvalidate, reload]);
934
+ const patchNotice = (provider, notice) => {
935
+ setNotices((current) => {
936
+ const copy = { ...current };
937
+ if (notice === void 0) delete copy[provider];
938
+ else copy[provider] = notice;
939
+ return copy;
940
+ });
941
+ };
913
942
  const save = async (draft) => {
914
- if (api === void 0 || describe === void 0) return;
943
+ if (api === void 0 || describe === void 0 || schema === void 0) return;
915
944
  if (status === "loading" || busyRoute !== null) return;
916
945
  const blocking = draft.models.map((row) => errorText(modelEffortError(row), t)).find((text) => text !== void 0);
917
946
  if (blocking !== void 0) {
918
- setNotices({ [draft.provider]: {
947
+ patchNotice(draft.provider, {
919
948
  kind: "error",
920
949
  text: blocking
921
- } });
950
+ });
922
951
  return;
923
952
  }
924
953
  setBusyRoute(draft.provider);
925
- setNotices({});
954
+ patchNotice(draft.provider, void 0);
926
955
  try {
927
956
  const ops = buildSaveOps({
928
957
  settingsPath: draft.settingsPath,
@@ -935,16 +964,38 @@ window.__ModuleLoader__.load({
935
964
  setDrafts((current) => current.map((row) => row.provider === draft.provider ? alignDraft(row) : row));
936
965
  return;
937
966
  }
967
+ const willWriteCompat = ops.some((op) => op.path.length > draft.settingsPath.length && op.path[draft.settingsPath.length] === "compat");
968
+ const pi = describe.getSnapshot().view?.namespaces.find((view) => view.ns === LLM_PI_AI_NS);
969
+ if (pi !== void 0) {
970
+ let root;
971
+ try {
972
+ root = schema.rehydrate(pi.schema);
973
+ } catch {
974
+ root = void 0;
975
+ }
976
+ if (root !== void 0) {
977
+ const schemaError = validateSaveDraft(schema, root, draft.settingsPath, draft.models, draft.compat, willWriteCompat);
978
+ if (schemaError !== void 0) {
979
+ patchNotice(draft.provider, {
980
+ kind: "error",
981
+ text: schemaError
982
+ });
983
+ return;
984
+ }
985
+ }
986
+ }
938
987
  const response = await api.settings.mutate({
939
988
  ns: LLM_PI_AI_NS,
940
989
  ops,
941
990
  expectedRevision: draft.revision
942
991
  });
943
992
  if (!response.result.ok) {
944
- setNotices({ [draft.provider]: {
945
- kind: "error",
946
- text: response.result.error.code === "settings-conflict" ? t("conflict") : response.result.error.message
947
- } });
993
+ const conflict = response.result.error.code === "settings-conflict";
994
+ patchNotice(draft.provider, {
995
+ kind: conflict ? "conflict" : "error",
996
+ text: conflict ? t("conflict") : response.result.error.message
997
+ });
998
+ if (conflict) reload(true);
948
999
  return;
949
1000
  }
950
1001
  const view = response.result.value;
@@ -953,15 +1004,15 @@ window.__ModuleLoader__.load({
953
1004
  user: view.user ?? {},
954
1005
  revision: view.revision
955
1006
  }));
956
- setNotices({ [draft.provider]: {
1007
+ patchNotice(draft.provider, {
957
1008
  kind: "saved",
958
1009
  text: t("saved")
959
- } });
1010
+ });
960
1011
  } catch (failure) {
961
- setNotices({ [draft.provider]: {
1012
+ patchNotice(draft.provider, {
962
1013
  kind: "error",
963
1014
  text: failure instanceof Error ? failure.message : t("loadError")
964
- } });
1015
+ });
965
1016
  } finally {
966
1017
  setBusyRoute(null);
967
1018
  }
@@ -969,6 +1020,8 @@ window.__ModuleLoader__.load({
969
1020
  const showLoading = status === "loading" && drafts.length === 0;
970
1021
  const showEmpty = status === "ready" && drafts.length === 0;
971
1022
  const showList = drafts.length > 0;
1023
+ const hasCardFailure = Object.values(notices).some((notice) => notice.kind === "conflict" || notice.kind === "error");
1024
+ const showReload = status === "error" || hasCardFailure || showEmpty || !writable || status === "loading" || showList;
972
1025
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
973
1026
  className: effort_declare_module_css_default.section,
974
1027
  children: [
@@ -992,7 +1045,7 @@ window.__ModuleLoader__.load({
992
1045
  className: effort_declare_module_css_default.error,
993
1046
  children: error
994
1047
  }) : null,
995
- status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1048
+ showReload ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
996
1049
  type: "button",
997
1050
  className: effort_declare_module_css_default.secondaryButton,
998
1051
  onClick: () => {
@@ -1018,22 +1071,14 @@ window.__ModuleLoader__.load({
1018
1071
  notice: notices[draft.provider],
1019
1072
  t,
1020
1073
  onChange: (next) => {
1021
- setNotices((current) => {
1022
- const copy = { ...current };
1023
- delete copy[next.provider];
1024
- return copy;
1025
- });
1074
+ patchNotice(next.provider, void 0);
1026
1075
  setDrafts((current) => current.map((row) => row.provider === next.provider ? next : row));
1027
1076
  },
1028
1077
  onSave: (next) => {
1029
1078
  save(next);
1030
1079
  },
1031
1080
  onCancel: (next) => {
1032
- setNotices((current) => {
1033
- const copy = { ...current };
1034
- delete copy[next.provider];
1035
- return copy;
1036
- });
1081
+ patchNotice(next.provider, void 0);
1037
1082
  setDrafts((current) => current.map((row) => row.provider === next.provider ? {
1038
1083
  ...row,
1039
1084
  models: cloneModels(row.originalModels),
@@ -1046,17 +1091,6 @@ window.__ModuleLoader__.load({
1046
1091
  });
1047
1092
  }
1048
1093
  //#endregion
1049
- //#region src/client/schema-ops.ts
1050
- /** Wrap a live settingsSchema service as plain callbacks. */
1051
- function bindSchema(service) {
1052
- return {
1053
- rehydrate: (serialized) => service.rehydrate(serialized),
1054
- nodeAtPath: (root, path) => service.nodeAtPath(root, path),
1055
- getPath: (value, path) => service.getPath(value, path),
1056
- hasPath: (value, path) => service.hasPath(value, path)
1057
- };
1058
- }
1059
- //#endregion
1060
1094
  //#region src/client/locales.ts
1061
1095
  const NS = "plugin-effort-declare";
1062
1096
  const zh = {
@@ -1090,7 +1124,7 @@ window.__ModuleLoader__.load({
1090
1124
  clear: "清除本模型声明",
1091
1125
  advanced: "高级:协议方言",
1092
1126
  thinkingFormat: "thinkingFormat",
1093
- thinkingFormatDefault: "默认(openai)",
1127
+ thinkingFormatDefault: "默认(省略该键)",
1094
1128
  supportsDeveloperRole: "系统提示走 system 而不是 developer(supportsDeveloperRole: false)",
1095
1129
  supportsReasoningEffort: "不发 reasoning_effort,只发开关(supportsReasoningEffort: false)",
1096
1130
  developerTrueHint: "当前文档是 supportsDeveloperRole: true。v1 只能强制 false 或缺席;勾选会写成 false,取消勾选会删除该键。",
@@ -1132,7 +1166,7 @@ window.__ModuleLoader__.load({
1132
1166
  clear: "Clear this model’s declaration",
1133
1167
  advanced: "Advanced: protocol dialect",
1134
1168
  thinkingFormat: "thinkingFormat",
1135
- thinkingFormatDefault: "Default (openai)",
1169
+ thinkingFormatDefault: "Default (omit the key)",
1136
1170
  supportsDeveloperRole: "Send system prompts as system, not developer (supportsDeveloperRole: false)",
1137
1171
  supportsReasoningEffort: "Do not send reasoning_effort; switch only (supportsReasoningEffort: false)",
1138
1172
  developerTrueHint: "The document has supportsDeveloperRole: true. v1 can only force false or omit the key; checking writes false, unchecking deletes the key.",
@@ -1181,7 +1215,8 @@ window.__ModuleLoader__.load({
1181
1215
  rehydrate: (serialized) => settingsSchema.rehydrate(serialized),
1182
1216
  nodeAtPath: (root, path) => settingsSchema.nodeAtPath(root, path),
1183
1217
  getPath: (value, path) => settingsSchema.getPath(value, path),
1184
- hasPath: (value, path) => settingsSchema.hasPath(value, path)
1218
+ hasPath: (value, path) => settingsSchema.hasPath(value, path),
1219
+ validate: (node, draft) => settingsSchema.validate(node, draft)
1185
1220
  });
1186
1221
  const t = ctx.locale.bind(NS);
1187
1222
  const describe = ctx.settingsScope.describe();
@@ -1191,6 +1226,9 @@ window.__ModuleLoader__.load({
1191
1226
  for (const listener of invalidation) listener(source);
1192
1227
  };
1193
1228
  const disposers = [
1229
+ describe.subscribe(() => {
1230
+ emit("settings");
1231
+ }),
1194
1232
  ctx.remote.$on("settings/document-updated", (ns) => {
1195
1233
  if (ns !== "llm-pi-ai") return;
1196
1234
  emit("settings");
@@ -1215,14 +1253,13 @@ window.__ModuleLoader__.load({
1215
1253
  ctx.slots.inject("settings.section", () => ctx.slots.register({
1216
1254
  name: "settings.section",
1217
1255
  id: "effort-declare",
1218
- order: 15,
1256
+ order: 12,
1219
1257
  label: () => t("nav"),
1220
1258
  locale: NS,
1221
1259
  inject: () => ({
1222
1260
  api: connection.api,
1223
1261
  describe,
1224
1262
  schema,
1225
- t,
1226
1263
  subscribeInvalidate
1227
1264
  })
1228
1265
  }, EffortDeclareSection));