@khorsheed/dsh-context-guard 0.2.0 → 0.2.2

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.en.md +2 -1
  3. package/README.i18n.yaml +2 -2
  4. package/README.md +2 -1
  5. package/lib/client.js +320 -135
  6. package/lib/index.js +47 -18
  7. package/lib/types/client/CompactGuardButton.js +2 -2
  8. package/lib/types/client/SettingsCard.d.ts +11 -6
  9. package/lib/types/client/SettingsCard.js +50 -22
  10. package/lib/types/client/config.js +14 -1
  11. package/lib/types/client/icons.d.ts +19 -0
  12. package/lib/types/client/icons.js +3 -0
  13. package/lib/types/client/index.d.ts +22 -7
  14. package/lib/types/client/index.js +52 -10
  15. package/lib/types/client/scope.d.ts +62 -0
  16. package/lib/types/client/scope.js +59 -0
  17. package/lib/types/client/slots.d.ts +22 -11
  18. package/lib/types/index.d.ts +22 -8
  19. package/lib/types/index.js +32 -9
  20. package/lib/types/namespace.d.ts +2 -2
  21. package/lib/types/namespace.js +2 -2
  22. package/lib/types/settings.d.ts +16 -10
  23. package/lib/types/settings.js +19 -6
  24. package/locale/en.json +6 -0
  25. package/locale/zh.json +6 -0
  26. package/package.json +7 -21
  27. package/lib/client.js.map +0 -1
  28. package/lib/tsconfig.tsbuildinfo +0 -1
  29. package/lib/types/client/CompactGuardButton.d.ts.map +0 -1
  30. package/lib/types/client/SettingsCard.d.ts.map +0 -1
  31. package/lib/types/client/config.d.ts.map +0 -1
  32. package/lib/types/client/guard.d.ts.map +0 -1
  33. package/lib/types/client/index.d.ts.map +0 -1
  34. package/lib/types/client/locales.d.ts.map +0 -1
  35. package/lib/types/client/slots.d.ts.map +0 -1
  36. package/lib/types/index.d.ts.map +0 -1
  37. package/lib/types/invariant.d.ts.map +0 -1
  38. package/lib/types/namespace.d.ts.map +0 -1
  39. package/lib/types/settings.d.ts.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # 变更记录
2
2
 
3
+ ## 0.2.2(2026-09-26)
4
+
5
+ 适配宿主 rc.1 线并实证 0.1.5/0.1.7 双线可用(0.1.5-rc.1 全量 boot 实证,2026-09-25)。
6
+
7
+ - **家族 bundle 形态下设置卡迁址**:随 `@khorsheed/dsh-bundle-conversation-toolbox` 安装时成员不再是 profile 直依,插件页不给成员开详情页——设置卡(阈值编辑)改注册到行级槽位 `plugins.row.config`(键 `…#context-guard`),从 bundle 详情页该行的「配置」入口打开;独立安装与 0.1.5 legacy 卡两臂不变
8
+ - 设置面双线:rc.1 走 entry Config(volatile 探测)+ `configForms`,0.1.5 留 `settings.register` + `settingsScope`;`SessionStatus` 新面迁移,卡注册改双臂(`settings.plugin.item` / `plugins.bundle.config`)
9
+ - 图标自持化:插件用到的 rc.1 图标图样由 `sync-icon-artwork` 生成器摊平进包内 `src/client/icons.tsx`(0.1.5 与 rc.1 的图标导出名零交集,外部化引用在 0.1.5 上是 undefined 会炸槽位);两条宿主线渲染同一份图样
10
+ - 插件清单展示元数据(`locale/*.json`):rc.1 宿主插件页的卡面标题/描述中文化
11
+
12
+ ## 0.2.1(2026-09-11)
13
+
14
+ - 无功能变更:开发基线随仓内 pin 对齐官方 `0.1.5-rc.1` / cordis `4.0.2`(消除 4.0.1/4.0.2 双实例图分裂);peer 依赖范围与 minHost(`0.1.2-rc.1`)不变
15
+
3
16
  ## 0.2.0(2026-09-10)
4
17
 
5
18
  适配宿主 0.1.2 线。
package/README.en.md CHANGED
@@ -31,7 +31,7 @@ dsh plugin --profile web remove @khorsheed/dsh-context-guard
31
31
 
32
32
  ## Config
33
33
 
34
- One tunable, editable in the GUI (Settings → Plugins → "压缩提醒时机 / Compaction reminder timing") and live with no restart.
34
+ One tunable, editable in the GUI and live with no restart — on 0.1.5 under Settings → Plugins ("压缩提醒时机 / Compaction reminder timing"); on 0.1.6-alpha.2+ in the bundle's own configuration section on its Plugins-page detail view.
35
35
 
36
36
  | Key | Default | Meaning |
37
37
  |---|---|---|
@@ -48,6 +48,7 @@ plugins:
48
48
  ## Compatibility
49
49
 
