@dsh-plus/subagent-model 0.1.7 → 0.1.9

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/lib/client.js CHANGED
@@ -4,34 +4,81 @@ 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 = require("react");
8
7
  let react_jsx_runtime = require("react/jsx-runtime");
9
- //#region src/ns.ts
10
- /**
11
- * settings 命名空间字面量(纯常量,零依赖,浏览器半可安全引入)。
12
- * 服务端经 settingsNamespace() 品牌化后使用(config.ts),浏览器半将其作为
13
- * settings.plugin.item keyed 槽位的 key —— 同一字面量两处共用,防止漂移。
14
- * 契约:dsh rc7 起该槽位按「卡片编辑的 settings 命名空间」作 key 分发,
15
- * 官方配置页只渲染 key 命中 Host 已注册命名空间的卡片。
16
- * @module subagent-model/ns
17
- */
18
- const SETTINGS_NS = "dsh-plus-subagent-model";
19
- //#endregion
20
- //#region src/client/api.ts
21
- const ROUTE_CATALOG = "/dsh-plus/subagent-model/catalog";
22
- async function parse(res) {
23
- const body = await res.json();
24
- if (!res.ok) throw new Error(body.error ?? `HTTP ${res.status}`);
25
- return body;
26
- }
27
- async function fetchCatalog() {
28
- return parse(await fetch(ROUTE_CATALOG, { credentials: "same-origin" }));
8
+ let react = require("react");
9
+ //#region ../shared/src/client/card.tsx
10
+ const IDLE_STATUS = {
11
+ kind: "idle",
12
+ text: ""
13
+ };
14
+ function CardChrome(props) {
15
+ const p = props.prefix;
16
+ const badge = props.statusBadge;
17
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
18
+ className: `${p}-card${props.open ? ` ${p}-cardOpen` : ""}`,
19
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
20
+ type: "button",
21
+ className: `${p}-header`,
22
+ "aria-expanded": props.open,
23
+ "aria-label": props.title,
24
+ onClick: () => props.onToggle(!props.open),
25
+ children: [
26
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
27
+ className: `${p}-headText`,
28
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
29
+ className: `${p}-name`,
30
+ children: props.title
31
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
32
+ className: `${p}-description`,
33
+ children: props.description
34
+ })]
35
+ }),
36
+ badge !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
37
+ className: `${p}-statusBadge ${badge.on ? `${p}-statusOn` : `${p}-statusOff`}`,
38
+ children: badge.text
39
+ }) : null,
40
+ props.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
41
+ className: `${p}-pending`,
42
+ children: props.dirtyLabel
43
+ }) : null,
44
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
45
+ className: `${p}-chevron${props.open ? ` ${p}-chevronOpen` : ""}`,
46
+ children: "▾"
47
+ })
48
+ ]
49
+ }), props.open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
50
+ className: `${p}-body`,
51
+ children: [
52
+ props.readOnlyNotice !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
53
+ className: `${p}-readOnly`,
54
+ role: "status",
55
+ children: props.readOnlyNotice
56
+ }) : null,
57
+ props.children,
58
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
59
+ className: `${p}-footer`,
60
+ children: [props.status !== void 0 && props.status.kind !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
61
+ className: `${p}-status${props.status.kind === "error" ? ` ${p}-statusError` : ""}`,
62
+ role: "status",
63
+ children: props.status.text
64
+ }) : null, props.actions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
65
+ type: "button",
66
+ className: `${p}-btn ${action.variant === "primary" ? `${p}-btnPrimary` : `${p}-btnGhost`}`,
67
+ disabled: action.disabled === true,
68
+ onClick: action.onClick,
69
+ children: action.label
70
+ }, action.key))]
71
+ })
72
+ ]
73
+ }) : null]
74
+ });
29
75
  }
30
76
  //#endregion
31
- //#region src/client/fields.tsx
77
+ //#region ../shared/src/client/fields.tsx
32
78
  function CheckRow(props) {
79
+ const p = props.prefix ?? "dshp";
33
80
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
34
- className: "dsm-checkRow",
81
+ className: `${p}-checkRow`,
35
82
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
36
83
  id: props.id,
37
84
  type: "checkbox",
@@ -45,21 +92,22 @@ window.__ModuleLoader__.load({
45
92
  });
46
93
  }
47
94
  function SelectField(props) {
95
+ const p = props.prefix ?? "dshp";
48
96
  const invalid = props.invalid === true;
49
97
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
50
- className: "dsm-field",
98
+ className: `${p}-field`,
51
99
  children: [
52
100
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
53
- className: "dsm-head",
101
+ className: `${p}-head`,
54
102
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
55
- className: "dsm-label",
103
+ className: `${p}-label`,
56
104
  htmlFor: props.id,
57
105
  children: props.label
58
106
  })
