dsh-custom-mode 2.2.1 → 2.2.3

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.i18n.yaml CHANGED
@@ -1,5 +1,5 @@
1
1
  # 双语配对一致性记录:两侧在「上次确认一致」时的 git blob 哈希。
2
2
  # 两份文档权威相同——改完任意一侧,请把另一侧也改掉,然后重新记录:
3
3
  # node tools/verify-translation-pairing.mjs --write
4
- README.md: 56782bdda64e7f6bc4bbeff3d83d5f194c9db794
5
- README.zh.md: e117f0d585894980bd28b4013c82b24347ca97a7
4
+ README.md: 00045c14e30c98d389fc658906146272bfd82c7f
5
+ README.zh.md: afac5524be240ba1c38a6ac9ba17be15e0acc4bd
package/README.md CHANGED
@@ -24,7 +24,7 @@ Edit a DeepSeek Harness (`dsh`) custom mode: system prompt, base mode, and plugi
24
24
  Pin the version. A bare name can install an older build during pnpm's release cooldown. Details: [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md).
25
25
 
26
26
  ```sh
27
- dsh plugin --profile web add dsh-custom-mode@2.2.1
27
+ dsh plugin --profile web add dsh-custom-mode@2.2.3
28
28
  ```
29
29
 
30
30
  Restart the process that serves the profile. The version on the settings page is the one that is running.
@@ -37,17 +37,17 @@ On Windows, `install.sh` is bash and it needs a `dsh` executable on `PATH`. Othe
37
37
 
38
38
  1. Open Settings → Custom mode.
39
39
  2. Edit the system prompt. Save is directly under the editor.
40
- 3. A new session uses this assistant when `settings.yaml` has no `agent-presets.default` yet. An existing value is left as it is.
40
+ 3. A new session uses this assistant when `settings.yaml` has no `agent-presets.default` yet. An existing value is left as it is. Flow-style, scalar, and alias `agent-presets` values are preserved without inserting YAML into them.
41
41
 
42
42
  The prompt is `prompt.md`. The next time a model step is assembled, that file is read again. History can load an older draft into the editor; loading does not write. A save computed against an older copy is refused when the file changed underneath. The model can change the same file only through `custom_prompt`, which asks first.
43
43
 
44
- New assistants start as one posture. That choice applies only to the assistant you create next.
44
+ The page is two branches. This assistant edits the one you have open, and all five base modes can be chosen. New assistant writes only when you click New assistant: Coding, Writing, and Chat stay selectable, and only the official Standard mode is lit. The other four official modes are dim and cannot be chosen there.
45
45
 
46
46
  | Posture | After create |
47
47
  | --- | --- |
48
- | Develop | Built-in Standard tool set. |
49
- | Write | Terminal off. File tools on. |
50
- | Chat | Files and terminal off. |
48
+ | Develop | Official Standard mode, tools as shipped. |
49
+ | Write | Official Standard mode. Terminal off, file tools on. |
50
+ | Chat | Official Standard mode. Files and terminal off. |
51
51
 
52
52
  ## Boundaries
53
53
 
@@ -59,13 +59,28 @@ New assistants start as one posture. That choice applies only to the assistant y
59
59
 
60
60
  Declared range: `>=0.1.5-rc.2 <0.2.0-0 || >=0.1.6-alpha.1 <0.2.0-0 || >=0.1.7-alpha.1 <0.2.0-0 || >=0.2.0-0 <0.3.0-0 || >=0.2.1-alpha.1 <0.3.0-0`
61
61
 
62
- | dsh | Claim | Checked for 2.2.1 |
62
+ On 2026-10-09, npm `latest` and `next` still name `0.2.0-rc.2`; `alpha` names `0.2.1-alpha.2`, published that day. Upstream has not published a final stable version yet.
63
+
64
+ | dsh | Channel | Checked for 2.2.3 |
63
65
  | --- | --- | --- |
64
- | `0.2.0-rc.2` | declared | Windows web profile, before this release |
65
- | `0.2.1-alpha.1` | declared, in CI | not re-run for this release |
66
- | `0.1.7-rc.2` | declared, in CI | not re-run for this release |
66
+ | `0.2.0-rc.2` | npm `latest` / `next` | Exact npm host, packed plugin load, API lifecycle, settings and picker in Windows web profile |
67
+ | `0.2.1-alpha.2` | npm `alpha` | The same isolated checks, against this host's own preset declarations |
68
+ | `0.1.7-rc.2` | earlier declared line | Kept in the CI regression matrix |
69
+
70
+ Release waits for the unit, boot, and browser workflows. To reproduce against an already installed exact host:
71
+
72
+ ```sh
73
+ node tools/compat-check.mjs --host-install /path/to/host --out /tmp/custom-mode-check
74
+ node tools/runtime-check.mjs --host-install /path/to/host --out /tmp/custom-mode-check --browser /path/to/chrome
75
+ ```
76
+
77
+ Both tools use a generated home and ordinary mock prompts. The runtime check exercises all five base modes, repeated saves, uninstall, reinstall, data preservation, and repeated installation. No model calls are needed. VMware and native Linux desktop results are not claimed.
78
+
79
+ ## Plugin markets
80
+
81
+ `dshmarket`, DSH Plugin Hub (`dsh-plugin`), and DSH Plugin Store (`dshmarketplace-plugin`) use different catalogs but install the same profile bundle. The root npm manifest provides `dsh.bundle.patch`, the browser export, the public repository, and matching compatibility ranges; no extra runtime adapter or build script is needed.
67
82
 
68
- The unit suite also passed under WSL2 Ubuntu 26.04 (Node only, no dsh process, no browser). That is not a Linux desktop result.
83
+ Run `node tools/market-check.mjs --out /tmp/custom-mode-markets.json` to check their public discovery data. An inaccessible catalog or an unverified/stale listing is reported explicitly. The tool never runs catalog-provided shell commands or submits a listing. [Market interfaces and limits](docs/MARKETPLACES.md).
69
84
 
70
85
  ## Docs
71
86
 