50
50
  - npm release line (`@deepseek-ai/dsh@0.1.2-rc.1`): ✅ full — baseline moved to the 0.1.2-rc.1 API surface (single-arm 0.1.2 API consumption; the 0.1.1-rc.2 runtime arm is retired), full build+test green; minHost moves up to 0.1.2-rc.1 — older hosts stay on the previous release line.
51
+ - 0.1.6-alpha.2 pre-release line (`@deepseek-ai/dsh@0.1.6-alpha.2`): ✅ full — the `settings.plugin.item` slot is removed in the ui-settings-plugins refactor; the settings card moves to `plugins.bundle.config` (keyed by package name, the Plugins page drawing the title chrome); the dual-inject probe keeps the 0.1.5 card working, minHost unchanged.
51
52
  - source line (deepseek-harness master): ✅ (verifiedHost: 0.1.2-rc.1)
52
53
 
53
54
  **Version line mapping**: 0.2.0 and up support host `0.1.2-rc.1` and later; hosts on `0.1.0-rc.6` ~ `0.1.1-rc.2` stay on the 0.1.x release line (last release `0.1.0`).
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # last confirmed-consistent state. Both languages carry equal authority; after
3
3
  # editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/context-guard/README.en.md
5
- packages/context-guard/README.en.md: 4c308d5e35b1ad8c0c9bc737e46ba5eed4a7278d
6
- packages/context-guard/README.md: c66c57b80d707077daec308c31e9a1a8c56bd197
5
+ packages/context-guard/README.en.md: 2c1f5f4465d6f658a2f5ba18bf9707d7a1082443
6
+ packages/context-guard/README.md: 12a9ddd4fb4ee7d2b7466a85315c783e113b6fa7
package/README.md CHANGED
@@ -31,7 +31,7 @@ dsh plugin --profile web remove @khorsheed/dsh-context-guard
31
31
 
32
32
  ## 配置
33
33
 
34
- 一个可调项,在 GUI 里改(设置 → 插件配置 →「压缩提醒时机 / Compaction reminder timing」)且实时生效,无需重启。
34
+ 一个可调项,在 GUI 里改且实时生效,无需重启——0.1.5:设置 → 插件配置 →「压缩提醒时机 / Compaction reminder timing」;0.1.6-alpha.2 起:插件管理页 → 本包详情页的配置区。
35
35
 
36
36
  | 键 | 默认值 | 含义 |
37
37
  |---|---|---|
@@ -48,6 +48,7 @@ plugins:
48
48
  ## Compatibility
49
49
 
50
50
  - npm 发布线(`@deepseek-ai/dsh@0.1.2-rc.1`):✅ 完整——基线迁移至 0.1.2-rc.1 API 面(单臂消费 0.1.2 API,0.1.1-rc.2 运行臂已退役),全量构建测试通过;minHost 前移至 0.1.2-rc.1,旧宿主请停留在旧发布线。
51
+ - 0.1.6-alpha.2 预发布线(`@deepseek-ai/dsh@0.1.6-alpha.2`):✅ 完整——`settings.plugin.item` 槽随 ui-settings-plugins 重构删除,设置卡改投 `plugins.bundle.config`(按包名 keyed,Plugins 页绘制标题后渲染本表单);双注入探测保持 0.1.5 线旧卡可用,minHost 不动。
51
52
  - 源码线(deepseek-harness master):✅(verifiedHost: 0.1.2-rc.1)
52
53
 
53
54
  **版本线对照**:0.2.0 起支持宿主 `0.1.2-rc.1` 及以后;宿主 `0.1.0-rc.6` ~ `0.1.1-rc.2` 的用户请停留在 0.1.x 发布线(末版 `0.1.0`)。
package/lib/client.js CHANGED
@@ -5,14 +5,13 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react = require("react");
8
- let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
8
  let react_jsx_runtime = require("react/jsx-runtime");
10
9
  //#region src/namespace.ts
11
10
  /**
12
11
  * The settings namespace owned by the context-guard plugin. Plain string so
13
12
  * both the host half (settings registration) and the browser half
14
- * (settingsScope binding and the settings.plugin.item card key) import it
15
- * without dragging host-only dependencies into the client bundle.
13
+ * (settingsScope binding and the 0.1.5 settings.plugin.item card key) import
14
+ * it without dragging host-only dependencies into the client bundle.
16
15
  */
17
16
  /** Settings namespace owned by the context-guard plugin. */
18
17
  const CONTEXT_GUARD_NS = "context-guard";