59
107
  }),
60
108
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
61
109
  id: props.id,
62
- className: "dsm-select",
110
+ className: `${p}-select${invalid ? ` ${p}-inputInvalid` : ""}`,
63
111
  "aria-invalid": invalid || void 0,
64
112
  value: props.value,
65
113
  disabled: props.disabled === true,
@@ -70,35 +118,252 @@ window.__ModuleLoader__.load({
70
118
  }, option.value))
71
119
  }),
72
120
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
73
- className: invalid ? "dsm-invalid" : "dsm-hint",
121
+ className: invalid ? `${p}-invalid` : `${p}-hint`,
74
122
  children: invalid ? props.invalidLabel ?? "" : props.hint ?? ""
75
123
  })
76
124
  ]
77
125
  });
78
126
  }
79
127
  //#endregion
80
- //#region src/client/card.tsx
128
+ //#region ../shared/src/client/i18n.ts
129
+ /** 卡片通用文案(save/discard/unsaved/loading 等四插件共有键)。 */
130
+ const commonZh = {
131
+ save: "保存",
132
+ saving: "保存中…",
133
+ discard: "放弃",
134
+ unsaved: "未保存",
135
+ loading: "加载中…",
136
+ unavailable: "配置服务不可用。",
137
+ readOnly: "当前部署无 settings provider,配置为只读;请编辑 settings.yaml。",
138
+ invalidNumber: "请输入正整数。",
139
+ collapse: "收起",
140
+ expand: "展开",
141
+ enabledOn: "已启用",
142
+ enabledOff: "未启用",
143
+ saveFailed: "保存失败,请检查填写内容。",
144
+ confirm: "确认",
145
+ cancel: "取消"
146
+ };
147
+ const commonEn = {
148
+ save: "Save",
149
+ saving: "Saving…",
150
+ discard: "Discard",
151
+ unsaved: "Unsaved",
152
+ loading: "Loading…",
153
+ unavailable: "Settings service unavailable.",
154
+ readOnly: "No settings provider in this deployment; config is read-only. Edit settings.yaml.",
155
+ invalidNumber: "Enter a positive integer.",
156
+ collapse: "Collapse",
157
+ expand: "Expand",
158
+ enabledOn: "On",
159
+ enabledOff: "Off",
160
+ saveFailed: "Save failed; check the fields.",
161
+ confirm: "Confirm",
162
+ cancel: "Cancel"
163
+ };
164
+ /** own 覆盖同键合并(返回新对象,入参不可变)。 */
165
+ function mergeDict(base, own) {
166
+ return {
167
+ ...base,
168
+ ...own
169
+ };
170
+ }
171
+ //#endregion
172
+ //#region ../shared/src/client/scope.ts
81
173
  /**
82
- * 「子代理模型配置」配置卡片:注册进 settings.plugin.item 插槽(官方插件配置页)。
83
- * 交互对齐官方卡片:折叠/展开、staged draft、未保存标记、保存/放弃。
84
- * 配置读写走官方 settingsScope 传输(scope.ts):value 为 schema 解析后的
85
- * 命名空间值(enabled + entries),行集合 = 目录返回的已注册子代理 provider
86
- * ∪ 已配置条目,保存时全量写回 entries(未配置的新 provider 行以默认空值
87
- * 落盘,自文档化);「模型目录」为唯一保留的自定义端点。
88
- * @module subagent-model/client/card
174
+ * api 直连实现的命名空间 scope(官方配置页 tab 同款模式)。
175
+ * 读:describe 取命名空间视图(脱敏 value/revision + 顶层 writable);
176
+ * 刷新:remote `settings/document-updated`(按命名空间过滤)与
177
+ * `connection/reset`;generation 防旧读覆盖新发布。任何页面 origin
178
+ * (loopback 或 tailnet 信任域名)下行为一致。
179
+ * effect 归属调用方 fiber(label 需含插件名以便诊断)。
89
180
  */