package/README.zh.md CHANGED
@@ -24,7 +24,7 @@
24
24
  钉版本。只写包名时,pnpm 的发布冷却期可能装到旧版。细节见 [docs/TROUBLESHOOTING.zh.md](docs/TROUBLESHOOTING.zh.md)。
25
25
 
26
26
  ```sh
27
- dsh plugin --profile web add dsh-custom-mode@2.2.1
27
+ dsh plugin --profile web add dsh-custom-mode@2.2.3
28
28
  ```
29
29
 
30
30
  重启为这个 profile 提供服务的进程。设置页上的版本号才是正在运行的版本。
@@ -37,17 +37,17 @@ dsh plugin --profile web add dsh-custom-mode@2.2.1
37
37
 
38
38
  1. 打开设置 → 自定义模式。
39
39
  2. 改系统提示词。保存按钮在编辑器下面。
40
- 3. `settings.yaml` 里还没有 `agent-presets.default` 时,新会话用这个助手。已经写过的值保持原样。
40
+ 3. `settings.yaml` 里还没有 `agent-presets.default` 时,新会话用这个助手。已经写过的值保持原样。`agent-presets` 使用行内对象、标量或别名时也原样保留,不向里面插入 YAML。
41
41
 
42
42
  提示词是 `prompt.md`。下一步组装模型输入时会重读这个文件。历史可以把旧稿载入编辑器;载入不落盘。打开页面之后文件被改过时,这一次保存会被拒绝。模型要改同一份文件,只能走 `custom_prompt`,并且先问过你。
43
43
 
44
- 新建助手时选一种姿态。它只影响下一次新建。
44
+ 页面分成两支。当前助手改的是已经打开的这一位,基础模式五档都可以点。新建助手只在点「新增助手」时写入:开发、写作、聊天可以点,工具底只有官方标准模式是亮的。其余四档是暗的,在这一支里点不了。
45
45
 
46
46
  | 姿态 | 新建之后 |
47
47
  | --- | --- |
48
- | 开发 | 内置标准模式的工具集。 |
49
- | 写作 | 终端关,文件工具开。 |
50
- | 聊天 | 文件和终端关。 |
48
+ | 开发 | 官方标准模式,工具按出厂。 |
49
+ | 写作 | 官方标准模式。终端关,文件工具开。 |
50
+ | 聊天 | 官方标准模式。文件和终端关。 |
51
51
 
52
52
  ## 边界
53
53
 
@@ -59,13 +59,28 @@ dsh plugin --profile web add dsh-custom-mode@2.2.1
59
59
 
60
60
  声明范围:`>=0.1.5-rc.2 <0.2.0-0 || >=0.1.6-alpha.1 <0.2.0-0 || >=0.1.7-alpha.1 <0.2.0-0 || >=0.2.0-0 <0.3.0-0 || >=0.2.1-alpha.1 <0.3.0-0`
61
61
 
62
- | dsh | 声明 | 2.2.1 核对 |
62
+ 2026-10-09 实时核对 npm:`latest` 和 `next` 仍是 `0.2.0-rc.2`,`alpha` 是当天发布的 `0.2.1-alpha.2`。上游尚未发布去掉预发布后缀的正式稳定版。
63
+
64
+ | dsh | 通道 | 2.2.3 核对 |
63
65
  | --- | --- | --- |
64
- | `0.2.0-rc.2` | 声明支持 | 本版发布前的 Windows web profile |
65
- | `0.2.1-alpha.1` | 声明支持,CI 有这条腿 | 本版没有重跑 |
66
- | `0.1.7-rc.2` | 声明支持,CI 有这条腿 | 本版没有重跑 |
66
+ | `0.2.0-rc.2` | npm `latest` / `next` | 精确 npm 宿主、打包加载、API 生命周期、Windows web profile 设置页与选择器 |
67
+ | `0.2.1-alpha.2` | npm `alpha` | 同一套隔离检查,读取该宿主自己的预设声明 |
68
+ | `0.1.7-rc.2` | 先前声明支持的版本 | 保留在 CI 回归矩阵里 |
69
+
70
+ 发布必须等待单元、启动和浏览器工作流通过。已有精确版本的宿主安装时,可以这样复现:
71
+
72
+ ```sh
73
+ node tools/compat-check.mjs --host-install /path/to/host --out /tmp/custom-mode-check
74
+ node tools/runtime-check.mjs --host-install /path/to/host --out /tmp/custom-mode-check --browser /path/to/chrome
75
+ ```
76
+
77
+ 两项工具只使用新生成的 home 和普通 mock 提示词。运行检查覆盖全部五档基础模式、重复保存、卸载、重装、用户数据保留与重复安装,不需要调用模型。不宣称 VMware 或原生 Linux 桌面已通过验收。
78
+
79
+ ## 插件市场
80
+
81
+ `dshmarket`、DSH Plugin Hub(`dsh-plugin`)和 DSH Plugin Store(`dshmarketplace-plugin`)使用不同目录,但安装的是同一个 profile bundle。根 npm 清单提供 `dsh.bundle.patch`、浏览器导出、公开仓库和一致的兼容范围,不需要额外的运行时适配器或构建脚本。
67
82
 
68
- 单元套件在 WSL2 Ubuntu 26.04 下也通过了(只有 Node,没有 dsh 进程,没有浏览器)。这不是 Linux 桌面的结果。
83
+ 运行 `node tools/market-check.mjs --out /tmp/custom-mode-markets.json` 检查它们的公开发现数据。目录不可访问、条目未审核或数据过期都会明确报告。工具不执行目录提供的 shell 命令,也不提交收录。[市场接口与限制](docs/MARKETPLACES.zh.md)。
69
84
 
70
85
  ## 文档
71
86
 
package/client.js CHANGED
@@ -2,11 +2,10 @@
2
2
  * Browser half: 「自定义模式」settings page — the assistant manager.
3
3
  *
4
4
  * Blocks:
