dsh-ui-fortifier 0.6.0 → 0.7.0

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
@@ -18,7 +18,7 @@ dsh plugin --profile web add dsh-ui-fortifier
18
18
 
19
19
  ## Features
20
20
 
21
- Adds a "ui-fortifier" tab in Settings to toggle every feature module of this plugin.
21
+ Adds a configuration area to this plugin's own card on the sidebar's Plugins page, toggling every feature module of this plugin.
22
22
 
23
23
  | Feature | Description |
24
24
  | --- | --- |
@@ -26,12 +26,12 @@ Adds a "ui-fortifier" tab in Settings to toggle every feature module of this plu
26
26
  | compact-button | Compact-context button at the right end of the composer tool row's left group; runs `/compact`. |
27
27
  | provider-label | Shows the currently selected provider to the left of the model selector. |
28
28
  | model-picker | Cascading model picker (provider → model) next to the model selector in the input row. |
29
- | open-dsh-folder | Adds an "Open .dsh folder" button next to "Open configuration file" at the top-right of the Settings page. |
29
+ | open-dsh-folder | Adds an "Open dshHome folder" button next to "Open configuration file" at the top-right of the Settings page. |
30
30
  | settings-frame | Makes the Settings panel draggable and resizable. |
31
31
 
32
32
  ## dsh version used for development
33
33
 
34
- - dsh version used for development: `0.1.5-rc.2`. `@deepseek-ai/dsh-*` dependencies use `workspace:^`.
34
+ - dsh version used for development: `0.1.7-rc.2`. `@deepseek-ai/dsh-*` dependencies use `workspace:^`.
35
35
 
36
36
  ## Secondary Development
37
37
 
package/README.zh.md CHANGED
@@ -18,7 +18,7 @@ dsh plugin --profile web add dsh-ui-fortifier
18
18
 
19
19
  ## 功能
20
20
 
21
- 在设置页新增「ui-fortifier」标签页,集中开关本插件各功能模块。
21
+ 在侧边栏「插件」页本插件自己的卡片上提供配置区,集中开关本插件各功能模块。
22
22
 
23
23
  | 功能 | 描述 |
24
24
  | --- | --- |
@@ -26,12 +26,12 @@ dsh plugin --profile web add dsh-ui-fortifier
26
26
  | compact-button | 输入框工具行左组右端提供压缩上下文按钮,等价于提交 `/compact` 指令。 |
27
27
  | provider-label | 在模型选择按钮左侧显示当前选择的提供方。 |
28
28
  | model-picker | 输入行模型选择器旁提供级联模型选择器(提供商 → 模型)。 |
29
- | open-dsh-folder | 在设置页右上角「打开配置文件」旁增加「打开 .dsh 文件夹」。 |
29
+ | open-dsh-folder | 在设置页右上角「打开配置文件」旁增加「打开dshHome文件夹」。 |
30
30
  | settings-frame | 设置面板可拖动位置与拖动大小。 |
31
31
 
32
32
  ## 构建时使用的 dsh 版本
33
33
 
34
- - 构建时使用的 dsh 版本:`0.1.5-rc.2`;`@deepseek-ai/dsh-*` 依赖使用 `workspace:^`。
34
+ - 构建时使用的 dsh 版本:`0.1.7-rc.2`;`@deepseek-ai/dsh-*` 依赖使用 `workspace:^`。
35
35
 
36
36
  ## 二次开发说明
37
37
 
package/lib/client.js CHANGED
@@ -4,12 +4,12 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
- let react_jsx_runtime = require("react/jsx-runtime");
8
- let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
7
  let react = require("react");
8
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
10
  let react_dom = require("react-dom");
11
11
  //#region \0dsh-css:D:\data\code\deepseek-harness\packages\my\dsh-ui-fortifier\src\client\settings\FortifierSettingsPage.module.css.mjs
12
- const css$6 = ".t1_2kq_page{flex-direction:column;gap:8px;max-width:520px;padding:16px;display:flex}.t1_2kq_heading{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;line-height:24px}.t1_2kq_list{flex-direction:column;margin:0;padding:0;list-style:none;display:flex}.t1_2kq_row{justify-content:space-between;align-items:center;gap:12px;padding:10px 12px;display:flex}.t1_2kq_row+.t1_2kq_row{border-top:.5px solid var(--dsw-alias-border-l3)}.t1_2kq_row:hover{background:var(--dsw-alias-interactive-bg-hover)}.t1_2kq_rowLabel{color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px}.t1_2kq_switch{box-sizing:border-box;background:var(--dsw-alias-border-l3);cursor:pointer;border:0;border-radius:10px;flex:none;width:36px;height:20px;padding:2px;position:relative}.t1_2kq_switchOn{background:var(--dsw-alias-brand-primary)}.t1_2kq_switch:disabled{cursor:default;opacity:.5}.t1_2kq_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.t1_2kq_thumb{corner-shape:round;background:var(--dsw-alias-label-primary-foreground);border-radius:50%;width:16px;height:16px;transition:transform .12s;display:block}.t1_2kq_switchOn .t1_2kq_thumb{transform:translate(16px)}";
12
+ const css$6 = ".t1_2kq_page{flex-direction:column;gap:8px;padding:16px;display:flex}.t1_2kq_heading{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;line-height:24px}.t1_2kq_list{flex-direction:column;margin:0;padding:0;list-style:none;display:flex}.t1_2kq_row{justify-content:space-between;align-items:center;gap:12px;padding:10px 12px;display:flex}.t1_2kq_row+.t1_2kq_row{border-top:.5px solid var(--dsw-alias-border-l3)}.t1_2kq_row:hover{background:var(--dsw-alias-interactive-bg-hover)}.t1_2kq_rowLabel{color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px}";
13
13
  const tagId$6 = "dsh-ui-fortifier/FortifierSettingsPage.module.css";
14
14
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$6) + "]") === null) {
15
15
  const tag = document.createElement("style");
@@ -23,23 +23,57 @@ window.__ModuleLoader__.load({
23
23
  "list": "t1_2kq_list",
24
24
  "page": "t1_2kq_page",
25
25
  "row": "t1_2kq_row",
26
- "rowLabel": "t1_2kq_rowLabel",
27
- "switch": "t1_2kq_switch",
28
- "switchOn": "t1_2kq_switchOn",
29
- "thumb": "t1_2kq_thumb"
26
+ "rowLabel": "t1_2kq_rowLabel"
30
27
  };
31
28
  //#endregion
32
29
  //#region src/client/settings/FortifierSettingsPage.tsx
33
- /** 设置页:读取配置项自动生成切换按钮行。 */
30
+ /**
31
+ * 配置区:插件页上本包卡片里的六个功能开关。
32
+ */
33
+ /**
34
+ * 渲染六个功能开关。
35
+ * @param props - 组装后的插槽 props。
36
+ * @returns 插件页本包卡片上的配置区。
37
+ */
34
38
  function FortifierSettingsPage(props) {
35
39
  const t = props.t;
36
40
  const rows = props.useToggles((value) => value);
41
+ const [local, setLocal] = (0, react.useState)(() => /* @__PURE__ */ new Map());
42
+ const [pendingWrites, setPendingWrites] = (0, react.useState)(0);
43
+ const synced = (0, react.useRef)(/* @__PURE__ */ new Map());
44
+ (0, react.useEffect)(() => {
45
+ const host = new Map(rows.map((row) => [row.field, row.value]));
46
+ const unchanged = [...host].every(([field, value]) => synced.current.get(field) === value) && host.size === synced.current.size;
47
+ if (pendingWrites > 0 || unchanged) return;
48
+ synced.current = host;
49
+ setLocal(/* @__PURE__ */ new Map());
50
+ }, [rows, pendingWrites]);
51
+ /** 记一次未落定的写入,落定后减一;被拒或传输失败也照样放行,好让宿主值接管。 */
52
+ const trackWrite = (write) => {
53
+ setPendingWrites((count) => count + 1);
54
+ write.catch(() => false).finally(() => {
55
+ setPendingWrites((count) => Math.max(0, count - 1));
56
+ });
57
+ };
58
+ /**
59
+ * 提交一个开关:先落在本地(控件立即显示新值),再写宿主。
60
+ * @param row - 要切换的开关行。
61
+ */
62
+ const commit = (row) => {
63
+ const next = !(local.get(row.field) ?? row.value);
64
+ const pending = new Map(local);
65
+ pending.set(row.field, next);
66
+ setLocal(pending);
67
+ synced.current = new Map(synced.current).set(row.field, next);
68
+ trackWrite(props.set(row.field, next));
69
+ };
70
+ if (props.view === "summary") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: t("nav") });
37
71
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
38
72
  className: FortifierSettingsPage_module_css_default.page,
39
73
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
40
74
  className: FortifierSettingsPage_module_css_default.heading,
41
75
  children: t("nav")
42
- }), rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("empty") }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
76
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
43
77
  className: FortifierSettingsPage_module_css_default.list,