@@ -25,6 +24,16 @@ window.__ModuleLoader__.load({
25
24
  return Math.min(max, Math.max(min, value));
26
25
  }
27
26
  /**
27
+ * Unwrap one maybe-Volatile config field. rc.1 resolves volatile-marked
28
+ * Config fields into live references (`{ get() }`); 0.1.5 passes the plain
29
+ * value. The schema's volatile marker is probed per line (see
30
+ * ../settings.ts), so BOTH shapes arrive depending on the host line.
31
+ */
32
+ function unwrapVolatile(value) {
33
+ if (value !== null && typeof value === "object" && typeof value.get === "function") return value.get();
34
+ return value;
35
+ }
36
+ /**
28
37
  * Normalize the entry config into the full {@link ContextGuardConfig}: every
29
38
  * omitted field takes its documented default and every numeric field is
30
39
  * clamped into its legal range.
@@ -32,7 +41,8 @@ window.__ModuleLoader__.load({
32
41
  * @returns the effective guard behavior.
33
42
  */
34
43
  function resolveConfig(config) {
35
- return { thresholdRatio: clamp(config?.thresholdRatio ?? .8, THRESHOLD_RATIO_MIN, THRESHOLD_RATIO_MAX) };
44
+ const ratio = unwrapVolatile(config?.thresholdRatio);
45
+ return { thresholdRatio: clamp(typeof ratio === "number" ? ratio : .8, THRESHOLD_RATIO_MIN, THRESHOLD_RATIO_MAX) };
36
46
  }
37
47
  //#endregion
38
48
  //#region src/client/locales.ts
@@ -76,6 +86,106 @@ window.__ModuleLoader__.load({
76
86
  "settings.error": "Save failed, please retry"
77
87
  };
78
88
  //#endregion
89
+ //#region src/client/scope.ts
90
+ /** Published while no settings provider serves the section (stable identity). */
91
+ const UNAVAILABLE = {
92
+ status: "unavailable",
93
+ value: void 0,
94
+ base: void 0,
95
+ user: void 0,
96
+ revision: void 0,
97
+ writable: false
98
+ };
99
+ /**
100
+ * Observable mirror of the bound scope. The slot entries' `hooks.config` seat
101
+ * binds to this at registration time — before (and whether or not) a settings
102
+ * provider appears — and arming swaps the source in and republishes.
103
+ */
104
+ var GuardScopeChannel = class {
105
+ scope;
106
+ listeners = /* @__PURE__ */ new Set();
107
+ getSnapshot = () => this.scope?.getSnapshot() ?? UNAVAILABLE;
108
+ subscribe = (listener) => {
109
+ this.listeners.add(listener);
110
+ return () => {
111
+ this.listeners.delete(listener);
112
+ };
113
+ };
114
+ set = (field, value) => this.scope?.set(field, value) ?? Promise.resolve(false);
115
+ unset = (field) => this.scope?.unset(field) ?? Promise.resolve(false);
116
+ /** Bind the line-served scope; the first arm wins (the two lines never coexist). */
117
+ arm(scope) {
118
+ if (this.scope !== void 0) return;
119
+ this.scope = scope;
120
+ scope.subscribe(() => {
121
+ this.emit();
122
+ });
123
+ this.emit();
124
+ }
125
+ emit() {
126
+ for (const listener of this.listeners) listener();
127
+ }
128
+ };
129
+ /**
130
+ * Arm the channel from whichever settings face the host line serves: rc.1's
131
+ * `configForms.get(entryId)` — the entry id IS the settings namespace, both
132
+ * being the row id — first, 0.1.5's `settingsScope.bind({ namespace })`
133
+ * second. Both probes ride deferred injects so a later-mounting provider
134
+ * still lands; a composition with neither leaves the channel unavailable.
135
+ * @param ctx - the client plugin context.
136
+ * @param channel - the observable the slot entries read.
137
+ */
138
+ function bindGuardScope(ctx, channel) {
139
+ ctx.inject(["configForms"], (formsCtx) => {
140
+ const forms = formsCtx.get("configForms");
141
+ if (typeof forms?.get === "function") channel.arm(forms.get(CONTEXT_GUARD_NS));
142
+ });
143
+ ctx.inject(["settingsScope"], (legacyCtx) => {
144
+ const legacy = legacyCtx.get("settingsScope");
145
+ if (typeof legacy?.bind === "function") channel.arm(legacy.bind({ namespace: CONTEXT_GUARD_NS }));
146
+ });
147
+ }
148
+ //#endregion
149
+ //#region src/client/icons.tsx
150
+ const IconChevronDownOutlineMedium = ({ size = 14, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
151
+ width: size,
152
+ height: size,
153
+ className,
154
+ viewBox: "0 0 16 16",
155
+ fill: "none",
156
+ xmlns: "http://www.w3.org/2000/svg",
157
+ "aria-hidden": "true",
158
+ strokeWidth: 1.3,
159
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
160
+ d: "M4 6L7.29289 9.29289C7.68342 9.68342 8.31658 9.68342 8.70711 9.29289L12 6",
161
+ stroke: "currentColor"
162
+ })
163
+ });
164
+ const IconWarningOutlineMedium = ({ size = 16, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
165
+ width: size,
166
+ height: size,
167
+ className,
168
+ viewBox: "0 0 16 16",
169
+ fill: "none",
170
+ xmlns: "http://www.w3.org/2000/svg",
171
+ "aria-hidden": "true",
172
+ strokeWidth: 1.3,
173
+ children: [
174
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
175
+ d: "M8 14.5C11.5899 14.5 14.5 11.5899 14.5 8C14.5 4.41015 11.5899 1.5 8 1.5C4.41015 1.5 1.5 4.41015 1.5 8C1.5 11.5899 4.41015 14.5 8 14.5Z",
176
+ stroke: "currentColor"
177
+ }),
178
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
179
+ d: "M8 4.29199V9.79199",
180
+ stroke: "currentColor"
181
+ }),
182
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
183
+ d: "M8 10.708V11.708",
184
+ stroke: "currentColor"
185
+ })
186
+ ]
187
+ });
188
+ //#endregion
79
189
  //#region src/client/guard.ts