90
- const IDLE_STATUS = {
91
- kind: "idle",
92
- text: ""
93
- };
181
+ function createApiScope(api, ns, c, label) {
182
+ let state = {
183
+ status: "loading",
184
+ value: void 0,
185
+ revision: void 0,
186
+ writable: false
187
+ };
188
+ const listeners = /* @__PURE__ */ new Set();
189
+ let generation = 0;
190
+ const publish = (next) => {
191
+ state = next;
192
+ for (const listener of [...listeners]) listener();
193
+ };
194
+ const load = async () => {
195
+ const gen = ++generation;
196
+ let response;
197
+ try {
198
+ response = await api.describe({});
199
+ } catch {
200
+ return;
201
+ }
202
+ if (gen !== generation || !response.result.ok) return;
203
+ const writable = response.result.value?.writable ?? false;
204
+ const view = response.result.value?.namespaces.find((candidate) => candidate.ns === ns);
205
+ if (view === void 0) {
206
+ publish({
207
+ status: "unavailable",
208
+ value: void 0,
209
+ revision: void 0,
210
+ writable
211
+ });
212
+ return;
213
+ }
214
+ publish({
215
+ status: "ready",
216
+ value: view.value,
217
+ revision: view.revision,
218
+ writable
219
+ });
220
+ };
221
+ const refresh = (namespace) => {
222
+ if (namespace !== void 0 && namespace !== ns) return;
223
+ load();
224
+ };
225
+ const disposers = [c.get("remote").$on("settings/document-updated", refresh), c.on("connection/reset", () => {
226
+ load();
227
+ })];
228
+ c.effect(() => () => {
229
+ for (const dispose of disposers) dispose();
230
+ }, label);
231
+ load();
232
+ return {
233
+ getSnapshot: () => state,
234
+ subscribe: (listener) => {
235
+ listeners.add(listener);
236
+ return () => {
237
+ listeners.delete(listener);
238
+ };
239
+ },
240
+ load
241
+ };
242
+ }
243
+ //#endregion
244
+ //#region ../shared/src/client/styles.ts
245
+ /**
246
+ * 配置卡片样式工厂(各插件 client 的公共收编版)。
247
+ * 视觉规则与迁移前逐字一致(类名前缀换为各插件自身,DOM 类名不变),
248
+ * 另补移动端与可达性增强(767px 断点 + pointer:coarse,对齐 ui-mobile-fit 约定):
249
+ * - 窄屏 input/textarea 16px(防 iOS 聚焦缩放)、可点目标 ≥44px;
250
+ * - footer 按钮等宽换行、状态文字独占一行;
251
+ * - 展开态 body 底部 sticky 保存栏(长表单滚动中始终可达)。
252
+ * 沿用官方 data-plugin / data-plugin-css 约定(HMR 据此卸载),不覆盖上游选择器。
253
+ * @module @dsh-plus/shared/client/styles
254
+ */
255
+ /** 生成全套卡片 CSS;prefix 为插件类名前缀(如 'dne'),extra 为插件特有规则。 */
256
+ function cardCss(prefix, extra = "") {
257
+ return `
258
+ .${prefix}-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}
259
+ .${prefix}-card:hover{border-color:var(--dsw-alias-label-dimmed)}
260
+ .${prefix}-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}
261
+ .${prefix}-header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}
262
+ .${prefix}-header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}
263
+ .${prefix}-headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}
264
+ .${prefix}-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}
265
+ .${prefix}-description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}
266
+ .${prefix}-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s;font-size:12px}
267
+ .${prefix}-chevronOpen{transform:rotate(180deg)}
268
+ .${prefix}-pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
269
+ .${prefix}-statusBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
270
+ .${prefix}-statusOn{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
271
+ .${prefix}-statusOff{color:var(--dsw-alias-label-tertiary)}
272
+ .${prefix}-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}
273
+ .${prefix}-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
274
+ .${prefix}-field+.${prefix}-field{border-top:1px solid var(--dsw-alias-border-l2)}
275
+ .${prefix}-head{align-items:center;gap:8px;display:flex}
276
+ .${prefix}-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}
277
+ .${prefix}-badge{white-space:nowrap;border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
278
+ .${prefix}-badgeSet{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
279
+ .${prefix}-badgeUnset{color:var(--dsw-alias-label-tertiary)}
280
+ .${prefix}-input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px}
281
+ .${prefix}-input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}
282
+ .${prefix}-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
283
+ .${prefix}-inputInvalid{border-color:var(--dsw-alias-label-error)}
284
+ .${prefix}-select{appearance:none;background-image:linear-gradient(45deg,transparent 50%,var(--dsw-alias-label-tertiary) 50%),linear-gradient(135deg,var(--dsw-alias-label-tertiary) 50%,transparent 50%);background-position:calc(100% - 16px) 50%,calc(100% - 11px) 50%;background-size:5px 5px;background-repeat:no-repeat;padding-right:28px}
285
+ .${prefix}-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
286
+ .${prefix}-invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}
287
+ .${prefix}-checkRow{align-items:center;gap:8px;display:flex;padding:6px 0}
288
+ .${prefix}-checkRow input{accent-color:var(--dsw-alias-brand-primary)}
289
+ .${prefix}-checkRow label{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5;cursor:pointer}
290
+ .${prefix}-groupLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;padding:12px 0 2px;margin:0}
291
+ .${prefix}-readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}
292
+ .${prefix}-warn{color:var(--dsw-alias-label-error);margin:12px 0 0;font-size:12px;line-height:1.5}
293
+ .${prefix}-footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex;flex-wrap:wrap}
294
+ .${prefix}-status{min-width:0;color:var(--dsw-alias-label-secondary);flex:1;margin:0;font-size:12px;line-height:1.5}
295
+ .${prefix}-statusError{color:var(--dsw-alias-label-error)}
296
+ .${prefix}-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}
297
+ .${prefix}-btnGhost{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}
298
+ .${prefix}-btnGhost:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}
299
+ .${prefix}-btnPrimary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}
300
+ .${prefix}-btn:disabled{opacity:.4;cursor:default}
301
+ .${prefix}-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
302
+ @media (max-width:767px){
303
+ .${prefix}-body{margin:0 10px}
304
+ .${prefix}-header{padding:12px 12px}
305
+ .${prefix}-input,.${prefix}-select{font-size:16px;height:40px}
306
+ .${prefix}-checkRow{min-height:44px;padding:8px 0}
307
+ .${prefix}-checkRow input{width:18px;height:18px;flex:none}
308
+ .${prefix}-footer{position:sticky;bottom:0;background:var(--dsw-alias-bg-layer-2);margin:0 -10px;padding:10px 10px 6px;border-top:1px solid var(--dsw-alias-border-l2);border-bottom-left-radius:12px;border-bottom-right-radius:12px}
309
+ .${prefix}-status{flex-basis:100%;flex:none}
310
+ .${prefix}-btn{min-height:44px;flex:1;min-width:96px}
311
+ .${prefix}-statusBadge{display:none}
312
+ }
313
+ @media (pointer:coarse){
314
+ .${prefix}-btn{min-height:44px}
315
+ .${prefix}-checkRow{min-height:44px}
316
+ .${prefix}-input,.${prefix}-select{height:40px}
317
+ }
318
+ ${extra}
319
+ `;
320
+ }
321
+ /**
322
+ * 幂等注入样式标签(官方 data-plugin-css 约定);返回新标签,
323
+ * 已存在或环境无 document 时返回 null(调用方 effect 卸载用)。
324
+ */
325
+ function injectCardStyle(pluginId, css) {
326
+ if (typeof document === "undefined") return null;
327
+ const tagId = `${pluginId}/card.css`;
328
+ if (document.querySelector(`style[data-plugin-css=${JSON.stringify(tagId)}]`) !== null) return null;
329
+ const tag = document.createElement("style");
330
+ tag.dataset.plugin = pluginId;
331
+ tag.dataset.pluginCss = tagId;
332
+ tag.textContent = css;
333
+ document.head.appendChild(tag);
334
+ return tag;
335
+ }
336
+ //#endregion
337
+ //#region src/ns.ts
338
+ /**
339
+ * settings 命名空间字面量(纯常量,零依赖,浏览器半可安全引入)。
340
+ * 服务端经 settingsNamespace() 品牌化后使用(config.ts),浏览器半将其作为
341
+ * settings.plugin.item keyed 槽位的 key —— 同一字面量两处共用,防止漂移。
342
+ * 契约:dsh rc7 起该槽位按「卡片编辑的 settings 命名空间」作 key 分发,
343
+ * 官方配置页只渲染 key 命中 Host 已注册命名空间的卡片。
344
+ * @module subagent-model/ns
345
+ */
346
+ const SETTINGS_NS = "dsh-plus-subagent-model";
347
+ //#endregion
348
+ //#region src/client/api.ts
349
+ const ROUTE_CATALOG = "/dsh-plus/subagent-model/catalog";
350
+ async function parse(res) {
351
+ const body = await res.json();
352
+ if (!res.ok) throw new Error(body.error ?? `HTTP ${res.status}`);
353
+ return body;
354
+ }
355
+ async function fetchCatalog() {
356
+ return parse(await fetch(ROUTE_CATALOG, { credentials: "same-origin" }));
357
+ }
358
+ //#endregion
359
+ //#region src/client/draft.ts
94
360
  const EMPTY_ROW = {
95
361
  enabled: false,
96
362
  provider: "",
97
363
  model: "",
98
364
  reasoningEffort: "inherit"
99
365
  };