44
78
  children: rows.map((row) => {
45
79
  const label = t(`config.${row.field}`);
@@ -48,16 +82,12 @@ window.__ModuleLoader__.load({
48
82
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
49
83
  className: FortifierSettingsPage_module_css_default.rowLabel,
50
84
  children: label
51
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
52
- type: "button",
53
- role: "switch",
54
- "aria-checked": row.value,
55
- "aria-label": label,
56
- className: `${FortifierSettingsPage_module_css_default.switch} ${row.value ? FortifierSettingsPage_module_css_default.switchOn : ""}`,
57
- onClick: () => {
58
- props.set(row.field, !row.value);
59
- },
60
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: FortifierSettingsPage_module_css_default.thumb })
85
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
86
+ checked: local.get(row.field) ?? row.value,
87
+ label,
88
+ onChange: () => {
89
+ commit(row);
90
+ }
61
91
  })]
62
92
  }, row.field);
63
93
  })
@@ -69,15 +99,14 @@ window.__ModuleLoader__.load({
69
99
  /** 英文文案。 */
70
100
  const en = {
71
101
  nav: "ui-fortifier",
72
- empty: "No feature modules registered.",
73
102
  "config.provider-label": "Input - Provider",
74
- "config.open-dsh-folder": "Settings top-right - Open .dsh folder",
103
+ "config.open-dsh-folder": "Settings top-right - Open dshHome folder",
75
104
  "config.settings-frame": "Settings panel - Drag and resize",
76
105
  "config.model-picker": "Input - Cascading model picker",
77
106
  "config.session-id-copy": "Session header - Copy session ID",
78
107
  "config.compact-button": "Input left group - Compact context",
79
- openDshFolder: "Open .dsh folder",
80
- "openDshFolder.error": "Could not open the .dsh folder",
108
+ openDshFolder: "Open dshHome folder",
109
+ "openDshFolder.error": "Could not open the dshHome folder",
81
110
  "openDshFolder.pathLabel": "Path: ",
82
111
  "settingsFrame.drag": "Drag the settings panel",
83
112
  "settingsFrame.resize": "Resize the settings panel",
@@ -104,15 +133,14 @@ window.__ModuleLoader__.load({
104
133
  /** 中文文案。 */
105
134
  const zh = {
106
135
  nav: "ui-fortifier",
107
- empty: "没有已注册的功能模块。",
108
136
  "config.provider-label": "输入框 - 提供商",
109
- "config.open-dsh-folder": "设置右上角 - 打开 .dsh 文件夹",
137
+ "config.open-dsh-folder": "设置右上角 - 打开dshHome文件夹",
110
138
  "config.settings-frame": "设置面板 - 拖动与缩放",
111
139
  "config.model-picker": "输入框 - 级联模型选择器",
112
140
  "config.session-id-copy": "会话标题栏 - 复制 Session ID",
113
141
  "config.compact-button": "输入框左组 - 压缩上下文",
114
- openDshFolder: "打开 .dsh 文件夹",
115
- "openDshFolder.error": "无法打开 .dsh 文件夹",
142
+ openDshFolder: "打开dshHome文件夹",
143
+ "openDshFolder.error": "无法打开dshHome文件夹",
116
144
  "openDshFolder.pathLabel": "路径:",
117
145
  "settingsFrame.drag": "拖动设置面板",
118
146
  "settingsFrame.resize": "缩放设置面板",
@@ -138,63 +166,58 @@ window.__ModuleLoader__.load({
138
166
  };
139
167
  //#endregion
140
168
  //#region src/client/settings/index.ts
141
- /** 本插件设置命名空间(与 host 侧一致)。 */
169
+ /** 本插件在 profile 里的入口 id,同时是设置契约的命名空间。 */
142
170
  const UI_FORTIFIER = "ui-fortifier";
143
171
  /**
144
- * 注册设置页并绑定功能开关的 settings 作用域。
145
- * @param ctx - 需已提供 slots/locale/settingsScope 的客户端上下文。
146
- * @param moduleOrder - 功能模块字段的显示顺序(settings 开关 key)。
147
- * @returns 功能开关的 settings 绑定,供调用方控制子模块注册。
172
+ * 本插件在 profile 里的包名,也是插件页派发配置区的键。
173
+ *
174
+ * 该页以 `pkg.name` 派发(`renderSlot('plugins.bundle.config', ..., { entryKey: pkg.name })`),
175
+ * 所以这里的值必须与 package.json 的 `name` 逐字一致;写错不会报错,只会让配置区静默
176
+ * 地不出现。
177
+ */
178
+ const BUNDLE_NAME = "dsh-ui-fortifier";
179
+ /**
180
+ * 注册配置区并把入口配置的表单绑成本页的数据源。
181
+ * @param ctx - 需已提供 slots/locale/configForms 的客户端上下文。
182
+ * @param moduleOrder - 功能开关字段的显示顺序。
183
+ * @returns 本入口的表单,供调用方控制子模块注册。
148
184
  */
149
185
  function installSettings(ctx, moduleOrder) {
150
186
  ctx.effect(() => ctx.locale.register(UI_FORTIFIER, {
151
187
  zh,
152
188
  en
153
189
  }), "dsh-ui-fortifier: copy dictionaries");
154
- const scope = ctx.settingsScope.bind({ namespace: UI_FORTIFIER });
155
- const mirror = ctx.settingsScope.describe();
156
- const view = () => mirror.getSnapshot().view?.namespaces.find((row) => row.ns === UI_FORTIFIER);
157
- const sectionInjected = () => {
190
+ const form = ctx.configForms.get(UI_FORTIFIER);
191
+ const injected = () => {
158
192
  let cached = [];
159
- let cachedSignature = "";
193
+ let signature = "";
160
194
  return {
161
195
  hooks: { toggles: {
162
196
  getSnapshot: () => {
163
- const value = view()?.value ?? {};
197
+ const value = form.getSnapshot().value;
164
198
  const rows = moduleOrder.map((field) => ({
165
199
  field,
166
- value: value[field] ?? true
200
+ value: value?.[field] ?? true
167
201
  }));
168
- const signature = JSON.stringify(rows);
169
- if (signature !== cachedSignature) {
202
+ const current = JSON.stringify(rows);
203
+ if (current !== signature) {
170
204
  cached = rows;
171
- cachedSignature = signature;
205
+ signature = current;
172
206
  }
173
207
  return cached;
174
208
  },
175
- subscribe: (listener) => {
176
- const offScope = scope.subscribe(listener);
177
- const offMirror = mirror.subscribe(listener);
178
- return () => {
179
- offScope();
180
- offMirror();
181
- };
182
- }
209
+ subscribe: (listener) => form.subscribe(listener)
183
210
  } },
184
- set: (field, value) => {
185
- scope.set(field, value);
186
- }
211
+ set: (field, value) => form.set(field, value)
187
212
  };
188
213
  };
189
- ctx.slots.inject("settings.section", () => ctx.slots.register({
190
- name: "settings.section",
191
- id: UI_FORTIFIER,
192
- order: 30,
193
- label: () => ctx.locale.bind(UI_FORTIFIER)("nav"),
214
+ ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
215
+ name: "plugins.bundle.config",
216
+ key: BUNDLE_NAME,
194
217
  locale: UI_FORTIFIER,
195
- inject: sectionInjected
218
+ inject: injected
196
219
  }, FortifierSettingsPage));
197
- return scope;
220
+ return form;
198
221
  }
199
222
  //#endregion
200
223
  //#region \0dsh-css:D:\data\code\deepseek-harness\packages\my\dsh-ui-fortifier\src\client\provider-label\ProviderLabel.module.css.mjs
@@ -252,7 +275,7 @@ window.__ModuleLoader__.load({
252
275
  //#endregion
253
276
  //#region src/client/open-dsh-folder/OpenDshFolderAction.tsx
254
277
  /**
255
- * 渲染「打开 .dsh 文件夹」操作,点击后请求 Host 打开 .dsh 目录。
278
+ * 渲染「打开 dshHome 文件夹」操作,点击后请求 Host 打开 dshHome 目录。
256
279
  * @param props - header owner 参数、本地化文案与注入的打开回调。
257
280
  * @returns 操作按钮及其结果提示。
258
281
  */
@@ -301,7 +324,7 @@ window.__ModuleLoader__.load({
301
324
  /** 本模块文案命名空间(与 host 侧一致)。 */
302
325
  const NS$2 = "ui-fortifier";
303
326
  /**
304
- * 注册「打开 .dsh 文件夹」到设置页右上角操作区,仅在回环连接下注册。
327
+ * 注册「打开 dshHome 文件夹」到设置页右上角操作区,仅在回环连接下注册。
305
328
  * @param ctx - 需已提供 slots/connection 的客户端上下文。
306
329
  * @returns 停止注入并卸载注册的 disposer。
307
330
  */
@@ -640,7 +663,7 @@ window.__ModuleLoader__.load({
640
663
  }
641
664
  //#endregion
642
665
  //#region \0dsh-css:D:\data\code\deepseek-harness\packages\my\dsh-ui-fortifier\src\client\model-picker\ModelPicker.module.css.mjs
643
- const css$2 = ".AGI7WG_root{flex:none;display:inline-flex}.AGI7WG_trigger{box-sizing:border-box;width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:14px;justify-content:center;align-items:center;padding:0;display:inline-flex}.AGI7WG_trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.AGI7WG_trigger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.AGI7WG_triggerIcon{flex:none;width:14px;height:14px}.AGI7WG_menu{z-index:1100;box-sizing:border-box;background:var(--dsw-specific-menu);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);min-width:min(420px,100vw - 32px);max-width:min(520px,100vw - 32px);height:min(420px,100vh - 96px);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border:0;border-radius:16px;flex-direction:column;padding:6px;display:flex;position:fixed;overflow:hidden}.AGI7WG_search{box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);border-radius:10px;flex:none;align-items:center;gap:6px;height:28px;margin:0 2px 6px;padding:0 8px;display:flex}.AGI7WG_search:focus-within{border-color:var(--dsw-alias-brand-primary)}.AGI7WG_searchIcon{color:var(--dsw-alias-label-tertiary);flex:none;display:inline-flex}.AGI7WG_searchIcon svg{width:13px;height:13px}.AGI7WG_searchInput{min-width:0;color:var(--dsw-alias-label-primary);font:inherit;background:0 0;border:none;outline:none;flex:1;padding:0;font-size:13px;line-height:20px}.AGI7WG_searchInput::placeholder{color:var(--dsw-alias-label-tertiary)}.AGI7WG_columns{flex:1;min-height:0;display:flex;overflow:hidden}.AGI7WG_providers{flex:none;width:160px;padding-right:4px;overflow-y:auto}.AGI7WG_models{flex:1;min-width:0;padding-left:4px;overflow-y:auto}.AGI7WG_columnTitle{color:var(--dsw-alias-label-tertiary);padding:5px 8px 3px;font-size:12px;font-weight:500;line-height:18px}.AGI7WG_cell{box-sizing:border-box;width:100%;min-height:34px;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:10px;align-items:center;gap:8px;padding:6px 8px;display:flex}.AGI7WG_cell:hover:not(:disabled),.AGI7WG_cell:focus-visible{background:var(--dsw-alias-interactive-bg-hover)}.AGI7WG_cellActive{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary)}.AGI7WG_cellSelected{color:var(--dsw-alias-label-primary);font-weight:500}.AGI7WG_cell:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.AGI7WG_modelName{overflow-wrap:anywhere;flex:1;min-width:0}.AGI7WG_check{color:var(--dsw-alias-brand-primary);flex:none}.AGI7WG_status{color:var(--dsw-alias-label-tertiary);padding:10px;font-size:13px;line-height:20px}.AGI7WG_error{background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);border-radius:8px;justify-content:space-between;align-items:flex-start;gap:8px;margin-bottom:4px;padding:7px 8px;font-size:12px;line-height:18px;display:flex}.AGI7WG_retry{color:inherit;font:inherit;cursor:pointer;background:0 0;border:none;flex:none;padding:0;font-weight:600}";
666
+ const css$2 = ".AGI7WG_root{flex:none;display:inline-flex}.AGI7WG_trigger{box-sizing:border-box;width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:14px;justify-content:center;align-items:center;padding:0;display:inline-flex}.AGI7WG_trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.AGI7WG_trigger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.AGI7WG_triggerIcon{flex:none;width:14px;height:14px}.AGI7WG_menu{z-index:1100;box-sizing:border-box;--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);min-width:min(420px,100vw - 32px);max-width:min(520px,100vw - 32px);height:min(420px,100vh - 96px);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border:0;flex-direction:column;padding:6px;display:flex;position:fixed;overflow:hidden}.AGI7WG_search{box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);border-radius:10px;flex:none;align-items:center;gap:6px;height:28px;margin:0 2px 6px;padding:0 8px;display:flex}.AGI7WG_search:focus-within{border-color:var(--dsw-alias-brand-primary)}.AGI7WG_searchIcon{color:var(--dsw-alias-label-tertiary);flex:none;display:inline-flex}.AGI7WG_searchIcon svg{width:13px;height:13px}.AGI7WG_searchInput{min-width:0;color:var(--dsw-alias-label-primary);font:inherit;background:0 0;border:none;outline:none;flex:1;padding:0;font-size:13px;line-height:20px}.AGI7WG_searchInput::placeholder{color:var(--dsw-alias-label-tertiary)}.AGI7WG_columns{flex:1;min-height:0;display:flex;overflow:hidden}.AGI7WG_providers{flex:none;width:160px;padding-right:4px;overflow-y:auto}.AGI7WG_models{flex:1;min-width:0;padding-left:4px;overflow-y:auto}.AGI7WG_columnTitle{color:var(--dsw-alias-label-tertiary);padding:5px 8px 3px;font-size:12px;font-weight:500;line-height:18px}.AGI7WG_cell{box-sizing:border-box;width:100%;min-height:34px;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:10px;align-items:center;gap:8px;padding:6px 8px;display:flex}.AGI7WG_cell:hover:not(:disabled),.AGI7WG_cell:focus-visible{background:var(--dsw-alias-interactive-bg-hover)}.AGI7WG_cellActive{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary)}.AGI7WG_cellSelected{color:var(--dsw-alias-label-primary);font-weight:500}.AGI7WG_cell:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.AGI7WG_modelName{overflow-wrap:anywhere;flex:1;min-width:0}.AGI7WG_check{color:var(--dsw-alias-brand-primary);flex:none}.AGI7WG_status{color:var(--dsw-alias-label-tertiary);padding:10px;font-size:13px;line-height:20px}.AGI7WG_error{background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);border-radius:8px;justify-content:space-between;align-items:flex-start;gap:8px;margin-bottom:4px;padding:7px 8px;font-size:12px;line-height:18px;display:flex}.AGI7WG_retry{color:inherit;font:inherit;cursor:pointer;background:0 0;border:none;flex:none;padding:0;font-weight:600}";
644
667
  const tagId$2 = "dsh-ui-fortifier/ModelPicker.module.css";
645
668
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
646
669
  const tag = document.createElement("style");
@@ -973,7 +996,7 @@ window.__ModuleLoader__.load({
973
996
  })
974
997
  ]
975
998
  })
976
- }), picker.open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
999
+ }), picker.open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.MenuSurface, {
977
1000
  ref: menuRef,
978
1001
  className: ModelPicker_module_css_default.menu,
979
1002
  role: "dialog",
@@ -1199,7 +1222,7 @@ window.__ModuleLoader__.load({
1199
1222
  className: SessionIdCopyAction_module_css_default.button,
1200
1223
  "aria-label": t("sessionIdCopy.action"),
1201
1224
  onClick: copy,
1202
- children: copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(HashGlyph, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, {})] })
1225
+ children: copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(HashGlyph, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutlineRegular, {})] })
1203
1226
  })
1204
1227
  });