80
190
  /**
81
191
  * Decide the guard level for one request budget.
@@ -100,7 +210,7 @@ window.__ModuleLoader__.load({
100
210
  }
101
211
  //#endregion
102
212
  //#region \0dsh-css:src/client/CompactGuardButton.module.css.mjs
103
- const css$1 = ".BFFw2a_wrap{align-items:center;gap:6px;display:inline-flex}.BFFw2a_button{cursor:pointer;border:none;border-radius:999px;align-items:center;gap:4px;padding:2px 8px;font-size:13px;font-weight:500;line-height:20px;display:inline-flex}.BFFw2a_button:focus-visible{outline-offset:2px;outline:2px solid}.BFFw2a_button:disabled{opacity:.6;cursor:default}.BFFw2a_icon{color:currentColor;align-items:center;display:inline-flex}.BFFw2a_warning{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label)}.BFFw2a_warning:hover:not(:disabled){color:var(--dsw-alias-state-warn-primary)}.BFFw2a_error{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}";
213
+ const css$1 = ".X5Dx0q_wrap{align-items:center;gap:6px;display:inline-flex}.X5Dx0q_button{cursor:pointer;border:none;border-radius:999px;align-items:center;gap:4px;padding:2px 8px;font-size:13px;font-weight:500;line-height:20px;display:inline-flex}.X5Dx0q_button:focus-visible{outline-offset:2px;outline:2px solid}.X5Dx0q_button:disabled{opacity:.6;cursor:default}.X5Dx0q_icon{color:currentColor;align-items:center;display:inline-flex}.X5Dx0q_warning{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label)}.X5Dx0q_warning:hover:not(:disabled){color:var(--dsw-alias-state-warn-primary)}.X5Dx0q_error{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}";
104
214
  const tagId$1 = "@khorsheed/dsh-context-guard/CompactGuardButton.module.css";
105
215
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
106
216
  const tag = document.createElement("style");
@@ -110,11 +220,11 @@ window.__ModuleLoader__.load({
110
220
  document.head.appendChild(tag);
111
221
  }
112
222
  var CompactGuardButton_module_css_default = {
113
- "error": "BFFw2a_error",
114
- "warning": "BFFw2a_warning",
115
- "button": "BFFw2a_button",
116
- "icon": "BFFw2a_icon",
117
- "wrap": "BFFw2a_wrap"
223
+ "icon": "X5Dx0q_icon",
224
+ "button": "X5Dx0q_button",
225
+ "wrap": "X5Dx0q_wrap",
226
+ "error": "X5Dx0q_error",
227
+ "warning": "X5Dx0q_warning"
118
228
  };
119
229
  //#endregion
120
230
  //#region src/client/CompactGuardButton.tsx
@@ -170,7 +280,7 @@ window.__ModuleLoader__.load({
170
280
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
171
281
  className: CompactGuardButton_module_css_default.icon,
172
282
  "aria-hidden": true,
173
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, { size: 14 })
283
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconWarningOutlineMedium, { size: 14 })
174
284
  }), t("button.label")]
175
285
  }), error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
176
286
  className: CompactGuardButton_module_css_default.error,
@@ -182,7 +292,7 @@ window.__ModuleLoader__.load({
182
292
  }
183
293
  //#endregion
184
294
  //#region \0dsh-css:src/client/SettingsCard.module.css.mjs
185
- const css = ".DKjZXq_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}.DKjZXq_card:hover{border-color:var(--dsw-alias-label-dimmed)}.DKjZXq_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.DKjZXq_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}.DKjZXq_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.DKjZXq_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.DKjZXq_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.DKjZXq_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.DKjZXq_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.DKjZXq_chevronOpen{transform:rotate(180deg)}.DKjZXq_body{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:12px;margin:0 16px;padding:12px 0 8px;display:flex}.DKjZXq_field{flex-direction:column;gap:6px;display:flex}.DKjZXq_fieldHead{align-items:center;gap:8px;display:flex}.DKjZXq_fieldName{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:1.4}.DKjZXq_badge{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label);border-radius:999px;padding:2px 6px;font-size:11px;line-height:1}.DKjZXq_inputRow{align-items:center;gap:8px;display:flex}.DKjZXq_input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:140px;color:var(--dsw-alias-label-primary);border-radius:8px;padding:4px 8px;font-size:13px;line-height:1.4}.DKjZXq_input:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-1px}.DKjZXq_inputInvalid{border-color:var(--dsw-alias-state-error-primary)}.DKjZXq_reset{appearance:none;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:6px;padding:2px 6px;font-size:12px;line-height:1.4}.DKjZXq_reset:hover{color:var(--dsw-alias-label-primary)}.DKjZXq_hint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}.DKjZXq_footer{justify-content:flex-end;align-items:center;gap:8px;margin-top:4px;display:flex}.DKjZXq_status{flex:1;min-width:0}.DKjZXq_saved{color:var(--dsw-alias-state-success-primary);font-size:12px}.DKjZXq_errorText{color:var(--dsw-alias-state-error-primary);font-size:12px}.DKjZXq_secondary,.DKjZXq_save{appearance:none;border:1px solid var(--dsw-alias-border-l2);cursor:pointer;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:8px;padding:4px 12px;font-size:13px;line-height:1.4}.DKjZXq_secondary:disabled,.DKjZXq_save:disabled{opacity:.5;cursor:default}.DKjZXq_save{background:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-primary-foreground);border-color:#0000}.DKjZXq_save:hover:not(:disabled){filter:brightness(.95)}";
295
+ const css = ".MRoRmW_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}.MRoRmW_card:hover{border-color:var(--dsw-alias-label-dimmed)}.MRoRmW_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.MRoRmW_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}.MRoRmW_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.MRoRmW_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.MRoRmW_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.MRoRmW_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.MRoRmW_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.MRoRmW_chevronOpen{transform:rotate(180deg)}.MRoRmW_body{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:12px;margin:0 16px;padding:12px 0 8px;display:flex}.MRoRmW_field{flex-direction:column;gap:6px;display:flex}.MRoRmW_fieldHead{align-items:center;gap:8px;display:flex}.MRoRmW_fieldName{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:1.4}.MRoRmW_badge{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label);border-radius:999px;padding:2px 6px;font-size:11px;line-height:1}.MRoRmW_inputRow{align-items:center;gap:8px;display:flex}.MRoRmW_input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:140px;color:var(--dsw-alias-label-primary);border-radius:8px;padding:4px 8px;font-size:13px;line-height:1.4}.MRoRmW_input:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-1px}.MRoRmW_inputInvalid{border-color:var(--dsw-alias-state-error-primary)}.MRoRmW_reset{appearance:none;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:6px;padding:2px 6px;font-size:12px;line-height:1.4}.MRoRmW_reset:hover{color:var(--dsw-alias-label-primary)}.MRoRmW_hint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}.MRoRmW_footer{justify-content:flex-end;align-items:center;gap:8px;margin-top:4px;display:flex}.MRoRmW_status{flex:1;min-width:0}.MRoRmW_saved{color:var(--dsw-alias-state-success-primary);font-size:12px}.MRoRmW_errorText{color:var(--dsw-alias-state-error-primary);font-size:12px}.MRoRmW_secondary,.MRoRmW_save{appearance:none;border:1px solid var(--dsw-alias-border-l2);cursor:pointer;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:8px;padding:4px 12px;font-size:13px;line-height:1.4}.MRoRmW_secondary:disabled,.MRoRmW_save:disabled{opacity:.5;cursor:default}.MRoRmW_save{background:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-primary-foreground);border-color:#0000}.MRoRmW_save:hover:not(:disabled){filter:brightness(.95)}";
186
296
  const tagId = "@khorsheed/dsh-context-guard/SettingsCard.module.css";
187
297
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
188
298
  const tag = document.createElement("style");
@@ -192,30 +302,30 @@ window.__ModuleLoader__.load({
192
302
  document.head.appendChild(tag);
193
303
  }
194
304
  var SettingsCard_module_css_default = {
195
- "secondary": "DKjZXq_secondary",
196
- "fieldName": "DKjZXq_fieldName",
197
- "badge": "DKjZXq_badge",
198
- "field": "DKjZXq_field",
199
- "chevronOpen": "DKjZXq_chevronOpen",
200
- "input": "DKjZXq_input",
201
- "footer": "DKjZXq_footer",
202
- "body": "DKjZXq_body",
203
- "saved": "DKjZXq_saved",
204
- "hint": "DKjZXq_hint",
205
- "headText": "DKjZXq_headText",
206
- "cardOpen": "DKjZXq_cardOpen",
207
- "header": "DKjZXq_header",
208
- "description": "DKjZXq_description",
209
- "inputRow": "DKjZXq_inputRow",
210
- "save": "DKjZXq_save",
211
- "status": "DKjZXq_status",
212
- "name": "DKjZXq_name",
213
- "chevron": "DKjZXq_chevron",
214
- "card": "DKjZXq_card",
215
- "fieldHead": "DKjZXq_fieldHead",
216
- "reset": "DKjZXq_reset",
217
- "errorText": "DKjZXq_errorText",
218
- "inputInvalid": "DKjZXq_inputInvalid"
305
+ "fieldName": "MRoRmW_fieldName",
306
+ "headText": "MRoRmW_headText",
307
+ "description": "MRoRmW_description",
308
+ "fieldHead": "MRoRmW_fieldHead",
309
+ "name": "MRoRmW_name",
310
+ "badge": "MRoRmW_badge",
311
+ "inputInvalid": "MRoRmW_inputInvalid",
312
+ "saved": "MRoRmW_saved",
313
+ "input": "MRoRmW_input",
314
+ "body": "MRoRmW_body",
315
+ "hint": "MRoRmW_hint",
316
+ "status": "MRoRmW_status",
317
+ "field": "MRoRmW_field",
318
+ "inputRow": "MRoRmW_inputRow",
319
+ "footer": "MRoRmW_footer",
320
+ "errorText": "MRoRmW_errorText",
321
+ "card": "MRoRmW_card",
322
+ "reset": "MRoRmW_reset",
323
+ "save": "MRoRmW_save",
324
+ "header": "MRoRmW_header",
325
+ "secondary": "MRoRmW_secondary",
326
+ "chevron": "MRoRmW_chevron",
327
+ "cardOpen": "MRoRmW_cardOpen",
328
+ "chevronOpen": "MRoRmW_chevronOpen"
219
329
  };
220
330
  //#endregion
221
331
  //#region src/client/SettingsCard.tsx
@@ -229,18 +339,11 @@ window.__ModuleLoader__.load({
229
339
  return snapshot.user !== void 0 && typeof snapshot.user === "object" && snapshot.user !== null && field in snapshot.user;
230
340
  }
231
341
  /**
232
- * The context-guard settings card in the plugin configuration tab: the same
233
- * collapsible chrome the official plugin cards use (a header naming the
234
- * plugin and what its settings govern, disclosing the controls in place),
235
- * re-implemented here because the client bundle-purity gate forbids
236
- * value-importing the official card chrome. Two staged number fields write
237
- * through the shared settingsScope on save; per-field reset reverts a field
238
- * to the composition layer.
342
+ * The staged threshold edit both settings surfaces share. Two staged number
343
+ * fields write through the shared settings scope on save; per-field reset
344
+ * reverts a field to the composition layer.
239
345
  */