100
- const EFFORT_INHERIT = "inherit";
101
- const EFFORT_DEFAULT = "default";
366
+ /** 行集 = 目录 subagentProviders ∪ 已配置条目;目录行缺省为空行。 */
102
367
  function draftFrom(value, catalog) {
103
368
  const rows = {};
104
369
  const names = /* @__PURE__ */ new Set();
@@ -119,11 +384,26 @@ window.__ModuleLoader__.load({
119
384
  };
120
385
  }
121
386
  function toPatch(draft) {
387
+ const entries = {};
388
+ for (const name of Object.keys(draft.rows).sort()) entries[name] = draft.rows[name];
122
389
  return {
123
390
  enabled: draft.enabled,
124
- entries: draft.rows
391
+ entries
125
392
  };
126
393
  }
394
+ //#endregion
395
+ //#region src/client/card.tsx
396
+ /**
397
+ * 「子代理模型配置」配置卡片:注册进 settings.plugin.item 插槽(官方插件配置页)。
398
+ * 外壳与基础控件走 @dsh-plus/shared/client 套件(CardChrome/CheckRow/SelectField)。
399
+ * 配置读写走官方 settingsScope 传输:value 为 schema 解析后的命名空间值
400
+ * (enabled + entries),行集合 = 目录返回的已注册子代理 provider
401
+ * ∪ 已配置条目,保存时全量写回 entries(未配置的新 provider 行以默认空值
402
+ * 落盘,自文档化);「模型目录」为唯一保留的自定义端点。
403
+ * @module subagent-model/client/card
404
+ */
405
+ const EFFORT_INHERIT = "inherit";
406
+ const EFFORT_DEFAULT = "default";
127
407
  function unknownOption(value, label) {
128
408
  return {
129
409
  value,
@@ -183,6 +463,7 @@ window.__ModuleLoader__.load({
183
463
  className: "dsm-rowName",
184
464
  children: name
185
465
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
466
+ prefix: "dsm",
186
467
  id: `dsm-row-${name}`,
187
468
  label: t("rowEnabled"),
188
469
  checked: row.enabled,
@@ -198,6 +479,7 @@ window.__ModuleLoader__.load({
198
479
  children: t("rowHint")
199
480
  }),
200
481
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectField, {
482
+ prefix: "dsm",
201
483
  id: `dsm-provider-${name}`,
202
484
  label: t("provider"),
203
485
  hint: t("providerHint"),
@@ -210,6 +492,7 @@ window.__ModuleLoader__.load({
210
492
  })
211
493
  }),
212
494
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectField, {
495
+ prefix: "dsm",
213
496
  id: `dsm-model-${name}`,
214
497
  label: t("model"),
215
498
  hint: t("modelHint"),
@@ -224,6 +507,7 @@ window.__ModuleLoader__.load({
224
507
  })
225
508
  }),
226
509
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectField, {
510
+ prefix: "dsm",
227
511
  id: `dsm-effort-${name}`,
228
512
  label: t("effort"),
229
513
  hint: t("effortHint"),
@@ -337,120 +621,88 @@ window.__ModuleLoader__.load({
337
621
  const disabled = !snapshot.writable;
338
622
  const rowNames = Object.keys(draft.rows).sort();
339
623
  const providerCount = (catalog?.providers ?? []).length;
340
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
341
- className: `dsm-card${open ? " dsm-cardOpen" : ""}`,
342
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
343
- type: "button",
344
- className: "dsm-header",
345
- "aria-expanded": open,
346
- "aria-label": `${t(open ? "collapse" : "expand")}: ${t("title")}`,
347
- onClick: () => setOpen(!open),
348
- children: [
349
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
350
- className: "dsm-headText",
351
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
352
- className: "dsm-name",
353
- children: t("title")
354
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
355
- className: "dsm-description",
356
- children: t("description")
357
- })]
358
- }),
359
- dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
360
- className: "dsm-pending",
361
- children: t("unsaved")
362
- }) : null,
363
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
364
- className: `dsm-chevron${open ? " dsm-chevronOpen" : ""}`,
365
- children: "▾"
366
- })
367
- ]
368
- }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
369
- className: "dsm-body",
370
- children: [
371
- disabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
372
- className: "dsm-empty",
373
- role: "status",
374
- children: t("readOnly")
375
- }) : null,
376
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
377
- id: "dsm-enabled",
378
- label: t("enabled"),
379
- checked: draft.enabled,
380
- disabled,
381
- onEdit: (v) => edit("enabled", v)
382
- }),
383
- catalogFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
384
- className: "dsm-banner",
385
- role: "status",
386
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("catalogError") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
387
- type: "button",
388
- className: "dsm-bannerRetry",
389
- onClick: onRetryCatalog,
390
- children: t("catalogRetry")
391
- })]
392
- }) : null,
393
- catalog === null && !catalogFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
394
- className: "dsm-empty",
395
- role: "status",
396
- children: t("catalogLoading")
397
- }) : null,
398
- rowNames.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
399
- className: "dsm-empty",
400
- role: "status",
401
- children: t("noRows")
402
- }) : null,
403
- rowNames.map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowBlock, {
404
- name,
405
- row: draft.rows[name] ?? { ...EMPTY_ROW },
406
- catalog,
407
- disabled,
408
- t,
409
- onEdit: (row) => editRow(name, row)
410
- }, name)),
411
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
412
- className: "dsm-hint dsm-rowHint",
413
- children: providerCount > 0 ? t("rowDesc") : ""
414
- }),
415
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
416
- className: "dsm-footer",
417
- children: [
418
- status.kind !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
419
- className: `dsm-status${status.kind === "error" ? " dsm-statusError" : ""}`,
420
- role: "status",
421
- children: status.text
422
- }) : null,
423
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
424
- type: "button",
425
- className: "dsm-btn dsm-btnGhost",
426
- disabled: !dirty || saving,
427
- onClick: () => {
428
- setDraft(draftFrom(value, catalog));
429
- setStatus(IDLE_STATUS);
430
- },
431
- children: t("discard")
432
- }),
433
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
434
- type: "button",
435
- className: "dsm-btn dsm-btnPrimary",
436
- disabled: !dirty || invalid || saving || disabled,
437
- onClick: onSave,
438
- children: t(saving ? "saving" : "save")
439
- })
440
- ]
441
- })
442
- ]
443
- }) : null]
624
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CardChrome, {
625
+ prefix: "dsm",
626
+ title: t("title"),
627
+ description: t("description"),
628
+ open,
629
+ onToggle: setOpen,
630
+ statusBadge: {
631
+ text: t(draft.enabled ? "enabledOn" : "enabledOff"),
632
+ on: draft.enabled
633
+ },
634
+ dirty,
635
+ dirtyLabel: t("unsaved"),
636
+ readOnlyNotice: disabled ? t("readOnly") : void 0,
637
+ status,
638
+ actions: [{
639
+ key: "discard",
640
+ label: t("discard"),
641
+ disabled: !dirty || saving,
642
+ onClick: () => {
643
+ setDraft(draftFrom(value, catalog));
644
+ setStatus(IDLE_STATUS);
645
+ }
646
+ }, {
647
+ key: "save",
648
+ label: t(saving ? "saving" : "save"),
649
+ variant: "primary",
650
+ disabled: !dirty || invalid || saving || disabled,
651
+ onClick: onSave
652
+ }],
653
+ children: [
654
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
655
+ prefix: "dsm",
656
+ id: "dsm-enabled",
657
+ label: t("enabled"),
658
+ checked: draft.enabled,
659
+ disabled,
660
+ onEdit: (v) => edit("enabled", v)
661
+ }),
662
+ catalogFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
663
+ className: "dsm-banner",
664
+ role: "status",
665
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("catalogError") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
666
+ type: "button",
667
+ className: "dsm-bannerRetry",
668
+ onClick: onRetryCatalog,
669
+ children: t("catalogRetry")
670
+ })]
671
+ }) : null,
672
+ catalog === null && !catalogFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
673
+ className: "dsm-empty",
674
+ role: "status",
675
+ children: t("catalogLoading")
676
+ }) : null,
677
+ rowNames.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
678
+ className: "dsm-empty",
679
+ role: "status",
680
+ children: t("noRows")
681
+ }) : null,
682
+ rowNames.map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowBlock, {
683
+ name,
684
+ row: draft.rows[name] ?? { ...EMPTY_ROW },
685
+ catalog,
686
+ disabled,
687
+ t,
688
+ onEdit: (row) => editRow(name, row)
689
+ }, name)),
690
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
691
+ className: "dsm-hint dsm-rowHint",
692
+ children: providerCount > 0 ? t("rowDesc") : ""
693
+ })
694
+ ]
444
695
  });