1205
1228
  }
@@ -1224,7 +1247,7 @@ window.__ModuleLoader__.load({
1224
1247
  }
1225
1248
  //#endregion
1226
1249
  //#region \0dsh-css:D:\data\code\deepseek-harness\packages\my\dsh-ui-fortifier\src\client\compact-button\CompactContextButton.module.css.mjs
1227
- const css = ".zxJg-q_root{flex:none;display:inline-flex}.zxJg-q_button{corner-shape:round;background:var(--dsw-specific-selector);width:28px;height:28px;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:999px;flex:none;place-items:center;display:grid}.zxJg-q_button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.zxJg-q_button:disabled{opacity:.5;cursor:default}.zxJg-q_glyph{fill:currentColor;flex:none;width:16px;height:16px;overflow:hidden}.zxJg-q_popover{z-index:1100;box-sizing:border-box;background:var(--dsw-specific-menu);max-width:min(260px,100vw - 32px);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);border-radius:12px;flex-direction:column;align-items:flex-start;gap:10px;padding:10px 12px;display:flex;position:fixed}.zxJg-q_popoverText{margin:0;font-size:13px;line-height:20px}";
1250
+ const css = ".zxJg-q_root{flex:none;display:inline-flex}.zxJg-q_button{corner-shape:round;background:var(--dsw-specific-selector);width:28px;height:28px;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:999px;flex:none;place-items:center;display:grid}.zxJg-q_button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.zxJg-q_button:disabled{opacity:.5;cursor:default}.zxJg-q_glyph{fill:currentColor;flex:none;width:16px;height:16px;overflow:hidden}.zxJg-q_popover{z-index:1100;box-sizing:border-box;max-width:min(260px,100vw - 32px);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);flex-direction:column;align-items:flex-start;gap:10px;padding:10px 12px;display:flex;position:fixed}.zxJg-q_popoverText{margin:0;font-size:13px;line-height:20px}";
1228
1251
  const tagId = "dsh-ui-fortifier/CompactContextButton.module.css";
1229
1252
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
1230
1253
  const tag = document.createElement("style");
@@ -1341,8 +1364,9 @@ window.__ModuleLoader__.load({
1341
1364
  },
1342
1365
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompressGlyph, {})
1343
1366
  })