240
- function ContextGuardSettingsCard({ useConfig, scope, t }) {
241
- const snapshot = useConfig((value) => value);
242
- const value = snapshot.status === "ready" ? snapshot.value : void 0;
243
- const [open, setOpen] = (0, react.useState)(false);
346
+ function useThresholdDraft(scope, snapshot, value) {
244
347
  const [threshold, setThreshold] = (0, react.useState)({
245
348
  text: "",
246
349
  invalid: false
@@ -265,7 +368,6 @@ window.__ModuleLoader__.load({
265
368
  aliveRef.current = false;
266
369
  };
267
370
  }, [value, snapshot.revision]);
268
- if (snapshot.status === "unavailable") return null;
269
371
  const write = (draft, min, max) => {
270
372
  const parsed = parseNumber(draft.text);
271
373
  setThreshold({
@@ -294,8 +396,116 @@ window.__ModuleLoader__.load({
294
396
  if (aliveRef.current) setError(true);
295
397
  });
296
398
  };
399
+ const discard = () => {
400
+ if (value === void 0) return;
401
+ setThreshold({
402
+ text: String(value.thresholdRatio),
403
+ invalid: false
404
+ });
405
+ setSaved(false);
406
+ setError(false);
407
+ };
408
+ return {
409
+ threshold,
410
+ saved,
411
+ error,
412
+ dirty: threshold.text !== String(value?.thresholdRatio ?? ""),
413
+ write,
414
+ save,
415
+ reset,
416
+ discard
417
+ };
418
+ }
419
+ /** The threshold field row and the save/discard footer both settings surfaces render. */
420
+ function SettingsBody({ snapshot, draft, t }) {
421
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
422
+ className: SettingsCard_module_css_default.body,
423
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
424
+ className: SettingsCard_module_css_default.field,
425
+ children: [
426
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
427
+ className: SettingsCard_module_css_default.fieldHead,
428
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
429
+ className: SettingsCard_module_css_default.fieldName,
430
+ children: t("settings.field.threshold")
431
+ }), overridden(snapshot, "thresholdRatio") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
432
+ className: SettingsCard_module_css_default.badge,
433
+ children: t("settings.overridden")
434
+ })]
435
+ }),
436
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
437
+ className: SettingsCard_module_css_default.inputRow,
438
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
439
+ className: draft.threshold.invalid ? `${SettingsCard_module_css_default.input} ${SettingsCard_module_css_default.inputInvalid}` : SettingsCard_module_css_default.input,
440
+ type: "number",
441
+ step: "0.01",
442
+ min: "0.01",
443
+ max: "1",
444
+ value: draft.threshold.text,
445
+ "aria-label": t("settings.field.threshold"),
446
+ onChange: (event) => {
447
+ draft.write({
448
+ text: event.target.value,
449
+ invalid: false
450
+ }, .01, 1);
451
+ }
452
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
453
+ type: "button",
454
+ className: SettingsCard_module_css_default.reset,
455
+ onClick: draft.reset,
456
+ children: t("settings.reset")
457
+ })]
458
+ }),
459
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
460
+ className: SettingsCard_module_css_default.hint,
461
+ children: t("settings.field.threshold.hint")
462
+ })
463
+ ]
464
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
465
+ className: SettingsCard_module_css_default.footer,
466
+ children: [
467
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
468
+ className: SettingsCard_module_css_default.status,
469
+ children: [draft.saved ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
470
+ className: SettingsCard_module_css_default.saved,
471
+ children: t("settings.saved")
472
+ }) : null, draft.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
473
+ className: SettingsCard_module_css_default.errorText,
474
+ children: t("settings.error")
475
+ }) : null]
476
+ }),
477
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
478
+ type: "button",
479
+ className: SettingsCard_module_css_default.secondary,
480
+ disabled: !draft.dirty,
481
+ onClick: draft.discard,
482
+ children: t("settings.discard")
483
+ }),
484
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
485
+ type: "button",
486
+ className: SettingsCard_module_css_default.save,
487
+ disabled: !draft.dirty || draft.threshold.invalid || snapshot.writable !== true,
488
+ onClick: draft.save,
489
+ children: t("settings.save")
490
+ })
491
+ ]
492
+ })]
493
+ });
494
+ }
495
+ /**
496
+ * The context-guard settings card in the 0.1.5 plugin configuration tab: the
497
+ * same collapsible chrome the official plugin cards use (a header naming the
498
+ * plugin and what its settings govern, disclosing the controls in place),
499
+ * re-implemented here because the client bundle-purity gate forbids
500
+ * value-importing the official card chrome.
501
+ */
502
+ function ContextGuardSettingsCard({ useConfig, scope, t }) {
503
+ const snapshot = useConfig((value) => value);
504
+ const value = snapshot.status === "ready" ? snapshot.value : void 0;
505
+ const [open, setOpen] = (0, react.useState)(false);
506
+ const draft = useThresholdDraft(scope, snapshot, value);
507
+ if (snapshot.status === "unavailable") return null;
297
508
  const title = t("settings.title");
298
- const dirty = threshold.text !== String(value?.thresholdRatio ?? "");
299
509
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
300
510
  className: open ? `${SettingsCard_module_css_default.card} ${SettingsCard_module_css_default.cardOpen}` : SettingsCard_module_css_default.card,
301
511
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
@@ -315,99 +525,54 @@ window.__ModuleLoader__.load({
315
525
  className: SettingsCard_module_css_default.description,
316
526
  children: t("settings.description")
317
527
  })]