445
696
  }
446
697
  //#endregion
447
698
  //#region src/client/i18n.ts
448
699
  /**
449
700
  * 配置卡片文案(zh/en)。经 ctx.locale.register 注册、bind 取用,与官方卡片同机制。
701
+ * 公共键(save/discard/unsaved 等)来自 shared 的 common 字典,本文件只维护业务键。
450
702
  * @module subagent-model/client/i18n
451
703
  */
452
704
  const NS = "dsh-plus-subagent-model";
453
- const zh = {
705
+ const ownZh = {
454
706
  title: "子代理模型配置",
455
707
  description: "为 subagent / subagent_fork 等子代理单独指定提供商、模型与思考程度,或继承主代理。",
456
708
  enabled: "启用插件",
@@ -472,19 +724,9 @@ window.__ModuleLoader__.load({
472
724
  catalogRetry: "重试",
473
725
  catalogLoading: "目录加载中…",
474
726
  unknownValue: "目录外",
475
- save: "保存",
476
- saving: "保存中…",
477
- discard: "放弃",
478
- unsaved: "未保存",
479
- saveFailed: "保存失败:",
480
- loading: "加载中…",
481
- unavailable: "配置服务不可用。",
482
- readOnly: "当前部署无 settings provider,配置为只读;请编辑 settings.yaml。",
483
- expand: "展开",
484
- collapse: "收起",
485
727
  invalidModel: "模型不能脱离提供商单独配置。"
486
728
  };
487
- const en = {
729
+ const ownEn = {
488
730
  title: "Subagent model config",
489
731
  description: "Pick a provider, model, and thinking effort for subagent / subagent_fork children, or inherit from the main agent.",
490
732
  enabled: "Enable plugin",
@@ -506,80 +748,35 @@ window.__ModuleLoader__.load({
506
748
  catalogRetry: "Retry",
507
749
  catalogLoading: "Loading catalog…",
508
750
  unknownValue: "outside catalog",
509
- save: "Save",
510
- saving: "Saving…",
511
- discard: "Discard",
512
- unsaved: "Unsaved",
513
- saveFailed: "Save failed: ",
514
- loading: "Loading…",
515
- unavailable: "Configuration service unavailable.",
516
- readOnly: "No settings provider in this deployment; edit settings.yaml instead.",
517
- expand: "Expand",
518
- collapse: "Collapse",
519
751
  invalidModel: "Model cannot be set without a provider."
520
752
  };
753
+ const zh = mergeDict(commonZh, ownZh);
754
+ const en = mergeDict(commonEn, ownEn);
521
755
  //#endregion
522
756
  //#region src/client/styles.ts
523
757
  /**
524
- * 配置卡片样式:沿用官方 data-plugin / data-plugin-css 约定(HMR 据此卸载),
525
- * 视觉对齐官方卡片(--dsw-alias-* 变量),不覆盖上游任何选择器。
758
+ * 配置卡片样式:基础规则走 @dsh-plus/shared/client 的 cardCss('dsm'),
759
+ * 本文件只补插件特有规则(provider 行块 / banner / 目录空态)。
760
+ * 沿用官方 data-plugin / data-plugin-css 约定(HMR 据此卸载)。
526
761
  * @module subagent-model/client/styles
527
762
  */
528
763
  const PLUGIN_ID = "@dsh-plus/subagent-model";
529
- const STYLE_TAG_ID = `${PLUGIN_ID}/card.css`;
530
- const cardCss = `
531
- .dsm-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}
532
- .dsm-card:hover{border-color:var(--dsw-alias-label-dimmed)}
533
- .dsm-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}
534
- .dsm-header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}
535
- .dsm-header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}
536
- .dsm-headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}
537
- .dsm-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}
538
- .dsm-description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}
539
- .dsm-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s;font-size:12px}
540
- .dsm-chevronOpen{transform:rotate(180deg)}
541
- .dsm-pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
542
- .dsm-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}
543
- .dsm-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
544
- .dsm-field+.dsm-field{border-top:1px solid var(--dsw-alias-border-l2)}
545
- .dsm-head{align-items:center;gap:8px;display:flex}
546
- .dsm-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}
547
- .dsm-select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;min-width:0;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 8px;font-size:13px}
548
- .dsm-select:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}
549
- .dsm-select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
550
- .dsm-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
551
- .dsm-invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}
552
- .dsm-checkRow{align-items:center;gap:8px;display:flex;padding:6px 0}
553
- .dsm-checkRow input{accent-color:var(--dsw-alias-brand-primary)}
554
- .dsm-checkRow label{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5;cursor:pointer}
764
+ const cardCssAll = cardCss("dsm", `
555
765
  .dsm-row{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:10px;margin:12px 0 0;padding:4px 12px 10px}
556
766
  .dsm-rowHead{align-items:center;gap:8px;padding:8px 0 2px;display:flex}
557
767
  .dsm-rowName{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;font-weight:600;line-height:1.5}
558
768
  .dsm-rowHint{color:var(--dsw-alias-label-tertiary);margin:0 0 4px;font-size:12px;line-height:1.5}
559
- .dsm-groupLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;padding:12px 0 2px;margin:0}
560
- .dsm-status{min-width:0;color:var(--dsw-alias-label-secondary);flex:1;margin:0;font-size:12px;line-height:1.5}
561
- .dsm-statusError{color:var(--dsw-alias-label-error)}
562
769
  .dsm-banner{background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);justify-content:space-between;align-items:center;gap:8px;border-radius:8px;margin-top:12px;padding:7px 8px;font-size:12px;line-height:18px;display:flex}
563
770
  .dsm-bannerRetry{color:inherit;font:inherit;cursor:pointer;background:0 0;border:none;flex:none;padding:0;font-weight:600}
564
771
  .dsm-empty{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}
565
- .dsm-footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex;flex-wrap:wrap}
566
- .dsm-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}
567
- .dsm-btnGhost{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}
568
- .dsm-btnGhost:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}
569
- .dsm-btnPrimary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}
570
- .dsm-btn:disabled{opacity:.4;cursor:default}
571
- .dsm-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
572
- `;
772
+ @media (max-width:767px){
773
+ .dsm-row{padding:2px 8px 8px}
774
+ .dsm-rowHead{flex-wrap:wrap}
775
+ }
776
+ `);
573
777
  /** 幂等注入样式标签;返回标签(已存在或环境无 document 时为 null)。 */