1344
- }), open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1367
+ }), open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.MenuSurface, {
1345
1368
  ref: panelRef,
1369
+ compact: true,
1346
1370
  className: CompactContextButton_module_css_default.popover,
1347
1371
  role: "dialog",
1348
1372
  "aria-label": props.t("compactButton.confirmTitle"),
@@ -1380,7 +1404,7 @@ window.__ModuleLoader__.load({
1380
1404
  }
1381
1405
  //#endregion
1382
1406
  //#region src/client/index.ts
1383
- /** 功能模块清单:字段名(settings 开关 key) → 模块安装面。 */
1407
+ /** 功能模块清单:字段名(配置开关 key) → 模块安装面。 */
1384
1408
  const modules = {
1385
1409
  "provider-label": {
1386
1410
  module: installProviderLabel,
@@ -1407,7 +1431,7 @@ window.__ModuleLoader__.load({
1407
1431
  dispose: void 0
1408
1432
  }
1409
1433
  };
1410
- /** 设置页开关行的显示顺序:按各模块在界面上的位置,从会话标题栏到输入行再到设置页。 */
1434
+ /** 配置区开关行的显示顺序:按各模块在界面上的位置,从会话标题栏到输入行再到插件页。 */
1411
1435
  const MODULE_ORDER = [
1412
1436
  "session-id-copy",
1413
1437
  "compact-button",
@@ -1419,7 +1443,7 @@ window.__ModuleLoader__.load({
1419
1443
  const inject = [
1420
1444
  "slots",
1421
1445
  "locale",
1422
- "settingsScope",
1446
+ "configForms",
1423
1447
  "modelDirectories",
1424
1448
  "connection",
1425
1449
  "sessions",
@@ -1428,22 +1452,26 @@ window.__ModuleLoader__.load({
1428
1452
  "remote.commands"
1429
1453
  ];
1430
1454
  /**
1431
- * 客户端插件体:注册设置页并据开关状态挂载各功能模块。
1432
- * @param ctx - 需已提供 slots/locale/settingsScope/modelDirectories/connection/sessions/remote 的客户端上下文。
1455
+ * 客户端插件体:注册插件页上的配置区并据开关状态挂载各功能模块。
1456
+ * @param ctx - 需已提供 slots/locale/configForms/modelDirectories/connection/sessions/remote 的客户端上下文。
1433
1457
  */
1434
1458
  function apply(ctx) {
1435
- const scope = installSettings(ctx, MODULE_ORDER);
1436
- for (const field of Object.keys(modules)) {
1459
+ const form = installSettings(ctx, MODULE_ORDER);
1460
+ const mounted = /* @__PURE__ */ new Map();
1461
+ const sync = (snapshot, field) => {
1462
+ const enabled = snapshot.value?.[field] ?? true;
1463
+ if (mounted.get(field) === enabled) return;
1464
+ mounted.set(field, enabled);
1437
1465
  const item = modules[field];
1438
- const sync = () => {
1439
- const enabled = scope.getSnapshot().value?.[field] ?? true;
1440
- item.dispose?.();
1441
- if (enabled) item.dispose = item.module(ctx);
1442
- else item.dispose = void 0;
1443
- };
1444
- scope.subscribe(sync);
1445
- sync();
1446
- }
1466
+ item.dispose?.();
1467
+ item.dispose = enabled ? item.module(ctx) : void 0;
1468
+ };
1469
+ const syncAll = () => {
1470
+ const snapshot = form.getSnapshot();
1471
+ for (const field of Object.keys(modules)) sync(snapshot, field);
1472
+ };
1473
+ form.subscribe(syncAll);
1474
+ syncAll();
1447
1475
  }
1448
1476
  //#endregion
1449
1477
  exports.MODULE_ORDER = MODULE_ORDER;
package/lib/index.js CHANGED
@@ -93,7 +93,7 @@ async function openWindowsFolderInForeground(path, signal) {
93
93
  windowsOpenScript(path)
94
94
  ], signal);
95
95
  }
96
- /** 用系统默认方式打开 .dsh 文件夹;无原生打开器时返回路径让 UI 展示。 */
96
+ /** 用系统默认方式打开 dshHome 文件夹;无原生打开器时返回路径让 UI 展示。 */
97
97
  let OpenDshFolderService = (() => {
98
98
  let _classSuper = TypertRemoteService;
99
99
  let _instanceExtraInitializers = [];
@@ -125,9 +125,9 @@ let OpenDshFolderService = (() => {
125
125
  __runInitializers(this, _instanceExtraInitializers);
126
126
  }
127
127
  /**
128
- * 打开 .dsh 文件夹。
128
+ * 打开 dshHome 文件夹。
129
129
  * @param signal - 调用方可取消信号。
130
- * @returns opened 为 false 时 path 为 .dsh 文件夹路径。
130
+ * @returns opened 为 false 时 path 为 dshHome 文件夹路径。
131
131
  */
132
132
  async openDshFolder(signal) {
133
133
  const home = resolveDshHome();
@@ -143,7 +143,7 @@ let OpenDshFolderService = (() => {
143
143
  path: home
144
144
  };
145
145
  } catch (error) {
146
- if (signal.aborted) throw new RemoteError("gateway/cancelled", "dsh folder open was aborted", {});
146
+ if (signal.aborted) throw new RemoteError("gateway/cancelled", "dshHome folder open was aborted", {});
147
147
  throw new RemoteError("gateway/internal", `path open failed: ${messageOf(error)}`, {}, { cause: error });
148
148
  }
149
149
  }
@@ -154,30 +154,30 @@ function messageOf(error) {
154
154
  }
155
155
  //#endregion
156
156
  //#region lib/types/index.js
157
- /** 功能开关的 settings 命名空间。 */
158
- const UI_FORTIFIER_SETTINGS_NAMESPACE = "ui-fortifier";
159
- /** 插件配置 schema。 */
157
+ /**
158
+ * 插件配置 schema。
159
+ *
160
+ * 不写成 `z<Config>`:`Config` 的字段是 `Volatile<T>`,而 volatile 字段的 schema 类型
161
+ * 处于 `"volatile-defined"` 模式,两者在双向方差下互不可赋值,加了注解反而编译不过
162
+ * (官方与已迁移的插件同样只写 `z.object`)。
163
+ */
160
164
  const Config = z.object({
161
- "provider-label": z.boolean().default(true),
162
- "open-dsh-folder": z.boolean().default(true),
163
- "settings-frame": z.boolean().default(true),
164
- "model-picker": z.boolean().default(true),
165
- "session-id-copy": z.boolean().default(true),
166
- "compact-button": z.boolean().default(true)
165
+ "provider-label": z.boolean().default(true).volatile(),
166
+ "open-dsh-folder": z.boolean().default(true).volatile(),
167
+ "settings-frame": z.boolean().default(true).volatile(),
168
+ "model-picker": z.boolean().default(true).volatile(),
169
+ "session-id-copy": z.boolean().default(true).volatile(),
170
+ "compact-button": z.boolean().default(true).volatile()
167
171
  });
168
172
  /**
169
- * 注册功能开关的 settings 命名空间并暴露打开 .dsh 文件夹的 Remote 服务。
170
- * @param ctx - 需已挂载 settings 服务的 Cordis 上下文。
171
- * @param config - 插件配置,作为 base 层。
173
+ * 挂载插件:暴露打开 dshHome 文件夹的 Remote 服务,并声明本插件自带配置区。
174
+ * @param ctx - 需已提供 reflect(Typert 网关)的 Cordis 上下文。
172
175
  */
173
- function apply(ctx, config) {
176
+ function apply(ctx) {
174
177
  new OpenDshFolderService(ctx);
175
178
  ctx.inject(["settings"], (settingsCtx) => {
176
- settingsCtx.settings.installSection(ctx, UI_FORTIFIER_SETTINGS_NAMESPACE, Config, config, {
177
- setSource: () => {},
178
- onChange: () => {}
179
- });
179
+ settingsCtx.effect(() => settingsCtx.settings.configure({ auto: false }, ctx.fiber));
180
180
  });
181
181
  }
182
182
  //#endregion
183
- export { Config, UI_FORTIFIER_SETTINGS_NAMESPACE, apply };
183
+ export { Config, apply };
@@ -8,14 +8,14 @@ interface ModuleEntry {
8
8
  /** 当前注册的 disposer;归位后为 undefined。 */
9
9
  dispose: (() => void) | undefined;
10
10
  }
11
- /** 功能模块清单:字段名(settings 开关 key) → 模块安装面。 */
11
+ /** 功能模块清单:字段名(配置开关 key) → 模块安装面。 */
12
12
  export declare const modules: Record<keyof Config, ModuleEntry>;
13
- /** 设置页开关行的显示顺序:按各模块在界面上的位置,从会话标题栏到输入行再到设置页。 */
13
+ /** 配置区开关行的显示顺序:按各模块在界面上的位置,从会话标题栏到输入行再到插件页。 */
14
14
  export declare const MODULE_ORDER: readonly (keyof Config)[];
15
15
  export declare const inject: string[];
16
16
  /**
17
- * 客户端插件体:注册设置页并据开关状态挂载各功能模块。
18
- * @param ctx - 需已提供 slots/locale/settingsScope/modelDirectories/connection/sessions/remote 的客户端上下文。
17
+ * 客户端插件体:注册插件页上的配置区并据开关状态挂载各功能模块。
18
+ * @param ctx - 需已提供 slots/locale/configForms/modelDirectories/connection/sessions/remote 的客户端上下文。
19
19
  */
20
20
  export declare function apply(ctx: Context): void;
21
21
  declare module '@deepseek-ai/dsh-client-ui-slots' {
@@ -1,6 +1,6 @@
1
1
  /** dsh-ui-fortifier 的文案。 */
2
2
  /** locale key。 */
3
- export type UiFortifierLocaleKey = 'nav' | 'empty' | 'config.provider-label' | 'config.open-dsh-folder' | 'config.settings-frame' | 'config.model-picker' | 'config.session-id-copy' | 'config.compact-button' | 'openDshFolder' | 'openDshFolder.error' | 'openDshFolder.pathLabel' | 'settingsFrame.drag' | 'settingsFrame.resize' | 'modelPicker.trigger' | 'modelPicker.searchPlaceholder' | 'modelPicker.providers' | 'modelPicker.models' | 'modelPicker.emptyProviders' | 'modelPicker.emptySearch' | 'modelPicker.emptyModels' | 'modelPicker.loading' | 'modelPicker.loadError' | 'modelPicker.retry' | 'modelPicker.provider' | 'modelPicker.model' | 'sessionIdCopy.action' | 'sessionIdCopy.hint' | 'sessionIdCopy.copied' | 'compactButton.action' | 'compactButton.confirmTitle' | 'compactButton.confirmText' | 'compactButton.confirm';
3
+ export type UiFortifierLocaleKey = 'nav' | 'config.provider-label' | 'config.open-dsh-folder' | 'config.settings-frame' | 'config.model-picker' | 'config.session-id-copy' | 'config.compact-button' | 'openDshFolder' | 'openDshFolder.error' | 'openDshFolder.pathLabel' | 'settingsFrame.drag' | 'settingsFrame.resize' | 'modelPicker.trigger' | 'modelPicker.searchPlaceholder' | 'modelPicker.providers' | 'modelPicker.models' | 'modelPicker.emptyProviders' | 'modelPicker.emptySearch' | 'modelPicker.emptyModels' | 'modelPicker.loading' | 'modelPicker.loadError' | 'modelPicker.retry' | 'modelPicker.provider' | 'modelPicker.model' | 'sessionIdCopy.action' | 'sessionIdCopy.hint' | 'sessionIdCopy.copied' | 'compactButton.action' | 'compactButton.confirmTitle' | 'compactButton.confirmText' | 'compactButton.confirm';
4
4
  /** 英文文案。 */
5
5
  export declare const en: Record<UiFortifierLocaleKey, string>;
6
6
  /** 中文文案。 */
@@ -2,15 +2,15 @@ import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-cli
2
2
  import type { ReactNode } from 'react';
3
3
  import type { ConnectionRpcResult } from '@deepseek-ai/dsh-client-connection/client';
4
4
  import type { DshFolderOpenValue } from '../../types.ts';
5
- /** 打开 .dsh 文件夹的注入面。 */
5
+ /** 打开 dshHome 文件夹的注入面。 */
6
6
  export interface OpenDshFolderInjected {
7
- /** 请求 Host 打开 .dsh 文件夹。 */
7
+ /** 请求 Host 打开 dshHome 文件夹。 */
8
8
  openDshFolder: () => Promise<ConnectionRpcResult<DshFolderOpenValue>>;
9
9
  }
10
10
  /** header 操作区的 owner 参数、本地化文案与注入回调。 */
11
11
  export type OpenDshFolderActionProps = PropsRuntime<'settings.action'> & PropsLocale<'ui-fortifier'> & InjectFace<OpenDshFolderInjected>;
12
12
  /**
13
- * 渲染「打开 .dsh 文件夹」操作,点击后请求 Host 打开 .dsh 目录。
13
+ * 渲染「打开 dshHome 文件夹」操作,点击后请求 Host 打开 dshHome 目录。
14
14
  * @param props - header owner 参数、本地化文案与注入的打开回调。
15
15
  * @returns 操作按钮及其结果提示。
16
16
  */
@@ -1,10 +1,10 @@
1
1
  /**
2
- * open-dsh-folder 功能:在设置页右上角「打开配置文件」旁增加「打开 .dsh 文件夹」。
2
+ * open-dsh-folder 功能:在设置页右上角「打开配置文件」旁增加「打开 dshHome 文件夹」。
3
3
  */
4
4
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
5
5
  export type { OpenDshFolderActionProps, OpenDshFolderInjected } from './OpenDshFolderAction.tsx';
6
6
  /**
7
- * 注册「打开 .dsh 文件夹」到设置页右上角操作区,仅在回环连接下注册。
7
+ * 注册「打开 dshHome 文件夹」到设置页右上角操作区,仅在回环连接下注册。
8
8
  * @param ctx - 需已提供 slots/connection 的客户端上下文。
9
9
  * @returns 停止注入并卸载注册的 disposer。
10
10
  */
@@ -1,22 +1,27 @@
1
1
  import type { HostObservable, InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
+ import type { Config } from '../../types.ts';
2
3
  /** 一个功能开关行的状态。 */
3
4
  export interface FortifierToggleRow {
4
- /** 字段名(即 settings 的 key)。 */
5
- field: string;
5
+ /** 字段名(即配置的 key)。 */
6
+ field: keyof Config;
6
7
  /** 当前值。 */
7
8
  value: boolean;
8
9
  }
9
- /** 设置页渲染所需的面。 */
10
+ /** 配置区渲染所需的面。 */
10
11
  export interface FortifierPageInjected {
11
12
  hooks: {
12
13
  /** 所有功能开关的当前值(响应式)。 */
13
14
  toggles: HostObservable<readonly FortifierToggleRow[]>;
14
15
  };
15
- /** 写入一个开关值(user 层)。 */
16
- set: (field: string, value: boolean) => void;
16
+ /** 写入一个开关值(user 层);落定后 resolve。 */
17
+ set: (field: keyof Config, value: boolean) => Promise<boolean>;
17
18
  }
18
- /** 设置页 props。 */
19
- export type FortifierSettingsPageProps = PropsRuntime<'settings.section'> & PropsLocale<'ui-fortifier'> & InjectFace<FortifierPageInjected>;
20
- /** 设置页:读取配置项自动生成切换按钮行。 */
19
+ /** 配置区 props:挂在插件页本包详情页的 `plugins.bundle.config` 上。 */
20
+ export type FortifierSettingsPageProps = PropsRuntime<'plugins.bundle.config'> & PropsLocale<'ui-fortifier'> & InjectFace<FortifierPageInjected>;
21
+ /**
22
+ * 渲染六个功能开关。
23
+ * @param props - 组装后的插槽 props。
24
+ * @returns 插件页本包卡片上的配置区。
25
+ */
21
26
  export declare function FortifierSettingsPage(props: FortifierSettingsPageProps): import("react").JSX.Element;
22
27
  //# sourceMappingURL=FortifierSettingsPage.d.ts.map
@@ -1,14 +1,17 @@
1
+ /**
2
+ * 注册本插件的配置区(插件页上本包自己的卡片),并把入口配置的表单绑成本页的数据源。
3
+ */
1
4
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
2
- import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
5
+ import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
3
6
  import type { Config } from '../../types.ts';
4
7
  export type { FortifierPageInjected, FortifierSettingsPageProps } from './FortifierSettingsPage.tsx';
5
- /** 本插件设置命名空间(与 host 侧一致)。 */
8
+ /** 本插件在 profile 里的入口 id,同时是设置契约的命名空间。 */
6
9
  export declare const UI_FORTIFIER = "ui-fortifier";
7
10
  /**
8
- * 注册设置页并绑定功能开关的 settings 作用域。
9
- * @param ctx - 需已提供 slots/locale/settingsScope 的客户端上下文。
10
- * @param moduleOrder - 功能模块字段的显示顺序(settings 开关 key)。
11
- * @returns 功能开关的 settings 绑定,供调用方控制子模块注册。
11
+ * 注册配置区并把入口配置的表单绑成本页的数据源。
12
+ * @param ctx - 需已提供 slots/locale/configForms 的客户端上下文。
13
+ * @param moduleOrder - 功能开关字段的显示顺序。
14
+ * @returns 本入口的表单,供调用方控制子模块注册。
12
15
  */
13
- export declare function installSettings(ctx: ClientContext, moduleOrder: readonly string[]): SettingsScope<Config>;
16
+ export declare function installSettings(ctx: ClientContext, moduleOrder: readonly (keyof Config)[]): ConfigForm<Config>;
14
17
  //# sourceMappingURL=index.d.ts.map
@@ -1,17 +1,62 @@
1
- import type { Context } from '@deepseek-ai/cordis';
1
+ /**
2
+ * dsh-ui-fortifier 的宿主半侧。
3
+ *
4
+ * 只做两件事:暴露「打开 dshHome 文件夹」的 Remote 服务,以及声明本插件自带配置区。
5
+ * 六个功能开关的值由浏览器半侧经设置表单读写(见 `src/client/settings/index.ts`),
6
+ * 宿主不读它们,因此 `apply` 不接配置参数。
7
+ *
8
+ * @module dsh-ui-fortifier
9
+ */
10
+ import type { Context, Volatile } from '@deepseek-ai/cordis';
2
11
  import z from '@deepseek-ai/schemastery';
3
- import type { Config as FortifierConfig } from './types.ts';
4
- /** 插件配置类型(定义见 `./types.ts`,与 Client 面共享)。 */
5
- export type Config = FortifierConfig;
6
12
  export type { DshFolderOpenValue } from './types.ts';
7
- /** 功能开关的 settings 命名空间。 */
8
- export declare const UI_FORTIFIER_SETTINGS_NAMESPACE = "ui-fortifier";
9
- /** 插件配置 schema。 */
10
- export declare const Config: z<FortifierConfig>;
11
13
  /**
12
- * 注册功能开关的 settings 命名空间并暴露打开 .dsh 文件夹的 Remote 服务。
13
- * @param ctx - 需已挂载 settings 服务的 Cordis 上下文。
14
- * @param config - 插件配置,作为 base 层。
14
+ * 插件配置。
15
+ *
16
+ * 六个字段都是 **volatile 引用**:设置表单只投影入口 `Config` 里声明为 `.volatile()`
17
+ * 的字段,写入由 Loader 经 `updateVolatile` 落回本入口的 profile patch。volatile 引用
18
+ * 原地更新,所以表单每次 `.get()` 都拿到当前值,不必重挂插件。
19
+ * 解引用之后的普通值见 [src/types.ts](./types.ts) 的 `Config`。
20
+ */
21
+ export interface Config {
22
+ /** 输入框显示当前提供商。 */
23
+ 'provider-label': Volatile<boolean>;
24
+ /** 插件页右上角显示打开 dshHome 文件夹按钮。 */
25
+ 'open-dsh-folder': Volatile<boolean>;
26
+ /** 设置面板可拖动位置与大小。 */
27
+ 'settings-frame': Volatile<boolean>;
28
+ /** 输入框右侧显示级联模型选择器(先选提供商、再选模型)。 */
29
+ 'model-picker': Volatile<boolean>;
30
+ /** 会话标题栏显示复制会话 ID 按钮。 */
31
+ 'session-id-copy': Volatile<boolean>;
32
+ /** 输入框工具行左组右端显示压缩上下文按钮。 */
33
+ 'compact-button': Volatile<boolean>;
34
+ }
35
+ /**
36
+ * 插件配置 schema。
37
+ *
38
+ * 不写成 `z<Config>`:`Config` 的字段是 `Volatile<T>`,而 volatile 字段的 schema 类型
39
+ * 处于 `"volatile-defined"` 模式,两者在双向方差下互不可赋值,加了注解反而编译不过
40
+ * (官方与已迁移的插件同样只写 `z.object`)。
41
+ */
42
+ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
43
+ 'provider-label': z<boolean, boolean, "volatile-defined">;
44
+ 'open-dsh-folder': z<boolean, boolean, "volatile-defined">;
45
+ 'settings-frame': z<boolean, boolean, "volatile-defined">;
46
+ 'model-picker': z<boolean, boolean, "volatile-defined">;
47
+ 'session-id-copy': z<boolean, boolean, "volatile-defined">;
48
+ 'compact-button': z<boolean, boolean, "volatile-defined">;
49
+ }>>, Schemastery.ObjectT<NoInfer<{
50
+ 'provider-label': z<boolean, boolean, "volatile-defined">;
51
+ 'open-dsh-folder': z<boolean, boolean, "volatile-defined">;
52
+ 'settings-frame': z<boolean, boolean, "volatile-defined">;
53
+ 'model-picker': z<boolean, boolean, "volatile-defined">;
54
+ 'session-id-copy': z<boolean, boolean, "volatile-defined">;
55
+ 'compact-button': z<boolean, boolean, "volatile-defined">;
56
+ }>>, "plain">;
57
+ /**
58
+ * 挂载插件:暴露打开 dshHome 文件夹的 Remote 服务,并声明本插件自带配置区。
59
+ * @param ctx - 需已提供 reflect(Typert 网关)的 Cordis 上下文。
15
60
  */
16
- export declare function apply(ctx: Context, config: FortifierConfig): void;
61
+ export declare function apply(ctx: Context): void;
17
62
  //# sourceMappingURL=index.d.ts.map
@@ -1,18 +1,18 @@
1
1
  /**
2
- * 打开本机 .dsh 文件夹的 Host Remote 服务。
2
+ * 打开本机 dshHome 文件夹的 Host Remote 服务。
3
3
  * 端点 `uiFortifier/openDshFolder` 由网关的 SRC 反射发现,无需 typert 生成器产物。
4
4
  */
5
5
  import { Context } from '@deepseek-ai/cordis';
6
6
  import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
7
7
  import type { DshFolderOpenValue } from './types.ts';
8
8
  export type { DshFolderOpenValue };
9
- /** 用系统默认方式打开 .dsh 文件夹;无原生打开器时返回路径让 UI 展示。 */
9
+ /** 用系统默认方式打开 dshHome 文件夹;无原生打开器时返回路径让 UI 展示。 */
10
10
  export declare class OpenDshFolderService extends TypertRemoteService {
11
11
  constructor(ctx: Context);
12
12
  /**
13
- * 打开 .dsh 文件夹。
13
+ * 打开 dshHome 文件夹。
14
14
  * @param signal - 调用方可取消信号。
15
- * @returns opened 为 false 时 path 为 .dsh 文件夹路径。
15
+ * @returns opened 为 false 时 path 为 dshHome 文件夹路径。
16
16
  */
17
17
  openDshFolder(signal: AbortSignal): Promise<DshFolderOpenValue>;
18
18
  }
@@ -1,27 +1,30 @@
1
1
  /**
2
2
  * 本包 Host/Client 两面共享的类型。
3
3
  * 仅类型、无运行时代码:双面工程各自编译本文件,无需跨面引用。
4
+ *
5
+ * 这里的 `Config` 是**解引用之后的普通值**,也就是配置区读写的那一份;Host 面的
6
+ * `Config` 六个字段都包在 volatile 引用里,见 [src/index.ts](./index.ts)。
4
7
  */
5
8
  /** 插件配置:功能开关状态(配置默认值)。 */
6
9
  export interface Config {
7
10
  /** 输入框显示当前提供商。 */
8
- 'provider-label': boolean;
9
- /** 设置页右上角显示打开 .dsh 文件夹按钮。 */
10
- 'open-dsh-folder': boolean;
11
+ readonly 'provider-label': boolean;
12
+ /** 插件页右上角显示打开 dshHome 文件夹按钮。 */
13
+ readonly 'open-dsh-folder': boolean;
11
14
  /** 设置面板可拖动位置与大小。 */
12
- 'settings-frame': boolean;
15
+ readonly 'settings-frame': boolean;
13
16
  /** 输入框右侧显示级联模型选择器(先选提供商、再选模型)。 */
14
- 'model-picker': boolean;
17
+ readonly 'model-picker': boolean;
15
18
  /** 会话标题栏显示复制会话 ID 按钮。 */
16
- 'session-id-copy': boolean;
19
+ readonly 'session-id-copy': boolean;
17
20
  /** 输入框工具行左组右端显示压缩上下文按钮。 */
18
- 'compact-button': boolean;
21
+ readonly 'compact-button': boolean;
19
22
  }
20
- /** 打开 .dsh 文件夹的返回值。 */
23
+ /** 打开 dshHome 文件夹的返回值。 */
21
24
  export interface DshFolderOpenValue {
22
25
  /** 是否已发起系统打开。 */
23
26
  readonly opened: boolean;
24
- /** .dsh 文件夹绝对路径(未打开时供 UI 展示)。 */
27
+ /** dshHome 文件夹绝对路径(未打开时供 UI 展示)。 */
25
28
  readonly path: string;
26
29
  }
27
30
  //# sourceMappingURL=types.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-ui-fortifier",
3
3
  "description": "UI 强化",
4
- "version": "0.6.0",
4
+ "version": "0.7.0",
5
5
  "license": "MIT",
6
6
  "contributors": [
7
7
  "pgnqukezrdxmhjso"
@@ -44,35 +44,36 @@
44
44
  "lib/types/**/*.d.ts"
45
45
  ],
46
46
  "peerDependencies": {
47
- "@deepseek-ai/cordis": "^4.0.2",
48
- "@deepseek-ai/dsh-home-paths": "^0.1.5-rc.2",
49
- "@deepseek-ai/dsh-native-command": "^0.1.5-rc.2",
50
- "@deepseek-ai/dsh-typert-protocol": "^0.1.5-rc.2",
51
- "@deepseek-ai/schemastery": "^3.18.2"
47
+ "@deepseek-ai/cordis": "^4.0.4",
48
+ "@deepseek-ai/dsh-native-command": "^0.1.7-rc.2",
49
+ "@deepseek-ai/dsh-home-paths": "^0.1.7-rc.2",
50
+ "@deepseek-ai/dsh-typert-protocol": "^0.1.7-rc.2",
51
+ "@deepseek-ai/schemastery": "^3.18.4"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/react": "~18.3.1",
55
55
  "@types/react-dom": "~18.3.0",
56
56
  "react": "^18.2.0",
57
57
  "react-dom": "^18.2.0",
58
- "@deepseek-ai/dsh-api-remotes": "^0.1.5-rc.2",
59
- "@deepseek-ai/dsh-api-session-controller": "^0.1.5-rc.2",
60
- "@deepseek-ai/cordis": "^4.0.2",
61
- "@deepseek-ai/dsh-client-connection": "^0.1.5-rc.2",
62
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.2",
63
- "@deepseek-ai/dsh-client-ui-model-selection": "^0.1.5-rc.2",
64
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.2",
65
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.2",
66
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.2",
67
- "@deepseek-ai/dsh-home-paths": "^0.1.5-rc.2",
68
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.2",
69
- "@deepseek-ai/dsh-client-locale": "^0.1.5-rc.2",
70
- "@deepseek-ai/dsh-client-ui-session": "^0.1.5-rc.2",
71
- "@deepseek-ai/dsh-native-command": "^0.1.5-rc.2",
72
- "@deepseek-ai/dsh-typert-protocol": "^0.1.5-rc.2",
73
- "@deepseek-ai/schemastery": "^3.18.2",
74
- "@deepseek-ai/dsh-session": "^0.1.5-rc.2",
75
- "@deepseek-ai/dsh-settings": "^0.1.5-rc.2"
58
+ "@deepseek-ai/cordis": "^4.0.4",
59
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.7-rc.2",
60
+ "@deepseek-ai/dsh-api-remotes": "^0.1.7-rc.2",
61
+ "@deepseek-ai/dsh-client-connection": "^0.1.7-rc.2",
62
+ "@deepseek-ai/dsh-client-ui-model-selection": "^0.1.7-rc.2",
63
+ "@deepseek-ai/dsh-api-session-controller": "^0.1.7-rc.2",
64
+ "@deepseek-ai/dsh-client-ui-plugin-manager": "^0.1.7-rc.2",
65
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.7-rc.2",
66
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.7-rc.2",
67
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.7-rc.2",
68
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.7-rc.2",
69
+ "@deepseek-ai/dsh-client-ui-session": "^0.1.7-rc.2",
70
+ "@deepseek-ai/dsh-client-locale": "^0.1.7-rc.2",
71
+ "@deepseek-ai/dsh-home-paths": "^0.1.7-rc.2",
72
+ "@deepseek-ai/dsh-native-command": "^0.1.7-rc.2",
73
+ "@deepseek-ai/dsh-session": "^0.1.7-rc.2",
74
+ "@deepseek-ai/dsh-settings": "^0.1.7-rc.2",
75
+ "@deepseek-ai/schemastery": "^3.18.4",
76
+ "@deepseek-ai/dsh-typert-protocol": "^0.1.7-rc.2"
76
77
  },
77
78
  "dsh": {
78
79
  "bundle": {
@@ -85,6 +86,7 @@
85
86
  "@deepseek-ai/dsh-client-connection",
86
87
  "@deepseek-ai/dsh-client-ui-conversation",
87
88
  "@deepseek-ai/dsh-client-ui-model-selection",
89
+ "@deepseek-ai/dsh-client-ui-plugin-manager",
88
90
  "@deepseek-ai/dsh-client-ui-renderer",
89
91
  "@deepseek-ai/dsh-client-ui-settings",
90
92
  "@deepseek-ai/dsh-client-locale"
@@ -98,6 +100,7 @@
98
100
  "verify": "node ../dsh-pgnqukezrdxmhjso-base/verify.mjs",
99
101
  "pub": "pnpm run build && pnpm login --registry=https://registry.npmjs.org && pnpm publish --registry=https://registry.npmjs.org --no-git-checks",
100
102
  "pack": "pnpm run build && pnpm pack --pack-destination lib",
103
+ "link-test-i": "pnpm run build && (pnpm dsh plugin --profile pkgtest remove %npm_package_name% & pnpm dsh plugin --profile pkgtest add link:./)",
101
104
  "pack-test-i": "pnpm run pack && (pnpm dsh plugin --profile pkgtest remove %npm_package_name% & pnpm dsh plugin --profile pkgtest add ./lib/%npm_package_name%-%npm_package_version%.tgz)",
102
105
  "npm-test-i": "pnpm dsh plugin --profile pkgtest remove %npm_package_name% & pnpm dsh plugin --profile pkgtest add --registry=https://registry.npmjs.org %npm_package_name%@%npm_package_version%",
103
106
  "test-create": "pnpm dsh --profile pkgtest --from-default-profile web",