318
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: open ? `${SettingsCard_module_css_default.chevron} ${SettingsCard_module_css_default.chevronOpen}` : SettingsCard_module_css_default.chevron })]
319
- }), open && value !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
320
- className: SettingsCard_module_css_default.body,
321
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
322
- className: SettingsCard_module_css_default.field,
323
- children: [
324
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
325
- className: SettingsCard_module_css_default.fieldHead,
326
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
327
- className: SettingsCard_module_css_default.fieldName,
328
- children: t("settings.field.threshold")
329
- }), overridden(snapshot, "thresholdRatio") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
330
- className: SettingsCard_module_css_default.badge,
331
- children: t("settings.overridden")
332
- })]
333
- }),
334
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
335
- className: SettingsCard_module_css_default.inputRow,
336
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
337
- className: threshold.invalid ? `${SettingsCard_module_css_default.input} ${SettingsCard_module_css_default.inputInvalid}` : SettingsCard_module_css_default.input,
338
- type: "number",
339
- step: "0.01",
340
- min: "0.01",
341
- max: "1",
342
- value: threshold.text,
343
- "aria-label": t("settings.field.threshold"),
344
- onChange: (event) => {
345
- write({
346
- text: event.target.value,
347
- invalid: false
348
- }, .01, 1);
349
- }
350
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
351
- type: "button",
352
- className: SettingsCard_module_css_default.reset,
353
- onClick: reset,
354
- children: t("settings.reset")
355
- })]
356
- }),
357
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
358
- className: SettingsCard_module_css_default.hint,
359
- children: t("settings.field.threshold.hint")
360
- })
361
- ]
362
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
363
- className: SettingsCard_module_css_default.footer,
364
- children: [
365
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
366
- className: SettingsCard_module_css_default.status,
367
- children: [saved ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
368
- className: SettingsCard_module_css_default.saved,
369
- children: t("settings.saved")
370
- }) : null, error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
371
- className: SettingsCard_module_css_default.errorText,
372
- children: t("settings.error")
373
- }) : null]
374
- }),
375
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
376
- type: "button",
377
- className: SettingsCard_module_css_default.secondary,
378
- disabled: !dirty,
379
- onClick: () => {
380
- if (value === void 0) return;
381
- setThreshold({
382
- text: String(value.thresholdRatio),
383
- invalid: false
384
- });
385
- setSaved(false);
386
- setError(false);
387
- },
388
- children: t("settings.discard")
389
- }),
390
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
391
- type: "button",
392
- className: SettingsCard_module_css_default.save,
393
- disabled: !dirty || threshold.invalid || snapshot.writable !== true,
394
- onClick: save,
395
- children: t("settings.save")
396
- })
397
- ]
398
- })]
528
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDownOutlineMedium, { className: open ? `${SettingsCard_module_css_default.chevron} ${SettingsCard_module_css_default.chevronOpen}` : SettingsCard_module_css_default.chevron })]
529
+ }), open && value !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsBody, {
530
+ snapshot,
531
+ draft,
532
+ t
399
533
  })]