5
- * 0. 助手 — pick, create, duplicate, delete.
6
- * 1. 系统提示词 — the prompt, then Save directly under it.
7
- * 2. 模式名称 — display name and description.
8
- * 3. 基础模式 — standard / ptc / minimal / cordis / all.
9
- * 4. 插件开关 — collapsed until the user opens it.
5
+ * 0. 助手 — which assistant is selected.
6
+ * 1. 当前助手 — prompt, name, five live base modes, plugin switches.
7
+ * 2. 新建助手 — Coding / Writing / Chat stay selectable. The official
8
+ * modes are shown beside them, and only Standard is lit.
10
9
  *
11
10
  * Three decisions worth knowing before editing this file:
12
11
  *
@@ -62,13 +61,20 @@ try {
62
61
  "nav": "自定义模式",
63
62
  "assistant.heading": "助手",
64
63
  "assistant.hint": "每个助手单独保存系统提示词、基础模式和插件开关。",
65
- "posture.hint": "只用于新建,不改变当前助手。",
64
+ "posture.hint": "这一支只在点「新增助手」时写入。",
65
+ "tree.posture": "起步",
66
+ "tree.base": "工具底",
67
+ "tree.baseHold": "新建时固定用官方标准模式。",
68
+ "tree.baseSkip": "这一支点不了。建好后到「当前助手」里改。",
69
+ "tree.baseLocked": "只有官方标准模式是亮的。其余四档是暗的,新建时点不了。",
66
70
  "posture.develop.label": "开发",
67
- "posture.develop.note": "新建后:标准模式,工具按出厂。",
71
+ "posture.develop.note": "官方标准模式,工具按出厂。",
68
72
  "posture.write.label": "写作",
69
- "posture.write.note": "新建后:终端关闭,文件读写打开。",
73
+ "posture.write.note": "官方标准模式。终端关闭,文件读写打开。",
70
74
  "posture.chat.label": "聊天",
71
- "posture.chat.note": "新建后:文件和终端关闭。",
75
+ "posture.chat.note": "官方标准模式。文件和终端关闭。",
76
+ "editor.heading": "当前助手",
77
+ "editor.hint": "这一支改上面选中的助手。基础模式五档都可以点。",
72
78
  "assistant.empty": "还没有助手,用下面的输入框新建一个。",
73
79
  "assistant.loadingList": "正在读取助手列表…",
74
80
  "assistant.newPlaceholder": "新助手的名字(例如:写作助手)",
@@ -78,7 +84,7 @@ try {
78
84
  "assistant.brokenHint": "此模式在本机无法启动,不会出现在新会话菜单里。点「按本线修复」只关闭那些行。",
79
85
  "assistant.switchHint": "切换助手保留各自的未保存草稿;列表中的「未保存」标记即为此。",
80
86
  "btn.create": "新增助手",
81
- "create.heading": "新建",
87
+ "create.heading": "新建助手",
82
88
  "btn.creating": "创建中…",
83
89
  "btn.duplicate": "复制",
84
90
  "btn.moveUp": "上移",
@@ -196,7 +202,7 @@ try {
196
202
  "name.placeholder": "自定义模式",
197
203
  "name.descriptionPlaceholder": "描述,显示在模式菜单里,可留空",
198
204
  "mode.heading": "基础模式",
199
- "mode.hint": "选一套官方预设作为工具底。提示词以本页为准。",
205
+ "mode.hint": "五档都可以点。改的是当前这位助手。提示词仍以本页为准。",
200
206
  "rows.heading": "插件开关",
201
207
  "rows.filterPlaceholder": "按名称或 id 筛选",
202
208
  "cap.heading": "插件开关",
@@ -296,13 +302,20 @@ try {
296
302
  "nav": "Custom mode",
297
303
  "assistant.heading": "Assistants",
298
304
  "assistant.hint": "Each assistant keeps its own system prompt, base mode and plugin switches.",
299
- "posture.hint": "Applies only to the assistant you are about to create.",
305
+ "posture.hint": "This branch is written only when you click New assistant.",
306
+ "tree.posture": "Starter",
307
+ "tree.base": "Tool base",
308
+ "tree.baseHold": "A new assistant always starts on the official Standard mode.",
309
+ "tree.baseSkip": "Cannot be chosen on this branch. Change it later under This assistant.",
310
+ "tree.baseLocked": "Only the official Standard mode is lit. The other four are dim and cannot be chosen when creating.",
300
311
  "posture.develop.label": "Coding",
301
- "posture.develop.note": "After create: Standard, tools as shipped.",
312
+ "posture.develop.note": "Official Standard mode, tools as shipped.",
302
313
  "posture.write.label": "Writing",
303
- "posture.write.note": "After create: terminal off, file access on.",
314
+ "posture.write.note": "Official Standard mode. Terminal off, file access on.",
304
315
  "posture.chat.label": "Chat",
305
- "posture.chat.note": "After create: files and terminal off.",
316
+ "posture.chat.note": "Official Standard mode. Files and terminal off.",
317
+ "editor.heading": "This assistant",
318
+ "editor.hint": "This branch edits the assistant selected above. All five base modes can be chosen.",
306
319
  "assistant.empty": "No assistants yet — create one with the field below.",
307
320
  "assistant.loadingList": "Loading assistants…",
308
321
  "assistant.newPlaceholder": "Name of the new assistant (e.g. Writing assistant)",
@@ -430,7 +443,7 @@ try {
430
443
  "name.placeholder": "Custom mode",
431
444
  "name.descriptionPlaceholder": "Description, shown in the mode menu. Optional.",
432
445
  "mode.heading": "Base mode",
433
- "mode.hint": "Pick a shipped preset as the tool base. The prompt on this page still applies.",
446
+ "mode.hint": "All five can be chosen. This edits the assistant selected above. The prompt on this page still applies.",
434
447
  "rows.heading": "Plugin switches",
435
448
  "rows.filterPlaceholder": "Filter by name or id",
436
449
  "cap.heading": "Plugin switches",
@@ -748,18 +761,19 @@ try {
748
761
  ".cpfe-hint{display:flex;flex-direction:column;gap:2px;margin:0 0 6px}",
749
762
  ".cpfe-hint-line{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
750
763
  ".cpfe-hint-detail{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
751
- // 描述:多行、自适应高度(没有多行输入组件,所以用 textarea + 同一批语义变量)
752
- ".cpfe-desc{box-sizing:border-box;min-height:40px;max-height:120px;resize:vertical;padding:8px 12px;border-radius:var(--dsw-radius-md);border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:20px;margin-bottom:0}",
764
+ // The native InlineEditor saves on Enter and autofocuses; document editing must keep
765
+ // Enter as a newline and manual Save. Use its exact surface/font tokens for these
766
+ // controlled textareas, with only document height and width as layout differences.
767
+ ".cpfe-desc,.cpfe-editor{box-sizing:border-box;min-width:0;margin:0;padding:3px 8px;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-sm);outline:none;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font:var(--dsw-font-xs-13);font-family:Inter,var(--dsw-font-family);overflow-y:auto}",
768
+ ".cpfe-desc:focus,.cpfe-editor:focus{border-color:var(--dsw-alias-state-business-primary)}",
769
+ ".cpfe-desc{min-height:40px;max-height:120px;resize:vertical}",
753
770
  ".cpfe-base-pending{color:var(--dsw-alias-state-warn-primary)}",
754
771
  ".cpfe-note{display:block;margin:8px 0 0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
755
- ".cpfe-mono{display:block;margin:0;font-family:var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace);font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}",
772
+ ".cpfe-mono{display:block;margin:0;font:inherit;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}",
756
773
  ".cpfe-pills{display:flex;flex-wrap:wrap;gap:6px;align-items:center}",
757
774
  ".cpfe-pills > *{flex:0 0 auto;white-space:nowrap}",
758
775
  ".cpfe-pills button{white-space:nowrap}",
759
776
  ".cpfe-picker{display:flex;align-items:center;gap:8px;margin:2px 0 4px}",
760
- // 壳的菜单浮层靠一个独立的 backing 元素上色(实测在 0.1.7-rc.2 + headless 下那块是透明的,
761
- // 文字会"压"在下面的输入框上)。用官方 token 显式补一层底色,真实浏览器与 headless 一致。
762
- ".cpfe-menu{background:var(--dsw-menu-surface-fill,var(--dsw-alias-bg-layer-3));border-radius:var(--dsw-radius-lg);box-shadow:var(--dsw-elevation-soft)}",
763
777
  ".cpfe-history{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:10px}",
764
778
  ".cpfe-history-label{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
765
779
  ".cpfe-history-hint{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
@@ -767,14 +781,16 @@ try {
767
781
  ".cpfe-actions{display:flex;flex-wrap:wrap;gap:var(--g);align-items:center;margin-top:4px}",
768
782
  ".cpfe-newrow{display:flex;gap:var(--g);align-items:center;flex-wrap:wrap;margin-top:10px}",
769
783
  ".cpfe-postures{display:flex;flex-direction:column;gap:6px}",
770
- ".cpfe-create{display:flex;flex-direction:column;gap:6px;margin-top:8px}",
784
+ ".cpfe-create{display:flex;flex-direction:column;gap:6px}",
785
+ ".cpfe-branch{display:flex;flex-direction:column;gap:16px;margin-top:8px;padding-left:12px;border-left:1px solid var(--dsw-alias-border-l1)}",
786
+ ".cpfe-node{display:flex;flex-direction:column;gap:4px}",
787
+ ".cpfe-node-label{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
771
788
  ".cpfe-field{display:flex;width:100%;margin-bottom:8px}",
772
789
  ".cpfe-row-head{font-size:14px;line-height:22px;color:var(--dsw-alias-label-primary)}",
773
790
  ".cpfe-row-switch{flex:0 0 auto}",
774
791
  ".cpfe-row-line{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-width:0}",
775
792
  ".cpfe-row-note{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
776
- ".cpfe-row-toggle{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:0;border-radius:var(--dsw-radius-sm);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}",
777
- ".cpfe-row-toggle:hover{background:var(--dsw-alias-bg-layer-2)}",
793
+ ".cpfe-row-toggle{flex:0 0 auto}",
778
794
  ".cpfe-row-open{border-color:var(--dsw-alias-border-l2)}",
779
795
  ".cpfe-row-detail{display:flex;flex-direction:column;gap:4px;margin:0 0 12px;padding:0 0 0 16px;border:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
780
796
  ".cpfe-detail-line{display:flex;gap:8px;min-width:0}",
@@ -794,19 +810,17 @@ try {
794
810
  ".cpfe-row-meta{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}",
795
811
  ".cpfe-row-badges{display:flex;align-items:center;gap:4px;flex:0 1 auto;min-width:0}",
796
812
  ".cpfe-row{display:flex;gap:12px;align-items:center;box-sizing:border-box;min-height:74px;padding:16px 0;border:0;border-radius:0;background:none}",
797
- ".cpfe-editor{box-sizing:border-box;width:100%;min-height:120px;resize:vertical;padding:12px;border-radius:var(--dsw-radius-md);border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font-family:var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace);font-size:13px;line-height:20px}",
813
+ ".cpfe-editor{width:100%;min-height:120px;resize:vertical}",
798
814
  ".cpfe-bar{display:flex;align-items:center;gap:var(--g);flex-wrap:wrap;margin-top:8px}",
799
- ".cpfe-disclosure{appearance:none;display:inline-flex;align-items:center;gap:6px;margin:8px 0 0;padding:0;border:0;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;font-weight:400;cursor:pointer}",
800
- ".cpfe-filter{box-sizing:border-box;width:100%;height:36px;margin:0 0 8px;padding:0 12px;border-radius:var(--dsw-radius-md);border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}",
815
+ ".cpfe-disclosure{align-self:flex-start;margin-top:8px}",
801
816
  ".cpfe .cpfe-danger{margin-left:auto}",
802
817
  ".cpfe-status{font-size:13px;line-height:20px}",
803
818
  ".cpfe-ok{color:var(--dsw-alias-label-secondary)}",
804
819
  ".cpfe-err{color:var(--dsw-alias-state-error-primary)}",
805
820
  ".cpfe-dirty{color:var(--dsw-alias-state-warn-primary)}",
806
- ".cpfe-danger{color:var(--dsw-alias-state-error-primary)}",
807
821
  ".cpfe-meta-line{display:flex;flex-wrap:wrap;align-items:baseline;gap:8px;min-width:0}",
808
822
  ".cpfe-version{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);white-space:nowrap}",
809
- ".cpfe-path{font-family:var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace);font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}",
823
+ ".cpfe-path{font:inherit;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}",
810
824
  // ── fallback-path controls (unused when the shell provides the atoms) ──
811
825
  ".cpfe-btn{appearance:none;cursor:pointer;padding:0 14px;height:36px;border-radius:var(--dsw-radius-md);border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font:inherit;font-size:14px}",
812
826
  ".cpfe-btn:disabled{opacity:.4;cursor:not-allowed}",
@@ -820,7 +834,7 @@ try {
820
834
  // 光拦状态还不够:DOM 会先显示用户敲进去的字/翻过去的开关,等下一次重渲染才被拉回来 ——
821
835
  // 那就是"我明明改了,它自己弹回去了"。`pointer-events` 让点击根本到不了控件,
822
836
  // 视觉上也给一个明确的"此刻不能动"。
823
- ".cpfe-busy .cpfe-switch,.cpfe-busy .cpfe-row-switch,.cpfe-busy .cpfe-row-toggle,.cpfe-busy .cpfe-input,.cpfe-busy .cpfe-field,.cpfe-busy .cpfe-editor,.cpfe-busy .cpfe-pill,.cpfe-busy .cpfe-selector{pointer-events:none;opacity:.65}",
837
+ ".cpfe-busy .cpfe-switch,.cpfe-busy .cpfe-row-switch,.cpfe-busy .cpfe-row-toggle,.cpfe-busy .cpfe-input,.cpfe-busy .cpfe-field,.cpfe-busy .cpfe-pill,.cpfe-busy .cpfe-selector{pointer-events:none}",
824
838
  ".cpfe-busy .cpfe-editor{cursor:progress}",
825
839
  ".cpfe-tag{font-size:12px;line-height:18px;padding:0 5px;border-radius:4px;border:.5px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-secondary)}",
826
840
  ".cpfe-tag-success,.cpfe-tag-info{color:var(--dsw-alias-state-success-primary)}",
@@ -828,18 +842,7 @@ try {
828
842
  ".cpfe-tag-danger{color:var(--dsw-alias-state-error-primary)}",
829
843
  ".cpfe-pill{appearance:none;cursor:pointer;height:28px;padding:0 12px;border-radius:999px;border:.5px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px}",
830
844
  ".cpfe-pill-on{border-color:var(--dsw-alias-label-primary);color:var(--dsw-alias-label-primary)}",
831
- // ── 官方的「设置项下拉」形态 ────────────────────────────────────────────
832
- // 实测(**0.1.7-rc.2 与 0.2.0-rc.2 各量一次,结论相同**):官方的 `_selector` 是
833
- // height 36px · border-radius var(--dsw-radius-md) · background var(--dsw-alias-bg-module-platform)
834
- // · padding 0 14px · font-size 14px/line-height 22px · gap 12px,hover 换
835
- // var(--dsw-alias-interactive-bg-hover)。
836
- // 壳的 Button 只有 primary / ghost / outline / toolbar 四种变体,没有"灰底选择器"这一种 ——
837
- // 官方各设置页也是**各自定义** `_selector`(oY77xG / hVGvvW / _2XZxNq / T1PP_q 各一份、
838
- // 度量完全一致),所以这里照同一套度量自绘,做法与官方一致。
839
- // 双类选择器(`.cpfe .cpfe-selector`)用来压过壳 Button 的单类变体规则,
840
- // 免得 `_ghost_` / `_sm_` 把灰底和高度覆盖掉。
841
- ".cpfe .cpfe-selector{height:36px;padding:0 14px;gap:12px;border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px;font-weight:400}",
842
- ".cpfe .cpfe-selector:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}",
845
+ // Menu surfaces and trigger Buttons keep the native atoms' own styles.
843
846
  // ── 告警区 ──────────────────────────────────────────────────────────────
844
847
  // 这一段此前**一条 CSS 都没有**(2026-10-02 逐表核对:样式表里 `cpfe-warn` 零规则),
845
848
  // 于是出现告警时它是一串没有排版的裸 `<p>`:没有行距、图标与文字不对齐、
@@ -1041,9 +1044,10 @@ try {
1041
1044
  : null,
1042
1045
  ),
1043
1046
  react.createElement(
1044
- "button",
1047
+ A.Button,
1045
1048
  {
1046
- type: "button",
1049
+ variant: "ghost",
1050
+ size: "sm",
1047
1051
  className: "cpfe-row-toggle",
1048
1052
  "aria-expanded": open,
1049
1053
  "aria-label": t(open ? "aria.collapse" : "aria.expand"),
@@ -1717,38 +1721,70 @@ try {
1717
1721
  : "cpfe-status cpfe-ok"
1718
1722
  const shown = dirty && status === "" ? t("msg.unsaved") : status
1719
1723
 
1724
+ const lockedModes =
1725
+ payload !== null && Array.isArray(payload.modes) && payload.modes.length > 0
1726
+ ? payload.modes
1727
+ : [{ id: "standard" }, { id: "ptc" }, { id: "minimal" }, { id: "cordis" }, { id: "all" }]
1720
1728
  const createGroup = react.createElement(
1721
1729
  "section",
1722
1730
  { className: "cpfe-create" },
1723
1731
  react.createElement("h2", { className: "cpfe-h" }, t("create.heading")),
1724
1732
  react.createElement(SectionHint, { text: t("posture.hint") }),
1725
- postureChoices.length === 0
1726
- ? null
1727
- : react.createElement(
1728
- "div",
1729
- null,
1730
- react.createElement(
1733
+ react.createElement(
1734
+ "div",
1735
+ { className: "cpfe-branch" },
1736
+ postureChoices.length === 0
1737
+ ? null
1738
+ : react.createElement(
1731
1739
  "div",
1732
- { className: "cpfe-pills", role: "radiogroup", "aria-label": t("posture.hint") },
1733
- postureChoices.map((item) =>
1734
- react.createElement(
1735
- A.Pill,
1736
- {
1737
- key: item.id,
1738
- active: item.id === posture,
1739
- disabled: busy,
1740
- "aria-pressed": item.id === posture,
1741
- onClick: () => setPosture(item.id),
1742
- },
1743
- t("posture." + item.id + ".label", item.id),
1740
+ { className: "cpfe-node" },
1741
+ react.createElement("span", { className: "cpfe-node-label" }, t("tree.posture")),
1742
+ react.createElement(
1743
+ "div",
1744
+ { className: "cpfe-pills", role: "radiogroup", "aria-label": t("tree.posture") },
1745
+ postureChoices.map((item) =>
1746
+ react.createElement(
1747
+ A.Pill,
1748
+ {
1749
+ key: item.id,
1750
+ active: item.id === posture,
1751
+ disabled: busy,
1752
+ "aria-pressed": item.id === posture,
1753
+ onClick: () => setPosture(item.id),
1754
+ },
1755
+ t("posture." + item.id + ".label", item.id),
1756
+ ),
1744
1757
  ),
1745
1758
  ),
1759
+ react.createElement("p", { className: "cpfe-note" }, t("posture." + posture + ".note", "")),
1760
+ ),
1761
+ react.createElement(
1762
+ "div",
1763
+ { className: "cpfe-node" },
1764
+ react.createElement("span", { className: "cpfe-node-label" }, t("tree.base")),
1765
+ react.createElement(
1766
+ "div",
1767
+ { className: "cpfe-pills cpfe-locked", role: "radiogroup", "aria-disabled": "true", "aria-label": t("tree.base") },
1768
+ lockedModes.map((mode) =>
1769
+ react.createElement(
1770
+ A.Pill,
1771
+ {
1772
+ key: mode.id,
1773
+ active: mode.id === "standard",
1774
+ disabled: true,
1775
+ "aria-pressed": mode.id === "standard",
1776
+ title: mode.id === "standard" ? t("tree.baseHold") : t("tree.baseSkip"),
1777
+ onClick: () => {},
1778
+ },
1779
+ t("base." + mode.id + ".label", mode.label),
1780
+ ),
1746
1781
  ),
1747
- react.createElement("p", { className: "cpfe-note" }, t("posture." + posture + ".note", "")),
1748
1782
  ),
1749
- react.createElement(
1750
- "div",
1751
- { className: "cpfe-newrow" },
1783
+ react.createElement("p", { className: "cpfe-note" }, t("tree.baseLocked")),
1784
+ ),
1785
+ react.createElement(
1786
+ "div",
1787
+ { className: "cpfe-newrow" },
1752
1788
  react.createElement(A.Input, {
1753
1789
  className: "cpfe-newinput",
1754
1790
  value: newName,
@@ -1772,6 +1808,7 @@ try {
1772
1808
  },
1773
1809
  busy ? t("btn.creating") : t("btn.create"),
1774
1810
  ),
1811
+ ),
1775
1812
  ),
1776
1813
  )
1777
1814
 
@@ -1804,14 +1841,13 @@ try {
1804
1841
  // ★ portal:不把浮层渲染在我们的容器里。设置面板有裁剪与叠层,非 portal 的
1805
1842
  // 菜单会被裁掉表面,只剩文字"压"在下面的输入框上(实测截图如此)。
1806
1843
  portal: true,
1807
- listClassName: "cpfe-menu",
1808
1844
  align: "start",
1809
1845
  side: "bottom",
1810
1846
  selectedId: selected,
1811
1847
  anchor: react.createElement(
1812
1848
  A.Button,
1813
1849
  {
1814
- variant: "ghost",
1850
+ variant: "toolbar",
1815
1851
  className: "cpfe-selector",
1816
1852
  disabled: busy,
1817
1853
  onClick: () => setAssistantOpen((open) => open !== true),
@@ -1927,7 +1963,6 @@ try {
1927
1963
  ),
1928
1964
  )
1929
1965
  : null,
1930
- createGroup,
1931
1966
  typeof (assistants.find((item) => item.id === selected) || {}).broken === "string"
1932
1967
  ? react.createElement("p", { className: "cpfe-note" }, t("assistant.brokenHint"))
1933
1968
  : null,
@@ -1944,6 +1979,7 @@ try {
1944
1979
  react.createElement(SectionHint, { text: t("name.hint") }),
1945
1980
  react.createElement(A.Input, {
1946
1981
  className: "cpfe-field",
1982
+ disabled: busy,
1947
1983
  value: draft.name,
1948
1984
  placeholder: t("name.placeholder"),
1949
1985
  "aria-label": t("name.heading"),
@@ -1975,7 +2011,7 @@ try {
1975
2011
  ? react.createElement("p", { className: "cpfe-note" }, t("mode.unavailable"))
1976
2012
  : react.createElement(
1977
2013
  "div",
1978
- { className: "cpfe-pills" },
2014
+ { className: "cpfe-pills", "data-live": "base" },
1979
2015
  payload.modes.map((mode) =>
1980
2016
  react.createElement(
1981
2017
  A.Pill,
@@ -2014,8 +2050,9 @@ try {
2014
2050
  : react.createElement(
2015
2051
  "div",
2016
2052
  null,
2017
- react.createElement("input", {
2018
- className: "cpfe-filter",
2053
+ react.createElement(A.Input, {
2054
+ className: "cpfe-field cpfe-filter",
2055
+ disabled: busy,
2019
2056
  value: rowQuery,
2020
2057
  placeholder: t("rows.filterPlaceholder"),
2021
2058
  "aria-label": t("rows.filterPlaceholder"),
@@ -2126,14 +2163,13 @@ try {
2126
2163
  ? react.createElement(A.Menu, {
2127
2164
  open: historyOpen,
2128
2165
  portal: true,
2129
- listClassName: "cpfe-menu",
2130
2166
  align: "start",
2131
2167
  side: "bottom",
2132
2168
  selectedId: draft.historyPick,
2133
2169
  anchor: react.createElement(
2134
2170
  A.Button,
2135
2171
  {
2136
- variant: "ghost",
2172
+ variant: "toolbar",
2137
2173
  className: "cpfe-selector",
2138
2174
  disabled: busy,
2139
2175
  onClick: () => setHistoryOpen((open) => open !== true),
@@ -2251,8 +2287,17 @@ try {
2251
2287
  ),
2252
2288
  ),
2253
2289
  ),
2254
- ...(editorReady ? editorSections.filter((section) => section.key === "prompt") : []),
2255
- react.createElement(
2290
+ editorReady
2291
+ ? react.createElement(
2292
+ "section",
2293
+ null,
2294
+ react.createElement("h2", { className: "cpfe-h" }, t("editor.heading")),
2295
+ react.createElement(SectionHint, { text: t("editor.hint") }),
2296
+ react.createElement(
2297
+ "div",
2298
+ { className: "cpfe-branch" },
2299
+ editorSections.filter((section) => section.key === "prompt"),
2300
+ react.createElement(
2256
2301
  "div",
2257
2302
  { className: "cpfe-bar" },
2258
2303
  react.createElement(
@@ -2300,9 +2345,10 @@ try {
2300
2345
  "section",
2301
2346
  null,
2302
2347
  react.createElement(
2303
- "button",
2348
+ A.Button,
2304
2349
  {
2305
- type: "button",
2350
+ variant: "ghost",
2351
+ disabled: busy,
2306
2352
  "data-cap": "toggle",
2307
2353
  className: "cpfe-disclosure",
2308
2354
  "aria-expanded": capOpen,
@@ -2326,6 +2372,10 @@ try {
2326
2372
  : null,
2327
2373
  )
2328
2374
  : null,
2375
+ ),
2376
+ )
2377
+ : null,
2378
+ createGroup,
2329
2379
  // The confirmation is a portal: rendering it here keeps every piece of this page's
2330
2380
  // state in one component.
2331
2381
  renderable(A.RiskConfirmation) && editorReady
package/defaults.mjs CHANGED
@@ -35,8 +35,12 @@ export function ensureUnsetDefaultPreset(home, presetId) {
35
35
  for (let i = 0; i < lines.length; i += 1) {
36
36
  const line = lines[i]
37
37
  if (line.trim() === '' || /^\s*#/.test(line)) continue
38
- if (/^agent-presets\s*:/.test(line)) {
39
- if (/default\s*:/.test(line)) return { wrote: false, reason: 'inline' }
38
+ const key = /^(?:agent-presets|'agent-presets'|"agent-presets")\s*:\s*(.*)$/.exec(line.replace(/^\uFEFF/, ''))
39
+ if (key !== null) {
40
+ // A flow mapping, alias or scalar cannot receive an indented child.
41
+ // Leave unsupported YAML forms byte-identical instead of breaking boot.
42
+ const value = key[1].trim()
43
+ if (value !== '' && !value.startsWith('#')) return { wrote: false, reason: 'inline' }
40
44
  section = i
41
45
  break
42
46
  }
@@ -50,7 +54,7 @@ export function ensureUnsetDefaultPreset(home, presetId) {
50
54
  const line = lines[i]
51
55
  if (line.trim() === '' || /^\s*#/.test(line)) continue
52
56
  if (/^\S/.test(line)) break
53
- if (/^\s+default\s*:/.test(line)) return { wrote: false, reason: 'present' }
57
+ if (/^\s+(?:default|'default'|"default")\s*:/.test(line)) return { wrote: false, reason: 'present' }
54
58
  }
55
59
  lines.splice(section + 1, 0, ' default: ' + presetId)
56
60
  const next = lines.join('\n')
package/locales.mjs CHANGED
@@ -21,13 +21,20 @@ export const zh = {
21
21
  // ── 助手管理 ──────────────────────────────────────────────────────────────
22
22
  'assistant.heading': '助手',
23
23
  'assistant.hint': '每个助手单独保存系统提示词、基础模式和插件开关。',
24
- 'posture.hint': '只用于新建,不改变当前助手。',
24
+ 'posture.hint': '这一支只在点「新增助手」时写入。',
25
+ 'tree.posture': '起步',
26
+ 'tree.base': '工具底',
27
+ 'tree.baseHold': '新建时固定用官方标准模式。',
28
+ 'tree.baseSkip': '这一支点不了。建好后到「当前助手」里改。',
29
+ 'tree.baseLocked': '只有官方标准模式是亮的。其余四档是暗的,新建时点不了。',
25
30
  'posture.develop.label': '开发',
26
- 'posture.develop.note': '新建后:标准模式,工具按出厂。',
31
+ 'posture.develop.note': '官方标准模式,工具按出厂。',
27
32
  'posture.write.label': '写作',
28
- 'posture.write.note': '新建后:终端关闭,文件读写打开。',
33
+ 'posture.write.note': '官方标准模式。终端关闭,文件读写打开。',
29
34
  'posture.chat.label': '聊天',
30
- 'posture.chat.note': '新建后:文件和终端关闭。',
35
+ 'posture.chat.note': '官方标准模式。文件和终端关闭。',
36
+ 'editor.heading': '当前助手',
37
+ 'editor.hint': '这一支改上面选中的助手。基础模式五档都可以点。',
31
38
  'assistant.empty': '还没有助手,用下面的输入框新建一个。',
32
39
  'assistant.loadingList': '正在读取助手列表…',
33
40
  'assistant.newPlaceholder': '新助手的名字(例如:写作助手)',
@@ -39,7 +46,7 @@ export const zh = {
39
46
  'assistant.brokenHint': '此模式在本机无法启动,不会出现在新会话菜单里。点「按本线修复」只关闭那些行。',
40
47
  'assistant.switchHint': '切换助手保留各自的未保存草稿;列表中的「未保存」标记即为此。',
41
48
  'btn.create': '新增助手',
42
- 'create.heading': '新建',
49
+ 'create.heading': '新建助手',
43
50
  'btn.creating': '创建中…',
44
51
  'btn.duplicate': '复制',
45
52
  'btn.moveUp': '上移',
@@ -159,7 +166,7 @@ export const zh = {
159
166
  'name.descriptionPlaceholder': '描述,显示在模式菜单里,可留空',
160
167
 
161
168
  'mode.heading': '基础模式',
162
- 'mode.hint': '选一套官方预设作为工具底。提示词以本页为准。',
169
+ 'mode.hint': '五档都可以点。改的是当前这位助手。提示词仍以本页为准。',
163
170
 
164
171
  'rows.heading': '插件开关',
165
172
  'rows.filterPlaceholder': '按名称或 id 筛选',
@@ -279,13 +286,20 @@ export const en = {
279
286
  // ── assistant management ──────────────────────────────────────────────────
280
287
  'assistant.heading': 'Assistants',
281
288
  'assistant.hint': 'Each assistant keeps its own system prompt, base mode and plugin switches.',
282
- 'posture.hint': 'Applies only to the assistant you are about to create.',
289
+ 'posture.hint': 'This branch is written only when you click New assistant.',
290
+ 'tree.posture': 'Starter',
291
+ 'tree.base': 'Tool base',
292
+ 'tree.baseHold': 'A new assistant always starts on the official Standard mode.',
293
+ 'tree.baseSkip': 'Cannot be chosen on this branch. Change it later under This assistant.',
294
+ 'tree.baseLocked': 'Only the official Standard mode is lit. The other four are dim and cannot be chosen when creating.',
283
295
  'posture.develop.label': 'Coding',
284
- 'posture.develop.note': 'After create: Standard, tools as shipped.',
296
+ 'posture.develop.note': 'Official Standard mode, tools as shipped.',
285
297
  'posture.write.label': 'Writing',
286
- 'posture.write.note': 'After create: terminal off, file access on.',
298
+ 'posture.write.note': 'Official Standard mode. Terminal off, file access on.',
287
299
  'posture.chat.label': 'Chat',
288
- 'posture.chat.note': 'After create: files and terminal off.',
300
+ 'posture.chat.note': 'Official Standard mode. Files and terminal off.',
301
+ 'editor.heading': 'This assistant',
302
+ 'editor.hint': 'This branch edits the assistant selected above. All five base modes can be chosen.',
289
303
  'assistant.empty': 'No assistants yet — create one with the field below.',
290
304
  'assistant.loadingList': 'Loading assistants…',
291
305
  'assistant.newPlaceholder': 'Name of the new assistant (e.g. Writing assistant)',
@@ -415,7 +429,7 @@ export const en = {
415
429
  'name.descriptionPlaceholder': 'Description, shown in the mode menu. Optional.',
416
430
 
417
431
  'mode.heading': 'Base mode',
418
- 'mode.hint': 'Pick a shipped preset as the tool base. The prompt on this page still applies.',
432
+ 'mode.hint': 'All five can be chosen. This edits the assistant selected above. The prompt on this page still applies.',
419
433
 
420
434
  'rows.heading': 'Plugin switches',
421
435
  'rows.filterPlaceholder': 'Filter by name or id',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-custom-mode",
3
- "version": "2.2.1",
3
+ "version": "2.2.3",
4
4
  "description": "Edit a DeepSeek Harness (dsh) custom mode: system prompt, base mode, and plugin switches.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -74,7 +74,10 @@
74
74
  "union-mount-check": "node tools/union-mount-check.mjs",
75
75
  "session-trace": "node tools/session-trace.mjs",
76
76
  "browser-verify": "node tools/browser-verify.mjs",
77
- "upgrade-preflight": "node tools/upgrade-preflight.mjs"
77
+ "upgrade-preflight": "node tools/upgrade-preflight.mjs",
78
+ "compat-check": "node tools/compat-check.mjs",
79
+ "runtime-check": "node tools/runtime-check.mjs",
80
+ "market-check": "node tools/market-check.mjs"
78
81
  },
79
82
  "peerDependencies": {
80
83
  "@deepseek-ai/dsh": ">=0.1.5-rc.2 <0.2.0-0 || >=0.1.6-alpha.1 <0.2.0-0 || >=0.1.7-alpha.1 <0.2.0-0 || >=0.2.0-0 <0.3.0-0 || >=0.2.1-alpha.1 <0.3.0-0"
@@ -50,6 +50,18 @@ export function absoluteSpecifier(specifier, presetDir) {
50
50
  return pathToFileURL(join(presetDir, specifier)).href
51
51
  }
52
52
 
53
+ /** Preserve config expressions in the host loader's native node representation.
54
+ * The local parser returns __js without executing it. The host recognizes
55
+ * __jsExpr and evaluates it with its own baseUrl/context. Passing __js through
56
+ * as ordinary config broke Creator's customSkillDirs.
57
+ */
58
+ function loaderConfig(value) {
59
+ if (value === null || typeof value !== 'object') return value
60
+ if (typeof value.__js === 'string') return { __jsExpr: value.__js }
61
+ if (Array.isArray(value)) return value.map(loaderConfig)
62
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, loaderConfig(item)]))
63
+ }
64
+
53
65
  /**
54
66
  * Turn parsed composition rows into the row objects `register()` takes.
55
67
  *
@@ -72,7 +84,7 @@ export function toPluginRows(rows, presetDir) {
72
84
  if (row.isolate !== undefined && row.isolate !== null) plugin.isolate = row.isolate
73
85
  plugin.config = toPluginRows(row.config, presetDir)
74
86
  } else if (row.config !== undefined && row.config !== null) {
75
- plugin.config = row.config
87
+ plugin.config = loaderConfig(row.config)
76
88
  }
77
89
  if (row.disabled !== undefined) plugin.disabled = resolveDisabled(row.disabled)
78
90
  out.push(plugin)