@khorsheed/dsh-context-guard 0.1.0 → 0.2.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/CHANGELOG.md +8 -0
- package/README.en.md +13 -9
- package/README.i18n.yaml +2 -2
- package/README.md +17 -13
- package/lib/client.js +22 -22
- package/lib/client.js.map +1 -1
- package/lib/index.js +1 -2
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/types/client/index.d.ts +2 -2
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/slots.d.ts +1 -1
- package/lib/types/client/slots.d.ts.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +1 -2
- package/package.json +15 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# 变更记录
|
|
2
2
|
|
|
3
|
+
## 0.2.0(2026-09-10)
|
|
4
|
+
|
|
5
|
+
适配宿主 0.1.2 线。
|
|
6
|
+
|
|
7
|
+
- **BREAKING**:minHost 前移至 `0.1.2-rc.1`;宿主 `0.1.0-rc.6` ~ `0.1.1-rc.2` 的用户请停留在 0.1.x 线(末版 `0.1.0`)
|
|
8
|
+
- 导入面迁移:client bundle 不再引用宿主已删除的 `dsh-client-runtime`(类型改自 `dsh-client-ui-settings` / `dsh-session` 等 0.1.2 导出面),`apply` 签名回到 cordis `Context`
|
|
9
|
+
- 设置注册改用裸命名空间(`settingsNamespace()` 已随宿主移除)
|
|
10
|
+
|
|
3
11
|
## 0.1.0(2026-08-22)
|
|
4
12
|
|
|
5
13
|
首个公开发布。
|
package/README.en.md
CHANGED
|
@@ -2,18 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
English | [中文](README.md)
|
|
4
4
|
|
|
5
|
-
A
|
|
5
|
+
A "compact now" button that shows up in the composer before your context runs out.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
The longer a conversation runs, the fuller the context gets — and at the far end the provider starts rejecting requests outright. That wall arrives earlier than the 100% the context ring suggests, because every request also reserves room for the output. This plugin's job is simple: once occupancy crosses the ratio you set, an amber button appears in the composer's toolbar, and clicking it is the same as typing `/compact` yourself. It never compacts for you — it just reminds you while you still can.
|
|
8
8
|
|
|
9
|
-
<img src="https://raw.githubusercontent.com/Khorsheed/dsh-web-basic/main/docs/screenshots/context-guard-
|
|
9
|
+
<img src="https://raw.githubusercontent.com/Khorsheed/dsh-web-basic/main/docs/screenshots/context-guard-button.png" width="640" alt="the compact button appears in the composer toolbar once context occupancy crosses the configured ratio">
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
- **Compact button in the composer** —
|
|
13
|
+
- **Compact button in the composer** — invisible below the threshold, appears automatically above it.
|
|
14
14
|
- **Same number as the context ring** — driven by the official `contextPressure` projection, so button and ring never disagree.
|
|
15
|
-
- **Runs the official `/compact`** — idle-gating, the compaction lock, and presentation stay host-owned.
|
|
16
|
-
- **One live tunable** — the
|
|
15
|
+
- **Runs the official `/compact`** — idle-gating, the compaction lock, and presentation stay host-owned; a click behaves exactly like the typed command.
|
|
16
|
+
- **One live tunable** — the reminder threshold, editable in settings with no restart.
|
|
17
|
+
|
|
18
|
+
<img src="https://raw.githubusercontent.com/Khorsheed/dsh-web-basic/main/docs/screenshots/context-guard-settings.png" width="640" alt="the trigger ratio is configurable in settings (0.01–1)">
|
|
17
19
|
|
|
18
20
|
## Install
|
|
19
21
|
|
|
@@ -21,7 +23,7 @@ A context-window compaction reminder for the dsh web GUI: once **context occupan
|
|
|
21
23
|
dsh plugin --profile web add @khorsheed/dsh-context-guard
|
|
22
24
|
```
|
|
23
25
|
|
|
24
|
-
Restart the web instance after adding.
|
|
26
|
+
Restart the web instance after adding. Uninstalling removes every surface it adds:
|
|
25
27
|
|
|
26
28
|
```sh
|
|
27
29
|
dsh plugin --profile web remove @khorsheed/dsh-context-guard
|
|
@@ -45,8 +47,10 @@ plugins:
|
|
|
45
47
|
|
|
46
48
|
## Compatibility
|
|
47
49
|
|
|
48
|
-
- npm release line (`@deepseek-ai/dsh@0.1.
|
|
49
|
-
- source line (deepseek-harness master): ✅
|
|
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
|
+
- source line (deepseek-harness master): ✅ (verifiedHost: 0.1.2-rc.1)
|
|
52
|
+
|
|
53
|
+
**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`).
|
|
50
54
|
|
|
51
55
|
## Known Limitations
|
|
52
56
|
|
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:
|
|
6
|
-
packages/context-guard/README.md:
|
|
5
|
+
packages/context-guard/README.en.md: 4c308d5e35b1ad8c0c9bc737e46ba5eed4a7278d
|
|
6
|
+
packages/context-guard/README.md: c66c57b80d707077daec308c31e9a1a8c56bd197
|
package/README.md
CHANGED
|
@@ -2,18 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.en.md) | 中文
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
上下文快撑爆之前,输入框里会冒出一枚「立即压缩」按钮。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
聊得越久,上下文占用越高,涨到头 provider 会直接拒收请求——这堵墙比进度环的 100% 来得更早(请求还要给输出留位置)。这个插件就是干这个的:占用一过你设的比例,输入框工具栏里就出现一枚琥珀色按钮,点一下等于亲手输入 `/compact`。它不替你自动压缩,只在你还来得及的时候提醒你。
|
|
8
8
|
|
|
9
|
-
<img src="https://raw.githubusercontent.com/Khorsheed/dsh-web-basic/main/docs/screenshots/context-guard-
|
|
9
|
+
<img src="https://raw.githubusercontent.com/Khorsheed/dsh-web-basic/main/docs/screenshots/context-guard-button.png" width="640" alt="上下文占用越过配置比例后,输入框工具栏里出现琥珀色的压缩按钮">
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## 功能
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
- **执行官方 `/compact
|
|
16
|
-
-
|
|
13
|
+
- **输入框里的压缩按钮**——低于阈值时看不见,越过后自动出现。
|
|
14
|
+
- **和进度环同一个数**——由官方 `contextPressure` 投影驱动,按钮和进度环永远不会打架。
|
|
15
|
+
- **执行官方 `/compact`**——空闲门控、压缩锁和流程展示都由宿主负责,点击的效果和手打命令一模一样。
|
|
16
|
+
- **一个实时可调项**——提醒阈值在设置里改,保存即生效,不用重启。
|
|
17
|
+
|
|
18
|
+
<img src="https://raw.githubusercontent.com/Khorsheed/dsh-web-basic/main/docs/screenshots/context-guard-settings.png" width="640" alt="提醒比例可在设置中按偏好调整(0.01–1)">
|
|
17
19
|
|
|
18
20
|
## 安装
|
|
19
21
|
|
|
@@ -21,7 +23,7 @@ dsh Web GUI 的上下文窗口压缩提醒:当**上下文占用**——与输
|
|
|
21
23
|
dsh plugin --profile web add @khorsheed/dsh-context-guard
|
|
22
24
|
```
|
|
23
25
|
|
|
24
|
-
安装后重启 web
|
|
26
|
+
安装后重启 web 实例。卸载即清除它添加的所有界面:
|
|
25
27
|
|
|
26
28
|
```sh
|
|
27
29
|
dsh plugin --profile web remove @khorsheed/dsh-context-guard
|
|
@@ -43,12 +45,14 @@ plugins:
|
|
|
43
45
|
|
|
44
46
|
**该字段只影响按钮出现的时机**——真实压缩时机仍由官方压缩引擎自己的 `thresholdRatio` / `auto` 配置决定。
|
|
45
47
|
|
|
46
|
-
##
|
|
48
|
+
## Compatibility
|
|
49
|
+
|
|
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
|
+
- 源码线(deepseek-harness master):✅(verifiedHost: 0.1.2-rc.1)
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
- source 线(deepseek-harness master):✅
|
|
53
|
+
**版本线对照**:0.2.0 起支持宿主 `0.1.2-rc.1` 及以后;宿主 `0.1.0-rc.6` ~ `0.1.1-rc.2` 的用户请停留在 0.1.x 发布线(末版 `0.1.0`)。
|
|
50
54
|
|
|
51
|
-
##
|
|
55
|
+
## Known Limitations
|
|
52
56
|
|
|
53
57
|
- **提醒,不是保证**——从按钮出现到点击之间上下文可能继续增长,agent 运行中 `/compact` 可能报 `busy`。
|
|
54
58
|
- **没有输出上限旋钮**——拒绝墙取决于 `window − maxTokens`,这是模型属性而非用户偏好。
|
package/lib/client.js
CHANGED
|
@@ -99,7 +99,7 @@ window.__ModuleLoader__.load({
|
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
101
|
//#endregion
|
|
102
|
-
//#region \0dsh-css
|
|
102
|
+
//#region \0dsh-css:src/client/CompactGuardButton.module.css.mjs
|
|
103
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}";
|
|
104
104
|
const tagId$1 = "@khorsheed/dsh-context-guard/CompactGuardButton.module.css";
|
|
105
105
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
@@ -110,11 +110,11 @@ window.__ModuleLoader__.load({
|
|
|
110
110
|
document.head.appendChild(tag);
|
|
111
111
|
}
|
|
112
112
|
var CompactGuardButton_module_css_default = {
|
|
113
|
-
"wrap": "BFFw2a_wrap",
|
|
114
|
-
"button": "BFFw2a_button",
|
|
115
|
-
"warning": "BFFw2a_warning",
|
|
116
113
|
"error": "BFFw2a_error",
|
|
117
|
-
"
|
|
114
|
+
"warning": "BFFw2a_warning",
|
|
115
|
+
"button": "BFFw2a_button",
|
|
116
|
+
"icon": "BFFw2a_icon",
|
|
117
|
+
"wrap": "BFFw2a_wrap"
|
|
118
118
|
};
|
|
119
119
|
//#endregion
|
|
120
120
|
//#region src/client/CompactGuardButton.tsx
|
|
@@ -181,7 +181,7 @@ window.__ModuleLoader__.load({
|
|
|
181
181
|
});
|
|
182
182
|
}
|
|
183
183
|
//#endregion
|
|
184
|
-
//#region \0dsh-css
|
|
184
|
+
//#region \0dsh-css:src/client/SettingsCard.module.css.mjs
|
|
185
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)}";
|
|
186
186
|
const tagId = "@khorsheed/dsh-context-guard/SettingsCard.module.css";
|
|
187
187
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
@@ -192,30 +192,30 @@ window.__ModuleLoader__.load({
|
|
|
192
192
|
document.head.appendChild(tag);
|
|
193
193
|
}
|
|
194
194
|
var SettingsCard_module_css_default = {
|
|
195
|
-
"hint": "DKjZXq_hint",
|
|
196
|
-
"cardOpen": "DKjZXq_cardOpen",
|
|
197
|
-
"save": "DKjZXq_save",
|
|
198
195
|
"secondary": "DKjZXq_secondary",
|
|
199
|
-
"description": "DKjZXq_description",
|
|
200
196
|
"fieldName": "DKjZXq_fieldName",
|
|
201
|
-
"
|
|
202
|
-
"
|
|
203
|
-
"inputInvalid": "DKjZXq_inputInvalid",
|
|
204
|
-
"name": "DKjZXq_name",
|
|
205
|
-
"chevron": "DKjZXq_chevron",
|
|
197
|
+
"badge": "DKjZXq_badge",
|
|
198
|
+
"field": "DKjZXq_field",
|
|
206
199
|
"chevronOpen": "DKjZXq_chevronOpen",
|
|
207
|
-
"
|
|
208
|
-
"
|
|
209
|
-
"saved": "DKjZXq_saved",
|
|
200
|
+
"input": "DKjZXq_input",
|
|
201
|
+
"footer": "DKjZXq_footer",
|
|
210
202
|
"body": "DKjZXq_body",
|
|
203
|
+
"saved": "DKjZXq_saved",
|
|
204
|
+
"hint": "DKjZXq_hint",
|
|
211
205
|
"headText": "DKjZXq_headText",
|
|
206
|
+
"cardOpen": "DKjZXq_cardOpen",
|
|
207
|
+
"header": "DKjZXq_header",
|
|
208
|
+
"description": "DKjZXq_description",
|
|
212
209
|
"inputRow": "DKjZXq_inputRow",
|
|
213
|
-
"
|
|
210
|
+
"save": "DKjZXq_save",
|
|
211
|
+
"status": "DKjZXq_status",
|
|
212
|
+
"name": "DKjZXq_name",
|
|
213
|
+
"chevron": "DKjZXq_chevron",
|
|
214
|
+
"card": "DKjZXq_card",
|
|
215
|
+
"fieldHead": "DKjZXq_fieldHead",
|
|
214
216
|
"reset": "DKjZXq_reset",
|
|
215
|
-
"badge": "DKjZXq_badge",
|
|
216
|
-
"footer": "DKjZXq_footer",
|
|
217
217
|
"errorText": "DKjZXq_errorText",
|
|
218
|
-
"
|
|
218
|
+
"inputInvalid": "DKjZXq_inputInvalid"
|
|
219
219
|
};
|
|
220
220
|
//#endregion
|
|
221
221
|
//#region src/client/SettingsCard.tsx
|
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["useMemo","useState","useRef","css","IconWarningOutline16","useState","useRef","css","IconChevronDownOutline14"],"sources":["../src/namespace.ts","../src/client/config.ts","../src/client/locales.ts","../src/client/guard.ts","../src/client/CompactGuardButton.tsx","../src/client/SettingsCard.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * The settings namespace owned by the context-guard plugin. Plain string so\n * both the host half (settings registration) and the browser half\n * (settingsScope binding and the settings.plugin.item card key) import it\n * without dragging host-only dependencies into the client bundle.\n */\n\n/** Settings namespace owned by the context-guard plugin. */\nexport const CONTEXT_GUARD_NS = 'context-guard'\n","/**\n * Browser-half configuration of the context-guard plugin. The runner hands\n * the plugin its validated entry config through the apply second parameter;\n * this module supplies the defaults and clamps, so a composition that does\n * not pass config still renders with the documented values and out-of-range\n * input lands inside the legal bounds.\n *\n * Once the settings surface is composed, these values arrive through the\n * `context-guard` settings section instead: the composition entry becomes the\n * section's base layer, and the settings card overrides it per field. The\n * fallback path here only serves deployments without the settings surface.\n */\n\n/**\n * Deployment-tunable guard behavior.\n *\n * The guard is a reminder: when the context OCCUPANCY (the same\n * `projectedTokens / contextWindow` number the composer's context ring shows)\n * crosses `thresholdRatio`, the compact button appears. The official\n * compaction engine auto-compacts at 80% of the window on its own; the real\n * provider rejection wall is `window − maxTokens` (the request reserves\n * output), so a request can fail before occupancy alone reaches 80% — set a\n * lower threshold for an earlier reminder. The field tunes ONLY when the\n * button appears; real compaction timing (compaction-basic's own config) is\n * never affected.\n */\nexport interface ContextGuardConfig {\n /**\n * Context occupancy fraction at which the button appears (clamped to\n * (0, 1]; default 0.8, matching compaction-basic's default pressure\n * threshold).\n */\n thresholdRatio: number\n}\n\n/** Bounds of the deployment-tunable number (clamped in resolveConfig). */\nconst THRESHOLD_RATIO_MIN = 0.01\nconst THRESHOLD_RATIO_MAX = 1\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value))\n}\n\n/**\n * Normalize the entry config into the full {@link ContextGuardConfig}: every\n * omitted field takes its documented default and every numeric field is\n * clamped into its legal range.\n * @param config - the unvalidated entry config, when the runner passes one.\n * @returns the effective guard behavior.\n */\nexport function resolveConfig(config: Partial<ContextGuardConfig> | undefined): ContextGuardConfig {\n return {\n thresholdRatio: clamp(\n config?.thresholdRatio ?? 0.8,\n THRESHOLD_RATIO_MIN,\n THRESHOLD_RATIO_MAX,\n ),\n }\n}\n","/** `context-guard` namespace dictionaries. */\n\n/** Simplified Chinese dictionary (the key-set source of truth). */\nexport const zh = {\n 'button.label': '压缩',\n 'button.aria': '上下文已用 {percent}%,点击压缩历史',\n 'button.error': '压缩未执行',\n 'settings.title': '压缩提醒时机',\n 'settings.description': '上下文占用达到设定比例时,输入框出现压缩按钮',\n 'settings.expand': '展开',\n 'settings.collapse': '收起',\n 'settings.field.threshold': '提醒比例',\n 'settings.field.threshold.hint': '上下文占用达到窗口的该比例时显示压缩按钮;可配置范围 0.01–1,按自己的偏好调整即可',\n 'settings.overridden': '已自定义',\n 'settings.reset': '重置',\n 'settings.save': '保存',\n 'settings.discard': '放弃',\n 'settings.saved': '已保存',\n 'settings.error': '保存失败,请重试',\n} satisfies Record<string, string>\n\n/** The context-guard namespace key union. */\nexport type ContextGuardKey = keyof typeof zh\n\n/** The dictionary namespace owned by this plugin. */\nexport const NS = 'context-guard'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** The context-guard compact button's copy. */\n 'context-guard': ContextGuardKey\n }\n}\n\n/** English dictionary, checked complete against the zh key set. */\nexport const en = {\n 'button.label': 'compact',\n 'button.aria': 'Context at {percent}%, click to compact history',\n 'button.error': 'Compaction was not executed',\n 'settings.title': 'Compaction reminder timing',\n 'settings.description': 'Shows the compact button in the composer once context occupancy reaches the configured fraction',\n 'settings.expand': 'Expand',\n 'settings.collapse': 'Collapse',\n 'settings.field.threshold': 'Reminder ratio',\n 'settings.field.threshold.hint': 'Show the compact button once context occupancy reaches this fraction of the window; configurable from 0.01 to 1 — tune it to your preference',\n 'settings.overridden': 'Overridden',\n 'settings.reset': 'Reset',\n 'settings.save': 'Save',\n 'settings.discard': 'Discard',\n 'settings.saved': 'Saved',\n 'settings.error': 'Save failed, please retry',\n} satisfies Record<string, string>\n","/**\n * Pure guard decision math: whether the next request's prompt-plus-output\n * budget is about to overflow the model's context window, mirroring the\n * provider-side rejection rule (`prompt + max_tokens > context_length`).\n *\n * The numerator rides the official `contextPressure` projection so it answers\n * for the NEXT request rather than the last one: `projectedTokens` is the\n * provider-reported prompt sample carried forward over the surface's signed\n * movement since (compaction included), with the bare sample as the fallback\n * for logs whose projection predates that field. The occupancy is the SAME\n * number the composer's context ring shows — the guard is a reminder on top\n * of that ring, not a second figure.\n */\n\n/** The three guard states: nothing to show, about to overflow, already over. */\nexport type GuardLevel = 'ok' | 'warning' | 'overdue'\n\n/** Inputs to the guard decision; both come from the session's contextPressure projection plus config. */\nexport interface GuardInput {\n /** Current projected context tokens (projectedTokens ?? pressureTokens). */\n projectedTokens: number\n /** The routed model's advertised context window; absent until a route reports one. */\n contextWindow: number | undefined\n /** The occupancy fraction at which the guard turns on (clamped (0, 1] by config). */\n thresholdRatio: number\n}\n\n/** One guard reading: the level plus the occupancy figure behind it. */\nexport interface GuardReading {\n level: GuardLevel\n /** `projectedTokens / contextWindow`, rounded, clamped to 0–100 — the same occupancy the context ring shows. */\n percent: number\n /** The routed model's advertised context window. */\n contextWindow: number\n}\n\n/**\n * Decide the guard level for one request budget.\n *\n * `overdue` means the occupancy already reached the whole window — the\n * provider will have rejected requests before this point, so it is a\n * defensive floor rather than a reachable state.\n *\n * @param input - the projection figures plus config.\n * @returns the reading, or null when either the window or the context is unknown.\n */\nexport function guardReading(input: GuardInput): GuardReading | null {\n const { contextWindow, projectedTokens, thresholdRatio } = input\n if (contextWindow === undefined || !Number.isFinite(contextWindow) || contextWindow <= 0) {\n return null\n }\n if (!Number.isFinite(projectedTokens) || projectedTokens < 0) return null\n const ratio = projectedTokens / contextWindow\n const level: GuardLevel = ratio >= 1\n ? 'overdue'\n : ratio >= thresholdRatio\n ? 'warning'\n : 'ok'\n return {\n level,\n percent: Math.min(100, Math.round(ratio * 100)),\n contextWindow,\n }\n}\n","import { useEffect, useMemo, useRef, useState } from 'react'\nimport { IconWarningOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'\n// Type-only: pulls ui-conversation's SlotMap merge (the input.right seat and\n// its InputZone owner share).\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\n// Type-only: pulls the `contextPressure` SessionProjectionMap merge for\n// useProjection.\nimport type {} from '@deepseek-ai/dsh-token-meter/client'\nimport { guardReading } from './guard.ts'\nimport type { CompactGuardButtonProps } from './slots.ts'\nimport css from './CompactGuardButton.module.css'\n\nexport type { CompactGuardButtonProps } from './slots.ts'\n\n/**\n * Composer-tool-row compact button. Renders nothing until the context\n * occupancy (`projectedTokens / contextWindow` — the same number the\n * composer's context ring shows) crosses the threshold; then it appears\n * automatically in the amber warning tint. The threshold comes from the\n * shared `context-guard` settings section (live), falling back to the\n * composition-time value while the settings surface is absent. Clicking\n * executes the official `/compact` command through the injected face.\n */\nexport function CompactGuardButton({\n useProjection,\n useConfig,\n t,\n thresholdRatio,\n compactNow,\n}: CompactGuardButtonProps) {\n const pressure = useProjection('contextPressure')\n const config = useConfig(value => value)\n const effective = config.status === 'ready' && config.value !== undefined\n ? config.value\n : { thresholdRatio }\n const reading = useMemo(() => guardReading({\n projectedTokens: pressure?.projectedTokens ?? pressure?.pressureTokens ?? Number.NaN,\n contextWindow: pressure?.contextWindow,\n thresholdRatio: effective.thresholdRatio,\n }), [pressure, effective.thresholdRatio])\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const aliveRef = useRef(true)\n\n useEffect(() => {\n aliveRef.current = true\n return () => {\n aliveRef.current = false\n }\n }, [])\n\n if (reading === null || reading.level === 'ok') return null\n\n const run = (): void => {\n setBusy(true)\n setError(null)\n void compactNow().then((failure) => {\n if (!aliveRef.current) return\n setBusy(false)\n setError(failure)\n }, (reason: unknown) => {\n if (!aliveRef.current) return\n setBusy(false)\n setError(reason instanceof Error ? reason.message : String(reason))\n })\n }\n\n return (\n <span className={css.wrap}>\n <button\n type=\"button\"\n className={`${css.button} ${css.warning}`}\n aria-label={t('button.aria', { percent: String(reading.percent) })}\n disabled={busy}\n onClick={run}\n >\n <span className={css.icon} aria-hidden>\n <IconWarningOutline16 size={14} />\n </span>\n {t('button.label')}\n </button>\n {/* Failure copy stays English (error-surface policy: not localized). */}\n {error !== null && (\n <span className={css.error} role=\"status\" title={error}>\n {t('button.error')}\n </span>\n )}\n </span>\n )\n}\n","import { useEffect, useRef, useState } from 'react'\nimport { IconChevronDownOutline14 } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { SettingsScopeSnapshot } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { ContextGuardConfig } from './config.ts'\nimport type { ContextGuardSettingsCardProps } from './slots.ts'\nimport css from './SettingsCard.module.css'\n\n/** One number field's staged draft, its validity, and the reset/save actions. */\ninterface FieldDraft {\n text: string\n invalid: boolean\n}\n\n/** Parse a draft into a number, or NaN when it is not one. */\nfunction parseNumber(text: string): number {\n const value = Number(text.trim())\n return Number.isFinite(value) ? value : Number.NaN\n}\n\n/** Whether the user layer carries a field (presence marks an override). */\nfunction overridden(snapshot: SettingsScopeSnapshot<ContextGuardConfig>, field: keyof ContextGuardConfig): boolean {\n return snapshot.user !== undefined\n && typeof snapshot.user === 'object'\n && snapshot.user !== null\n && field in snapshot.user\n}\n\n/**\n * The context-guard settings card in the plugin configuration tab: the same\n * collapsible chrome the official plugin cards use (a header naming the\n * plugin and what its settings govern, disclosing the controls in place),\n * re-implemented here because the client bundle-purity gate forbids\n * value-importing the official card chrome. Two staged number fields write\n * through the shared settingsScope on save; per-field reset reverts a field\n * to the composition layer.\n */\nexport function ContextGuardSettingsCard({ useConfig, scope, t }: ContextGuardSettingsCardProps) {\n const snapshot = useConfig(value => value)\n const value = snapshot.status === 'ready' ? snapshot.value : undefined\n const [open, setOpen] = useState(false)\n const [threshold, setThreshold] = useState<FieldDraft>({ text: '', invalid: false })\n const [saved, setSaved] = useState(false)\n const [error, setError] = useState(false)\n const aliveRef = useRef(true)\n /** The section revision the drafts were last seeded from; re-seed only on a committed change. */\n const seededRevision = useRef<number | null>(null)\n\n // Re-seed the drafts whenever the section VALUE changes (first load, a save\n // settling, an external reset). Keyed on the snapshot revision rather than\n // the value's object identity: a save settling publishes a new snapshot\n // (revision bump), while an unrelated re-render must not discard a draft in\n // flight — the staged model promises the screen shows exactly what a save\n // would store.\n useEffect(() => {\n aliveRef.current = true\n if (value === undefined) return\n if (seededRevision.current === snapshot.revision) return\n seededRevision.current = snapshot.revision ?? null\n setThreshold({ text: String(value.thresholdRatio), invalid: false })\n setSaved(false)\n setError(false)\n return () => {\n aliveRef.current = false\n }\n }, [value, snapshot.revision])\n\n if (snapshot.status === 'unavailable') return null\n\n const write = (draft: FieldDraft, min: number, max: number): void => {\n const parsed = parseNumber(draft.text)\n setThreshold({ text: draft.text, invalid: !Number.isFinite(parsed) || parsed < min || parsed > max })\n }\n\n const save = (): void => {\n if (threshold.invalid || snapshot.writable !== true) return\n const ratio = parseNumber(threshold.text)\n if (!Number.isFinite(ratio)) return\n setError(false)\n setSaved(false)\n void scope.set('thresholdRatio', ratio).then(() => {\n if (!aliveRef.current) return\n setSaved(true)\n }, () => {\n if (!aliveRef.current) return\n setError(true)\n })\n }\n\n const reset = (): void => {\n setError(false)\n setSaved(false)\n void scope.unset('thresholdRatio').catch(() => {\n if (aliveRef.current) setError(true)\n })\n }\n\n const title = t('settings.title')\n const dirty = threshold.text !== String(value?.thresholdRatio ?? '')\n\n return (\n <li className={open ? `${css.card} ${css.cardOpen}` : css.card}>\n <button\n type=\"button\"\n className={css.header}\n aria-expanded={open}\n aria-label={`${t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`}\n onClick={() => { setOpen(!open) }}\n >\n <span className={css.headText}>\n <span className={css.name}>{title}</span>\n <span className={css.description}>{t('settings.description')}</span>\n </span>\n <IconChevronDownOutline14 className={open ? `${css.chevron} ${css.chevronOpen}` : css.chevron} />\n </button>\n {open && value !== undefined && (\n <div className={css.body}>\n <label className={css.field}>\n <span className={css.fieldHead}>\n <span className={css.fieldName}>{t('settings.field.threshold')}</span>\n {overridden(snapshot, 'thresholdRatio') && <span className={css.badge}>{t('settings.overridden')}</span>}\n </span>\n <span className={css.inputRow}>\n <input\n className={threshold.invalid ? `${css.input} ${css.inputInvalid}` : css.input}\n type=\"number\"\n step=\"0.01\"\n min=\"0.01\"\n max=\"1\"\n value={threshold.text}\n aria-label={t('settings.field.threshold')}\n onChange={(event) => { write({ text: event.target.value, invalid: false }, 0.01, 1) }}\n />\n <button type=\"button\" className={css.reset} onClick={reset}>\n {t('settings.reset')}\n </button>\n </span>\n <span className={css.hint}>{t('settings.field.threshold.hint')}</span>\n </label>\n <span className={css.footer}>\n <span className={css.status}>\n {saved ? <span className={css.saved}>{t('settings.saved')}</span> : null}\n {error ? <span className={css.errorText}>{t('settings.error')}</span> : null}\n </span>\n <button\n type=\"button\"\n className={css.secondary}\n disabled={!dirty}\n onClick={() => {\n if (value === undefined) return\n setThreshold({ text: String(value.thresholdRatio), invalid: false })\n setSaved(false)\n setError(false)\n }}\n >\n {t('settings.discard')}\n </button>\n <button\n type=\"button\"\n className={css.save}\n disabled={!dirty || threshold.invalid || snapshot.writable !== true}\n onClick={save}\n >\n {t('settings.save')}\n </button>\n </span>\n </div>\n )}\n </li>\n )\n}\n","/**\n * Context-guard plugin, browser half: contributes one\n * `conversation.input.right` entry — a compact button inside the composer's\n * tool row that appears automatically when the next request's budget\n * (`contextPressure.projectedTokens` + the output budget) crosses\n * `thresholdRatio` of the routed model's context window — plus one\n * `settings.plugin.item` card in the plugin configuration tab that edits the\n * same two numbers live. The action rides the official `/compact` command\n * channel (`remote.commands.execute` → host `ctx.commands` →\n * `ctx.compaction.compactNow`); the config rides the official settings\n * surface (host half registers the namespace, this half binds its\n * `settingsScope`), so neither needs a new RPC or any edit to core packages;\n * composing this plugin out of cordis.yml removes every surface it adds.\n *\n * Why this exists: the official auto-compaction fires at `agent/pre-step`\n * when the meter's context estimate crosses 80% of the window — a check that\n * excludes the request's own output budget and rides a heuristic that\n * deliberately underprices CJK/JSON. A long request can therefore push\n * context + maxTokens past the window while the estimate still sits below\n * 80%, so the provider rejects the request (CONTEXT_WINDOW_EXCEEDED). The\n * guard surfaces the danger while a manual compaction still fits — the\n * summarization call reserves only its own small output cap, so it keeps\n * running after the main request is rejected.\n * @module @khorsheed/dsh-context-guard/client\n */\nimport type { ClientContext, SessionId } from '@deepseek-ai/dsh-client-runtime/client'\n// Type-only: pulls the typed `ctx.remote` (commands namespace) merge.\nimport type {} from '@deepseek-ai/dsh-api-remotes/client'\n// Type-only: pulls the ctx.locale service merge.\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: pulls ui-conversation's SlotMap merge\n// ('conversation.input.right').\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\n// Type-only: pulls the ctx.settingsScope service merge.\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\n// Type-only: pulls ui-settings-plugins' SlotMap merge\n// ('settings.plugin.item').\nimport type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'\n// Type-only: pulls the `contextPressure` SessionProjectionMap merge for\n// useProjection.\nimport type {} from '@deepseek-ai/dsh-token-meter/client'\nimport { CONTEXT_GUARD_NS } from '../namespace.ts'\nimport { resolveConfig, type ContextGuardConfig } from './config.ts'\nimport { en, NS, zh } from './locales.ts'\nimport type { ContextGuardInjected, ContextGuardSettingsCardInjected } from './slots.ts'\nimport { CompactGuardButton } from './CompactGuardButton.tsx'\nimport { ContextGuardSettingsCard } from './SettingsCard.tsx'\n\nexport type { ContextGuardConfig } from './config.ts'\nexport { resolveConfig } from './config.ts'\nexport type { ContextGuardKey } from './locales.ts'\nexport { guardReading } from './guard.ts'\nexport type { GuardInput, GuardLevel, GuardReading } from './guard.ts'\nexport type {\n CompactGuardButtonProps, ContextGuardInjected, ContextGuardSettingsCardInjected, ContextGuardSettingsCardProps,\n} from './slots.ts'\n\n/** Dictionary namespace owned by this plugin. */\nexport { NS }\n\n/** Required services: the slot ledger, the command Remote, the settings scope, and the copy. */\nexport const inject = ['slots', 'remote', 'remote.commands', 'locale', 'settingsScope']\n\n/**\n * Client plugin body: register the composer-tool-row compact button and the\n * settings card over the shared `context-guard` section.\n * @param ctx - client root context.\n * @param config - entry config (the section's composition base layer); defaults apply when the runner passes none.\n */\nexport function apply(ctx: ClientContext, config?: Partial<ContextGuardConfig>): void {\n const fallback = resolveConfig(config)\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'context-guard: dictionaries')\n\n // One shared live section: the settings card writes it, the button reads\n // it. While the settings surface is absent, the button falls back to the\n // composition-time values above.\n const scope = ctx.settingsScope.bind<ContextGuardConfig>({ namespace: CONTEXT_GUARD_NS })\n\n // The slot is declared by ui-conversation, whose apply order relative to\n // this plugin is unconstrained: register through slots.inject so the entry\n // waits for the declaration instead of crashing the loader at boot.\n ctx.slots.inject('conversation.input.right', () => ctx.slots.register({\n name: 'conversation.input.right',\n id: 'context-guard',\n // Sit before the primary send button; the seat currently has no other\n // occupants, and a leading position keeps the danger visible.\n order: -10,\n locale: NS,\n inject: (sessionId: SessionId): ContextGuardInjected => ({\n thresholdRatio: fallback.thresholdRatio,\n // Failure strings stay English (error-surface policy: not localized).\n compactNow: async () => {\n const result = await ctx.remote.commands.execute(sessionId, '/compact', [])\n if (!result.ok) return `${result.error.message} (${result.error.code})`\n if (result.value === undefined) return 'unknown command: /compact'\n return null\n },\n hooks: { config: scope },\n }),\n }, CompactGuardButton))\n\n // The plugin configuration tab keys its cards on the settings namespace, so\n // the compact-timing card registers under CONTEXT_GUARD_NS and renders\n // wherever the tab dispatches that key.\n ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({\n name: 'settings.plugin.item',\n key: CONTEXT_GUARD_NS,\n locale: NS,\n inject: (): ContextGuardSettingsCardInjected => ({\n scope,\n hooks: { config: scope },\n }),\n }, ContextGuardSettingsCard))\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["useMemo","useState","useRef","css","IconWarningOutline16","useState","useRef","css","IconChevronDownOutline14"],"sources":["../src/namespace.ts","../src/client/config.ts","../src/client/locales.ts","../src/client/guard.ts","../src/client/CompactGuardButton.tsx","../src/client/SettingsCard.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * The settings namespace owned by the context-guard plugin. Plain string so\n * both the host half (settings registration) and the browser half\n * (settingsScope binding and the settings.plugin.item card key) import it\n * without dragging host-only dependencies into the client bundle.\n */\n\n/** Settings namespace owned by the context-guard plugin. */\nexport const CONTEXT_GUARD_NS = 'context-guard'\n","/**\n * Browser-half configuration of the context-guard plugin. The runner hands\n * the plugin its validated entry config through the apply second parameter;\n * this module supplies the defaults and clamps, so a composition that does\n * not pass config still renders with the documented values and out-of-range\n * input lands inside the legal bounds.\n *\n * Once the settings surface is composed, these values arrive through the\n * `context-guard` settings section instead: the composition entry becomes the\n * section's base layer, and the settings card overrides it per field. The\n * fallback path here only serves deployments without the settings surface.\n */\n\n/**\n * Deployment-tunable guard behavior.\n *\n * The guard is a reminder: when the context OCCUPANCY (the same\n * `projectedTokens / contextWindow` number the composer's context ring shows)\n * crosses `thresholdRatio`, the compact button appears. The official\n * compaction engine auto-compacts at 80% of the window on its own; the real\n * provider rejection wall is `window − maxTokens` (the request reserves\n * output), so a request can fail before occupancy alone reaches 80% — set a\n * lower threshold for an earlier reminder. The field tunes ONLY when the\n * button appears; real compaction timing (compaction-basic's own config) is\n * never affected.\n */\nexport interface ContextGuardConfig {\n /**\n * Context occupancy fraction at which the button appears (clamped to\n * (0, 1]; default 0.8, matching compaction-basic's default pressure\n * threshold).\n */\n thresholdRatio: number\n}\n\n/** Bounds of the deployment-tunable number (clamped in resolveConfig). */\nconst THRESHOLD_RATIO_MIN = 0.01\nconst THRESHOLD_RATIO_MAX = 1\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value))\n}\n\n/**\n * Normalize the entry config into the full {@link ContextGuardConfig}: every\n * omitted field takes its documented default and every numeric field is\n * clamped into its legal range.\n * @param config - the unvalidated entry config, when the runner passes one.\n * @returns the effective guard behavior.\n */\nexport function resolveConfig(config: Partial<ContextGuardConfig> | undefined): ContextGuardConfig {\n return {\n thresholdRatio: clamp(\n config?.thresholdRatio ?? 0.8,\n THRESHOLD_RATIO_MIN,\n THRESHOLD_RATIO_MAX,\n ),\n }\n}\n","/** `context-guard` namespace dictionaries. */\n\n/** Simplified Chinese dictionary (the key-set source of truth). */\nexport const zh = {\n 'button.label': '压缩',\n 'button.aria': '上下文已用 {percent}%,点击压缩历史',\n 'button.error': '压缩未执行',\n 'settings.title': '压缩提醒时机',\n 'settings.description': '上下文占用达到设定比例时,输入框出现压缩按钮',\n 'settings.expand': '展开',\n 'settings.collapse': '收起',\n 'settings.field.threshold': '提醒比例',\n 'settings.field.threshold.hint': '上下文占用达到窗口的该比例时显示压缩按钮;可配置范围 0.01–1,按自己的偏好调整即可',\n 'settings.overridden': '已自定义',\n 'settings.reset': '重置',\n 'settings.save': '保存',\n 'settings.discard': '放弃',\n 'settings.saved': '已保存',\n 'settings.error': '保存失败,请重试',\n} satisfies Record<string, string>\n\n/** The context-guard namespace key union. */\nexport type ContextGuardKey = keyof typeof zh\n\n/** The dictionary namespace owned by this plugin. */\nexport const NS = 'context-guard'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** The context-guard compact button's copy. */\n 'context-guard': ContextGuardKey\n }\n}\n\n/** English dictionary, checked complete against the zh key set. */\nexport const en = {\n 'button.label': 'compact',\n 'button.aria': 'Context at {percent}%, click to compact history',\n 'button.error': 'Compaction was not executed',\n 'settings.title': 'Compaction reminder timing',\n 'settings.description': 'Shows the compact button in the composer once context occupancy reaches the configured fraction',\n 'settings.expand': 'Expand',\n 'settings.collapse': 'Collapse',\n 'settings.field.threshold': 'Reminder ratio',\n 'settings.field.threshold.hint': 'Show the compact button once context occupancy reaches this fraction of the window; configurable from 0.01 to 1 — tune it to your preference',\n 'settings.overridden': 'Overridden',\n 'settings.reset': 'Reset',\n 'settings.save': 'Save',\n 'settings.discard': 'Discard',\n 'settings.saved': 'Saved',\n 'settings.error': 'Save failed, please retry',\n} satisfies Record<string, string>\n","/**\n * Pure guard decision math: whether the next request's prompt-plus-output\n * budget is about to overflow the model's context window, mirroring the\n * provider-side rejection rule (`prompt + max_tokens > context_length`).\n *\n * The numerator rides the official `contextPressure` projection so it answers\n * for the NEXT request rather than the last one: `projectedTokens` is the\n * provider-reported prompt sample carried forward over the surface's signed\n * movement since (compaction included), with the bare sample as the fallback\n * for logs whose projection predates that field. The occupancy is the SAME\n * number the composer's context ring shows — the guard is a reminder on top\n * of that ring, not a second figure.\n */\n\n/** The three guard states: nothing to show, about to overflow, already over. */\nexport type GuardLevel = 'ok' | 'warning' | 'overdue'\n\n/** Inputs to the guard decision; both come from the session's contextPressure projection plus config. */\nexport interface GuardInput {\n /** Current projected context tokens (projectedTokens ?? pressureTokens). */\n projectedTokens: number\n /** The routed model's advertised context window; absent until a route reports one. */\n contextWindow: number | undefined\n /** The occupancy fraction at which the guard turns on (clamped (0, 1] by config). */\n thresholdRatio: number\n}\n\n/** One guard reading: the level plus the occupancy figure behind it. */\nexport interface GuardReading {\n level: GuardLevel\n /** `projectedTokens / contextWindow`, rounded, clamped to 0–100 — the same occupancy the context ring shows. */\n percent: number\n /** The routed model's advertised context window. */\n contextWindow: number\n}\n\n/**\n * Decide the guard level for one request budget.\n *\n * `overdue` means the occupancy already reached the whole window — the\n * provider will have rejected requests before this point, so it is a\n * defensive floor rather than a reachable state.\n *\n * @param input - the projection figures plus config.\n * @returns the reading, or null when either the window or the context is unknown.\n */\nexport function guardReading(input: GuardInput): GuardReading | null {\n const { contextWindow, projectedTokens, thresholdRatio } = input\n if (contextWindow === undefined || !Number.isFinite(contextWindow) || contextWindow <= 0) {\n return null\n }\n if (!Number.isFinite(projectedTokens) || projectedTokens < 0) return null\n const ratio = projectedTokens / contextWindow\n const level: GuardLevel = ratio >= 1\n ? 'overdue'\n : ratio >= thresholdRatio\n ? 'warning'\n : 'ok'\n return {\n level,\n percent: Math.min(100, Math.round(ratio * 100)),\n contextWindow,\n }\n}\n","import { useEffect, useMemo, useRef, useState } from 'react'\nimport { IconWarningOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'\n// Type-only: pulls ui-conversation's SlotMap merge (the input.right seat and\n// its InputZone owner share).\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\n// Type-only: pulls the `contextPressure` SessionProjectionMap merge for\n// useProjection.\nimport type {} from '@deepseek-ai/dsh-token-meter/client'\nimport { guardReading } from './guard.ts'\nimport type { CompactGuardButtonProps } from './slots.ts'\nimport css from './CompactGuardButton.module.css'\n\nexport type { CompactGuardButtonProps } from './slots.ts'\n\n/**\n * Composer-tool-row compact button. Renders nothing until the context\n * occupancy (`projectedTokens / contextWindow` — the same number the\n * composer's context ring shows) crosses the threshold; then it appears\n * automatically in the amber warning tint. The threshold comes from the\n * shared `context-guard` settings section (live), falling back to the\n * composition-time value while the settings surface is absent. Clicking\n * executes the official `/compact` command through the injected face.\n */\nexport function CompactGuardButton({\n useProjection,\n useConfig,\n t,\n thresholdRatio,\n compactNow,\n}: CompactGuardButtonProps) {\n const pressure = useProjection('contextPressure')\n const config = useConfig(value => value)\n const effective = config.status === 'ready' && config.value !== undefined\n ? config.value\n : { thresholdRatio }\n const reading = useMemo(() => guardReading({\n projectedTokens: pressure?.projectedTokens ?? pressure?.pressureTokens ?? Number.NaN,\n contextWindow: pressure?.contextWindow,\n thresholdRatio: effective.thresholdRatio,\n }), [pressure, effective.thresholdRatio])\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const aliveRef = useRef(true)\n\n useEffect(() => {\n aliveRef.current = true\n return () => {\n aliveRef.current = false\n }\n }, [])\n\n if (reading === null || reading.level === 'ok') return null\n\n const run = (): void => {\n setBusy(true)\n setError(null)\n void compactNow().then((failure) => {\n if (!aliveRef.current) return\n setBusy(false)\n setError(failure)\n }, (reason: unknown) => {\n if (!aliveRef.current) return\n setBusy(false)\n setError(reason instanceof Error ? reason.message : String(reason))\n })\n }\n\n return (\n <span className={css.wrap}>\n <button\n type=\"button\"\n className={`${css.button} ${css.warning}`}\n aria-label={t('button.aria', { percent: String(reading.percent) })}\n disabled={busy}\n onClick={run}\n >\n <span className={css.icon} aria-hidden>\n <IconWarningOutline16 size={14} />\n </span>\n {t('button.label')}\n </button>\n {/* Failure copy stays English (error-surface policy: not localized). */}\n {error !== null && (\n <span className={css.error} role=\"status\" title={error}>\n {t('button.error')}\n </span>\n )}\n </span>\n )\n}\n","import { useEffect, useRef, useState } from 'react'\nimport { IconChevronDownOutline14 } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { SettingsScopeSnapshot } from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type { ContextGuardConfig } from './config.ts'\nimport type { ContextGuardSettingsCardProps } from './slots.ts'\nimport css from './SettingsCard.module.css'\n\n/** One number field's staged draft, its validity, and the reset/save actions. */\ninterface FieldDraft {\n text: string\n invalid: boolean\n}\n\n/** Parse a draft into a number, or NaN when it is not one. */\nfunction parseNumber(text: string): number {\n const value = Number(text.trim())\n return Number.isFinite(value) ? value : Number.NaN\n}\n\n/** Whether the user layer carries a field (presence marks an override). */\nfunction overridden(snapshot: SettingsScopeSnapshot<ContextGuardConfig>, field: keyof ContextGuardConfig): boolean {\n return snapshot.user !== undefined\n && typeof snapshot.user === 'object'\n && snapshot.user !== null\n && field in snapshot.user\n}\n\n/**\n * The context-guard settings card in the plugin configuration tab: the same\n * collapsible chrome the official plugin cards use (a header naming the\n * plugin and what its settings govern, disclosing the controls in place),\n * re-implemented here because the client bundle-purity gate forbids\n * value-importing the official card chrome. Two staged number fields write\n * through the shared settingsScope on save; per-field reset reverts a field\n * to the composition layer.\n */\nexport function ContextGuardSettingsCard({ useConfig, scope, t }: ContextGuardSettingsCardProps) {\n const snapshot = useConfig(value => value)\n const value = snapshot.status === 'ready' ? snapshot.value : undefined\n const [open, setOpen] = useState(false)\n const [threshold, setThreshold] = useState<FieldDraft>({ text: '', invalid: false })\n const [saved, setSaved] = useState(false)\n const [error, setError] = useState(false)\n const aliveRef = useRef(true)\n /** The section revision the drafts were last seeded from; re-seed only on a committed change. */\n const seededRevision = useRef<number | null>(null)\n\n // Re-seed the drafts whenever the section VALUE changes (first load, a save\n // settling, an external reset). Keyed on the snapshot revision rather than\n // the value's object identity: a save settling publishes a new snapshot\n // (revision bump), while an unrelated re-render must not discard a draft in\n // flight — the staged model promises the screen shows exactly what a save\n // would store.\n useEffect(() => {\n aliveRef.current = true\n if (value === undefined) return\n if (seededRevision.current === snapshot.revision) return\n seededRevision.current = snapshot.revision ?? null\n setThreshold({ text: String(value.thresholdRatio), invalid: false })\n setSaved(false)\n setError(false)\n return () => {\n aliveRef.current = false\n }\n }, [value, snapshot.revision])\n\n if (snapshot.status === 'unavailable') return null\n\n const write = (draft: FieldDraft, min: number, max: number): void => {\n const parsed = parseNumber(draft.text)\n setThreshold({ text: draft.text, invalid: !Number.isFinite(parsed) || parsed < min || parsed > max })\n }\n\n const save = (): void => {\n if (threshold.invalid || snapshot.writable !== true) return\n const ratio = parseNumber(threshold.text)\n if (!Number.isFinite(ratio)) return\n setError(false)\n setSaved(false)\n void scope.set('thresholdRatio', ratio).then(() => {\n if (!aliveRef.current) return\n setSaved(true)\n }, () => {\n if (!aliveRef.current) return\n setError(true)\n })\n }\n\n const reset = (): void => {\n setError(false)\n setSaved(false)\n void scope.unset('thresholdRatio').catch(() => {\n if (aliveRef.current) setError(true)\n })\n }\n\n const title = t('settings.title')\n const dirty = threshold.text !== String(value?.thresholdRatio ?? '')\n\n return (\n <li className={open ? `${css.card} ${css.cardOpen}` : css.card}>\n <button\n type=\"button\"\n className={css.header}\n aria-expanded={open}\n aria-label={`${t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`}\n onClick={() => { setOpen(!open) }}\n >\n <span className={css.headText}>\n <span className={css.name}>{title}</span>\n <span className={css.description}>{t('settings.description')}</span>\n </span>\n <IconChevronDownOutline14 className={open ? `${css.chevron} ${css.chevronOpen}` : css.chevron} />\n </button>\n {open && value !== undefined && (\n <div className={css.body}>\n <label className={css.field}>\n <span className={css.fieldHead}>\n <span className={css.fieldName}>{t('settings.field.threshold')}</span>\n {overridden(snapshot, 'thresholdRatio') && <span className={css.badge}>{t('settings.overridden')}</span>}\n </span>\n <span className={css.inputRow}>\n <input\n className={threshold.invalid ? `${css.input} ${css.inputInvalid}` : css.input}\n type=\"number\"\n step=\"0.01\"\n min=\"0.01\"\n max=\"1\"\n value={threshold.text}\n aria-label={t('settings.field.threshold')}\n onChange={(event) => { write({ text: event.target.value, invalid: false }, 0.01, 1) }}\n />\n <button type=\"button\" className={css.reset} onClick={reset}>\n {t('settings.reset')}\n </button>\n </span>\n <span className={css.hint}>{t('settings.field.threshold.hint')}</span>\n </label>\n <span className={css.footer}>\n <span className={css.status}>\n {saved ? <span className={css.saved}>{t('settings.saved')}</span> : null}\n {error ? <span className={css.errorText}>{t('settings.error')}</span> : null}\n </span>\n <button\n type=\"button\"\n className={css.secondary}\n disabled={!dirty}\n onClick={() => {\n if (value === undefined) return\n setThreshold({ text: String(value.thresholdRatio), invalid: false })\n setSaved(false)\n setError(false)\n }}\n >\n {t('settings.discard')}\n </button>\n <button\n type=\"button\"\n className={css.save}\n disabled={!dirty || threshold.invalid || snapshot.writable !== true}\n onClick={save}\n >\n {t('settings.save')}\n </button>\n </span>\n </div>\n )}\n </li>\n )\n}\n","/**\n * Context-guard plugin, browser half: contributes one\n * `conversation.input.right` entry — a compact button inside the composer's\n * tool row that appears automatically when the next request's budget\n * (`contextPressure.projectedTokens` + the output budget) crosses\n * `thresholdRatio` of the routed model's context window — plus one\n * `settings.plugin.item` card in the plugin configuration tab that edits the\n * same two numbers live. The action rides the official `/compact` command\n * channel (`remote.commands.execute` → host `ctx.commands` →\n * `ctx.compaction.compactNow`); the config rides the official settings\n * surface (host half registers the namespace, this half binds its\n * `settingsScope`), so neither needs a new RPC or any edit to core packages;\n * composing this plugin out of cordis.yml removes every surface it adds.\n *\n * Why this exists: the official auto-compaction fires at `agent/pre-step`\n * when the meter's context estimate crosses 80% of the window — a check that\n * excludes the request's own output budget and rides a heuristic that\n * deliberately underprices CJK/JSON. A long request can therefore push\n * context + maxTokens past the window while the estimate still sits below\n * 80%, so the provider rejects the request (CONTEXT_WINDOW_EXCEEDED). The\n * guard surfaces the danger while a manual compaction still fits — the\n * summarization call reserves only its own small output cap, so it keeps\n * running after the main request is rejected.\n * @module @khorsheed/dsh-context-guard/client\n */\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { SessionId } from '@deepseek-ai/dsh-session/types'\n// Type-only: pulls the typed `ctx.remote` (commands namespace) merge.\nimport type {} from '@deepseek-ai/dsh-api-remotes/client'\n// Type-only: pulls the ctx.locale service merge.\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: pulls the ctx.slots service merge.\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\n// Type-only: pulls ui-conversation's SlotMap merge\n// ('conversation.input.right').\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\n// Type-only: pulls the ctx.settingsScope service merge.\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\n// Type-only: pulls ui-settings-plugins' SlotMap merge\n// ('settings.plugin.item').\nimport type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'\n// Type-only: pulls the `contextPressure` SessionProjectionMap merge for\n// useProjection.\nimport type {} from '@deepseek-ai/dsh-token-meter/client'\nimport { CONTEXT_GUARD_NS } from '../namespace.ts'\nimport { resolveConfig, type ContextGuardConfig } from './config.ts'\nimport { en, NS, zh } from './locales.ts'\nimport type { ContextGuardInjected, ContextGuardSettingsCardInjected } from './slots.ts'\nimport { CompactGuardButton } from './CompactGuardButton.tsx'\nimport { ContextGuardSettingsCard } from './SettingsCard.tsx'\n\nexport type { ContextGuardConfig } from './config.ts'\nexport { resolveConfig } from './config.ts'\nexport type { ContextGuardKey } from './locales.ts'\nexport { guardReading } from './guard.ts'\nexport type { GuardInput, GuardLevel, GuardReading } from './guard.ts'\nexport type {\n CompactGuardButtonProps, ContextGuardInjected, ContextGuardSettingsCardInjected, ContextGuardSettingsCardProps,\n} from './slots.ts'\n\n/** Dictionary namespace owned by this plugin. */\nexport { NS }\n\n/** Required services: the slot ledger, the command Remote, the settings scope, and the copy. */\nexport const inject = ['slots', 'remote', 'remote.commands', 'locale', 'settingsScope']\n\n/**\n * Client plugin body: register the composer-tool-row compact button and the\n * settings card over the shared `context-guard` section.\n * @param ctx - client root context.\n * @param config - entry config (the section's composition base layer); defaults apply when the runner passes none.\n */\nexport function apply(ctx: Context, config?: Partial<ContextGuardConfig>): void {\n const fallback = resolveConfig(config)\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'context-guard: dictionaries')\n\n // One shared live section: the settings card writes it, the button reads\n // it. While the settings surface is absent, the button falls back to the\n // composition-time values above.\n const scope = ctx.settingsScope.bind<ContextGuardConfig>({ namespace: CONTEXT_GUARD_NS })\n\n // The slot is declared by ui-conversation, whose apply order relative to\n // this plugin is unconstrained: register through slots.inject so the entry\n // waits for the declaration instead of crashing the loader at boot.\n ctx.slots.inject('conversation.input.right', () => ctx.slots.register({\n name: 'conversation.input.right',\n id: 'context-guard',\n // Sit before the primary send button; the seat currently has no other\n // occupants, and a leading position keeps the danger visible.\n order: -10,\n locale: NS,\n inject: (sessionId: SessionId): ContextGuardInjected => ({\n thresholdRatio: fallback.thresholdRatio,\n // Failure strings stay English (error-surface policy: not localized).\n compactNow: async () => {\n const result = await ctx.remote.commands.execute(sessionId, '/compact', [])\n if (!result.ok) return `${result.error.message} (${result.error.code})`\n if (result.value === undefined) return 'unknown command: /compact'\n return null\n },\n hooks: { config: scope },\n }),\n }, CompactGuardButton))\n\n // The plugin configuration tab keys its cards on the settings namespace, so\n // the compact-timing card registers under CONTEXT_GUARD_NS and renders\n // wherever the tab dispatches that key.\n ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({\n name: 'settings.plugin.item',\n key: CONTEXT_GUARD_NS,\n locale: NS,\n inject: (): ContextGuardSettingsCardInjected => ({\n scope,\n hooks: { config: scope },\n }),\n }, ContextGuardSettingsCard))\n}\n"],"mappings":";;;;;;;;;;;;;;;;;EAQA,MAAa,mBAAmB;;;;EC4BhC,MAAM,sBAAsB;EAC5B,MAAM,sBAAsB;EAE5B,SAAS,MAAM,OAAe,KAAa,KAAqB;GAC9D,OAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;EAC3C;;;;;;;;EASA,SAAgB,cAAc,QAAqE;GACjG,OAAO,EACL,gBAAgB,MACd,QAAQ,kBAAkB,IAC1B,qBACA,mBACF,EACF;EACF;;;;;ECvDA,MAAa,KAAK;GAChB,gBAAgB;GAChB,eAAe;GACf,gBAAgB;GAChB,kBAAkB;GAClB,wBAAwB;GACxB,mBAAmB;GACnB,qBAAqB;GACrB,4BAA4B;GAC5B,iCAAiC;GACjC,uBAAuB;GACvB,kBAAkB;GAClB,iBAAiB;GACjB,oBAAoB;GACpB,kBAAkB;GAClB,kBAAkB;EACpB;;EAMA,MAAa,KAAK;;EAUlB,MAAa,KAAK;GAChB,gBAAgB;GAChB,eAAe;GACf,gBAAgB;GAChB,kBAAkB;GAClB,wBAAwB;GACxB,mBAAmB;GACnB,qBAAqB;GACrB,4BAA4B;GAC5B,iCAAiC;GACjC,uBAAuB;GACvB,kBAAkB;GAClB,iBAAiB;GACjB,oBAAoB;GACpB,kBAAkB;GAClB,kBAAkB;EACpB;;;;;;;;;;;;;ECLA,SAAgB,aAAa,OAAwC;GACnE,MAAM,EAAE,eAAe,iBAAiB,mBAAmB;GAC3D,IAAI,kBAAkB,KAAA,KAAa,CAAC,OAAO,SAAS,aAAa,KAAK,iBAAiB,GACrF,OAAO;GAET,IAAI,CAAC,OAAO,SAAS,eAAe,KAAK,kBAAkB,GAAG,OAAO;GACrE,MAAM,QAAQ,kBAAkB;GAMhC,OAAO;IACL,OANwB,SAAS,IAC/B,YACA,SAAS,iBACP,YACA;IAGJ,SAAS,KAAK,IAAI,KAAK,KAAK,MAAM,QAAQ,GAAG,CAAC;IAC9C;GACF;EACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECxCA,SAAgB,mBAAmB,EACjC,eACA,WACA,GACA,gBACA,cAC0B;GAC1B,MAAM,WAAW,cAAc,iBAAiB;GAChD,MAAM,SAAS,WAAU,UAAS,KAAK;GACvC,MAAM,YAAY,OAAO,WAAW,WAAW,OAAO,UAAU,KAAA,IAC5D,OAAO,QACP,EAAE,eAAe;GACrB,MAAM,WAAA,GAAUA,MAAAA,QAAAA,OAAc,aAAa;IACzC,iBAAiB,UAAU,mBAAmB,UAAU,kBAAkB;IAC1E,eAAe,UAAU;IACzB,gBAAgB,UAAU;GAC5B,CAAC,GAAG,CAAC,UAAU,UAAU,cAAc,CAAC;GACxC,MAAM,CAAC,MAAM,YAAA,GAAWC,MAAAA,SAAAA,CAAS,KAAK;GACtC,MAAM,CAAC,OAAO,aAAA,GAAYA,MAAAA,SAAAA,CAAwB,IAAI;GACtD,MAAM,YAAA,GAAWC,MAAAA,OAAAA,CAAO,IAAI;GAE5B,CAAA,GAAA,MAAA,UAAA,OAAgB;IACd,SAAS,UAAU;IACnB,aAAa;KACX,SAAS,UAAU;IACrB;GACF,GAAG,CAAC,CAAC;GAEL,IAAI,YAAY,QAAQ,QAAQ,UAAU,MAAM,OAAO;GAEvD,MAAM,YAAkB;IACtB,QAAQ,IAAI;IACZ,SAAS,IAAI;IACb,WAAgB,CAAC,CAAC,MAAM,YAAY;KAClC,IAAI,CAAC,SAAS,SAAS;KACvB,QAAQ,KAAK;KACb,SAAS,OAAO;IAClB,IAAI,WAAoB;KACtB,IAAI,CAAC,SAAS,SAAS;KACvB,QAAQ,KAAK;KACb,SAAS,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,CAAC;IACpE,CAAC;GACH;GAEA,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD;IAAM,WAAWC,sCAAI;IAArB,UAAA,CACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,UAAD;KACE,MAAK;KACL,WAAW,GAAGA,sCAAI,OAAO,GAAGA,sCAAI;KAChC,cAAY,EAAE,eAAe,EAAE,SAAS,OAAO,QAAQ,OAAO,EAAE,CAAC;KACjE,UAAU;KACV,SAAS;KALX,UAAA,CAOE,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;MAAM,WAAWA,sCAAI;MAAM,eAAA;MACzB,UAAA,iBAAA,GAAA,kBAAA,IAAA,CAACC,sCAAAA,sBAAD,EAAsB,MAAM,GAAK,CAAA;KAC7B,CAAA,GACL,EAAE,cAAc,CACX;IAEP,CAAA,GAAA,UAAU,QACT,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;KAAM,WAAWD,sCAAI;KAAO,MAAK;KAAS,OAAO;KAC9C,UAAA,EAAE,cAAc;IACb,CAAA,CAEJ;;EAEV;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EC3EA,SAAS,YAAY,MAAsB;GACzC,MAAM,QAAQ,OAAO,KAAK,KAAK,CAAC;GAChC,OAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;EAC1C;;EAGA,SAAS,WAAW,UAAqD,OAA0C;GACjH,OAAO,SAAS,SAAS,KAAA,KACpB,OAAO,SAAS,SAAS,YACzB,SAAS,SAAS,QAClB,SAAS,SAAS;EACzB;;;;;;;;;;EAWA,SAAgB,yBAAyB,EAAE,WAAW,OAAO,KAAoC;GAC/F,MAAM,WAAW,WAAU,UAAS,KAAK;GACzC,MAAM,QAAQ,SAAS,WAAW,UAAU,SAAS,QAAQ,KAAA;GAC7D,MAAM,CAAC,MAAM,YAAA,GAAWE,MAAAA,SAAAA,CAAS,KAAK;GACtC,MAAM,CAAC,WAAW,iBAAA,GAAgBA,MAAAA,SAAAA,CAAqB;IAAE,MAAM;IAAI,SAAS;GAAM,CAAC;GACnF,MAAM,CAAC,OAAO,aAAA,GAAYA,MAAAA,SAAAA,CAAS,KAAK;GACxC,MAAM,CAAC,OAAO,aAAA,GAAYA,MAAAA,SAAAA,CAAS,KAAK;GACxC,MAAM,YAAA,GAAWC,MAAAA,OAAAA,CAAO,IAAI;;GAE5B,MAAM,kBAAA,GAAiBA,MAAAA,OAAAA,CAAsB,IAAI;GAQjD,CAAA,GAAA,MAAA,UAAA,OAAgB;IACd,SAAS,UAAU;IACnB,IAAI,UAAU,KAAA,GAAW;IACzB,IAAI,eAAe,YAAY,SAAS,UAAU;IAClD,eAAe,UAAU,SAAS,YAAY;IAC9C,aAAa;KAAE,MAAM,OAAO,MAAM,cAAc;KAAG,SAAS;IAAM,CAAC;IACnE,SAAS,KAAK;IACd,SAAS,KAAK;IACd,aAAa;KACX,SAAS,UAAU;IACrB;GACF,GAAG,CAAC,OAAO,SAAS,QAAQ,CAAC;GAE7B,IAAI,SAAS,WAAW,eAAe,OAAO;GAE9C,MAAM,SAAS,OAAmB,KAAa,QAAsB;IACnE,MAAM,SAAS,YAAY,MAAM,IAAI;IACrC,aAAa;KAAE,MAAM,MAAM;KAAM,SAAS,CAAC,OAAO,SAAS,MAAM,KAAK,SAAS,OAAO,SAAS;IAAI,CAAC;GACtG;GAEA,MAAM,aAAmB;IACvB,IAAI,UAAU,WAAW,SAAS,aAAa,MAAM;IACrD,MAAM,QAAQ,YAAY,UAAU,IAAI;IACxC,IAAI,CAAC,OAAO,SAAS,KAAK,GAAG;IAC7B,SAAS,KAAK;IACd,SAAS,KAAK;IACd,MAAW,IAAI,kBAAkB,KAAK,CAAC,CAAC,WAAW;KACjD,IAAI,CAAC,SAAS,SAAS;KACvB,SAAS,IAAI;IACf,SAAS;KACP,IAAI,CAAC,SAAS,SAAS;KACvB,SAAS,IAAI;IACf,CAAC;GACH;GAEA,MAAM,cAAoB;IACxB,SAAS,KAAK;IACd,SAAS,KAAK;IACd,MAAW,MAAM,gBAAgB,CAAC,CAAC,YAAY;KAC7C,IAAI,SAAS,SAAS,SAAS,IAAI;IACrC,CAAC;GACH;GAEA,MAAM,QAAQ,EAAE,gBAAgB;GAChC,MAAM,QAAQ,UAAU,SAAS,OAAO,OAAO,kBAAkB,EAAE;GAEnE,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,MAAD;IAAI,WAAW,OAAO,GAAGC,gCAAI,KAAK,GAAGA,gCAAI,aAAaA,gCAAI;IAA1D,UAAA,CACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,UAAD;KACE,MAAK;KACL,WAAWA,gCAAI;KACf,iBAAe;KACf,cAAY,GAAG,EAAE,OAAO,sBAAsB,iBAAiB,EAAE,IAAI;KACrE,eAAe;MAAE,QAAQ,CAAC,IAAI;KAAE;KALlC,UAAA,CAOE,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD;MAAM,WAAWA,gCAAI;MAArB,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;OAAM,WAAWA,gCAAI;OAAO,UAAA;MAAY,CAAA,GACxC,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;OAAM,WAAWA,gCAAI;OAAc,UAAA,EAAE,sBAAsB;MAAQ,CAAA,CAC/D;KACN,CAAA,GAAA,iBAAA,GAAA,kBAAA,IAAA,CAACC,sCAAAA,0BAAD,EAA0B,WAAW,OAAO,GAAGD,gCAAI,QAAQ,GAAGA,gCAAI,gBAAgBA,gCAAI,QAAU,CAAA,CAC1F;IACP,CAAA,GAAA,QAAQ,UAAU,KAAA,KACjB,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;KAAK,WAAWA,gCAAI;KAApB,UAAA,CACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,SAAD;MAAO,WAAWA,gCAAI;MAAtB,UAAA;OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD;QAAM,WAAWA,gCAAI;QAArB,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;SAAM,WAAWA,gCAAI;SAAY,UAAA,EAAE,0BAA0B;QAAQ,CAAA,GACpE,WAAW,UAAU,gBAAgB,KAAK,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;SAAM,WAAWA,gCAAI;SAAQ,UAAA,EAAE,qBAAqB;QAAQ,CAAA,CACnG;;OACN,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD;QAAM,WAAWA,gCAAI;QAArB,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,SAAD;SACE,WAAW,UAAU,UAAU,GAAGA,gCAAI,MAAM,GAAGA,gCAAI,iBAAiBA,gCAAI;SACxE,MAAK;SACL,MAAK;SACL,KAAI;SACJ,KAAI;SACJ,OAAO,UAAU;SACjB,cAAY,EAAE,0BAA0B;SACxC,WAAW,UAAU;UAAE,MAAM;WAAE,MAAM,MAAM,OAAO;WAAO,SAAS;UAAM,GAAG,KAAM,CAAC;SAAE;QACrF,CAAA,GACD,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;SAAQ,MAAK;SAAS,WAAWA,gCAAI;SAAO,SAAS;SAClD,UAAA,EAAE,gBAAgB;QACb,CAAA,CACJ;;OACN,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;QAAM,WAAWA,gCAAI;QAAO,UAAA,EAAE,+BAA+B;OAAQ,CAAA;MAChE;KACP,CAAA,GAAA,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD;MAAM,WAAWA,gCAAI;MAArB,UAAA;OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD;QAAM,WAAWA,gCAAI;QAArB,UAAA,CACG,QAAQ,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;SAAM,WAAWA,gCAAI;SAAQ,UAAA,EAAE,gBAAgB;QAAQ,CAAA,IAAI,MACnE,QAAQ,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;SAAM,WAAWA,gCAAI;SAAY,UAAA,EAAE,gBAAgB;QAAQ,CAAA,IAAI,IACpE;;OACN,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;QACE,MAAK;QACL,WAAWA,gCAAI;QACf,UAAU,CAAC;QACX,eAAe;SACb,IAAI,UAAU,KAAA,GAAW;SACzB,aAAa;UAAE,MAAM,OAAO,MAAM,cAAc;UAAG,SAAS;SAAM,CAAC;SACnE,SAAS,KAAK;SACd,SAAS,KAAK;QAChB;QAEC,UAAA,EAAE,kBAAkB;OACf,CAAA;OACR,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;QACE,MAAK;QACL,WAAWA,gCAAI;QACf,UAAU,CAAC,SAAS,UAAU,WAAW,SAAS,aAAa;QAC/D,SAAS;QAER,UAAA,EAAE,eAAe;OACZ,CAAA;MACJ;KACH,CAAA,CAAA;IAEL,CAAA,CAAA;;EAER;;;;ECzGA,MAAa,SAAS;GAAC;GAAS;GAAU;GAAmB;GAAU;EAAe;;;;;;;EAQtF,SAAgB,MAAM,KAAc,QAA4C;GAC9E,MAAM,WAAW,cAAc,MAAM;GACrC,IAAI,aAAa,IAAI,OAAO,SAAS,IAAI;IAAE;IAAI;GAAG,CAAC,GAAG,6BAA6B;GAKnF,MAAM,QAAQ,IAAI,cAAc,KAAyB,EAAE,WAAW,iBAAiB,CAAC;GAKxF,IAAI,MAAM,OAAO,kCAAkC,IAAI,MAAM,SAAS;IACpE,MAAM;IACN,IAAI;IAGJ,OAAO;IACP,QAAQ;IACR,SAAS,eAAgD;KACvD,gBAAgB,SAAS;KAEzB,YAAY,YAAY;MACtB,MAAM,SAAS,MAAM,IAAI,OAAO,SAAS,QAAQ,WAAW,YAAY,CAAC,CAAC;MAC1E,IAAI,CAAC,OAAO,IAAI,OAAO,GAAG,OAAO,MAAM,QAAQ,IAAI,OAAO,MAAM,KAAK;MACrE,IAAI,OAAO,UAAU,KAAA,GAAW,OAAO;MACvC,OAAO;KACT;KACA,OAAO,EAAE,QAAQ,MAAM;IACzB;GACF,GAAG,kBAAkB,CAAC;GAKtB,IAAI,MAAM,OAAO,8BAA8B,IAAI,MAAM,SAAS;IAChE,MAAM;IACN,KAAK;IACL,QAAQ;IACR,eAAiD;KAC/C;KACA,OAAO,EAAE,QAAQ,MAAM;IACzB;GACF,GAAG,wBAAwB,CAAC;EAC9B"}
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
2
1
|
import z from "@deepseek-ai/schemastery";
|
|
3
2
|
//#region lib/types/namespace.js
|
|
4
3
|
/**
|
|
@@ -43,7 +42,7 @@ const ContextGuardSettingsSchema = z.object({ thresholdRatio: z.number().min(.01
|
|
|
43
42
|
*/
|
|
44
43
|
function apply(ctx, config = {}) {
|
|
45
44
|
ctx.inject(["settings"], (settingsCtx) => {
|
|
46
|
-
settingsCtx.settings.register(
|
|
45
|
+
settingsCtx.settings.register(CONTEXT_GUARD_NS, ContextGuardSettingsSchema, { base: config });
|
|
47
46
|
});
|
|
48
47
|
}
|
|
49
48
|
//#endregion
|