400
534
  });
401
535
  }
536
+ /**
537
+ * The bundle's configuration on the alpha.2 Plugins page
538
+ * (`plugins.bundle.config`, keyed by package name): the page draws the title,
539
+ * the icon, and the crumb itself, so the entry renders the one-liner for the
540
+ * `summary` view and the bare form — with its own save control — for `page`.
541
+ */
542
+ function ContextGuardBundleConfig({ view, useConfig, scope, t }) {
543
+ const snapshot = useConfig((value) => value);
544
+ const value = snapshot.status === "ready" ? snapshot.value : void 0;
545
+ const draft = useThresholdDraft(scope, snapshot, value);
546
+ if (view === "summary") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
547
+ className: SettingsCard_module_css_default.description,
548
+ children: t("settings.description")
549
+ });
550
+ if (snapshot.status === "unavailable" || value === void 0) return null;
551
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsBody, {
552
+ snapshot,
553
+ draft,
554
+ t
555
+ });
556
+ }
402
557
  //#endregion
403
558
  //#region src/client/index.ts
404
- /** Required services: the slot ledger, the command Remote, the settings scope, and the copy. */
559
+ /**
560
+ * This bundle's package name — the key the Plugins page dispatches
561
+ * `plugins.bundle.config` entries on (identity triangle: cordis.patch.yml,
562
+ * tsdown.config.ts, invariant.ts).
563
+ */
564
+ const PACKAGE_NAME = "@khorsheed/dsh-context-guard";
565
+ /**
566
+ * Required services: the slot ledger, the command Remote, and the copy. The
567
+ * settings scope is deliberately NOT injected — rc.1 and 0.1.5 name different
568
+ * services (`configForms` vs `settingsScope`), and a composition without
569
+ * either must not pend the bundle (see ./scope.ts).
570
+ */
405
571
  const inject = [
406
572
  "slots",
407
573
  "remote",
408
574
  "remote.commands",
409
- "locale",
410
- "settingsScope"
575
+ "locale"
411
576
  ];