574
778
  function injectStyle() {
575
- if (typeof document === "undefined") return null;
576
- if (document.querySelector(`style[data-plugin-css=${JSON.stringify(STYLE_TAG_ID)}]`) !== null) return null;
577
- const tag = document.createElement("style");
578
- tag.dataset.plugin = PLUGIN_ID;
579
- tag.dataset.pluginCss = STYLE_TAG_ID;
580
- tag.textContent = cardCss;
581
- document.head.appendChild(tag);
582
- return tag;
779
+ return injectCardStyle(PLUGIN_ID, cardCssAll);
583
780
  }
584
781
  //#endregion
585
782
  //#region src/client/client.ts
@@ -591,75 +788,6 @@ window.__ModuleLoader__.load({
591
788
  "connection",
592
789
  "remote"
593
790
  ];
594
- /**
595
- * api 直连实现的命名空间 scope(官方配置页 tab 同款模式)。
596
- * 读:describe 取命名空间视图(脱敏 value/revision + 顶层 writable);
597
- * 刷新:remote `settings/document-updated`(按命名空间过滤)与
598
- * `connection/reset`;generation 防旧读覆盖新发布。任何页面 origin
599
- * (loopback 或 tailnet 信任域名)下行为一致。
600
- */
601
- function createApiScope(api, ns, c) {
602
- let state = {
603
- status: "loading",
604
- value: void 0,
605
- revision: void 0,
606
- writable: false
607
- };
608
- const listeners = /* @__PURE__ */ new Set();
609
- let generation = 0;
610
- const publish = (next) => {
611
- state = next;
612
- for (const listener of [...listeners]) listener();
613
- };
614
- const load = async () => {
615
- const gen = ++generation;
616
- let response;
617
- try {
618
- response = await api.describe({});
619
- } catch {
620
- return;
621
- }
622
- if (gen !== generation || !response.result.ok) return;
623
- const writable = response.result.value?.writable ?? false;
624
- const view = response.result.value?.namespaces.find((candidate) => candidate.ns === ns);
625
- if (view === void 0) {
626
- publish({
627
- status: "unavailable",
628
- value: void 0,
629
- revision: void 0,
630
- writable
631
- });
632
- return;
633
- }
634
- publish({
635
- status: "ready",
636
- value: view.value,
637
- revision: view.revision,
638
- writable
639
- });
640
- };
641
- const refresh = (namespace) => {
642
- if (namespace !== void 0 && namespace !== ns) return;
643
- load();
644
- };
645
- const disposers = [c.get("remote").$on("settings/document-updated", refresh), c.on("connection/reset", () => {
646
- load();
647
- })];
648
- c.effect(() => () => {
649
- for (const dispose of disposers) dispose();
650
- }, "subagent-model: settings scope");
651
- load();
652
- return {
653
- getSnapshot: () => state,
654
- subscribe: (listener) => {
655
- listeners.add(listener);
656
- return () => {
657
- listeners.delete(listener);
658
- };
659
- },
660
- load
661
- };
662
- }
663
791
  function apply(ctx) {
664
792
  const c = ctx;
665
793
  const tag = injectStyle();
@@ -671,7 +799,7 @@ window.__ModuleLoader__.load({
671
799
  en
672
800
  }), "subagent-model: locale");
673
801
  const api = c.get("connection").api.settings;
674
- const scope = createApiScope(api, SETTINGS_NS, c);
802
+ const scope = createApiScope(api, SETTINGS_NS, c, "subagent-model: settings scope");
675
803
  c.slots.inject("settings.plugin.item", () => c.slots.register({
676
804
  name: "settings.plugin.item",
677
805
  key: SETTINGS_NS,