412
577
  /**
413
578
  * Client plugin body: register the composer-tool-row compact button and the
@@ -421,7 +586,8 @@ window.__ModuleLoader__.load({
421
586
  zh,
422
587
  en
423
588
  }), "context-guard: dictionaries");
424
- const scope = ctx.settingsScope.bind({ namespace: CONTEXT_GUARD_NS });
589
+ const scope = new GuardScopeChannel();
590
+ bindGuardScope(ctx, scope);
425
591
  ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
426
592
  name: "conversation.input.right",
427
593
  id: "context-guard",
@@ -438,7 +604,26 @@ window.__ModuleLoader__.load({
438
604
  hooks: { config: scope }
439
605
  })
440
606
  }, CompactGuardButton));
441
- ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
607
+ ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
608
+ name: "plugins.bundle.config",
609
+ key: PACKAGE_NAME,
610
+ locale: NS,
611
+ inject: () => ({
612
+ scope,
613
+ hooks: { config: scope }
614
+ })
615
+ }, ContextGuardBundleConfig));
616
+ ctx.slots.inject("plugins.row.config", () => ctx.slots.register({
617
+ name: "plugins.row.config",
618
+ key: "@khorsheed/dsh-bundle-conversation-toolbox#context-guard",
619
+ locale: NS,
620
+ inject: () => ({
621
+ scope,
622
+ hooks: { config: scope }
623
+ })
624
+ }, ContextGuardBundleConfig));
625
+ const legacy = ctx.slots;
626
+ legacy.inject("settings.plugin.item", () => legacy.register({
442
627
  name: "settings.plugin.item",
443
628
  key: CONTEXT_GUARD_NS,
444
629
  locale: NS,