dsh-flash-ctx-mon 0.0.0-stage → 0.1.6

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/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/README.md CHANGED
@@ -1,3 +1,248 @@
1
- # Temporary Holding Version
1
+ # dsh-flash-ctx-mon
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > The **context monitor** companion plugin for
4
+ > [dock-flash](https://gitee.com/lenin.guo/dock-flash) — it registers its own alert
5
+ > provider and its own panel switch instead of living inside dock-flash's `apply()`.
6
+
7
+ Apache-2.0
8
+
9
+ **[中文](./README.zh-CN.md)**
10
+
11
+ ## What it does
12
+
13
+ Two capabilities, one in each half of the plugin:
14
+
15
+ 1. **Context monitor** — reads the **precise** token usage DSH already reports on its
16
+ session event stream and turns pressure on the model's context window into
17
+ dock-flash alerts, at three rising thresholds.
18
+ 2. **Session skills chip** — a chip in the conversation header listing the skills the
19
+ current session has actually loaded, resolved against the skill catalog.
20
+
21
+ The monitor is **on by default** — only an explicit "off" turns it off — and it is
22
+ **entirely client-side**.
23
+
24
+ ## What it registers
25
+
26
+ ### Host half — `src/index.ts` → `dist/index.js`
27
+
28
+ A Cordis plugin named `dsh-flash-ctx-mon`.
29
+
30
+ | Thing | Detail |
31
+ | --- | --- |
32
+ | Settings namespace | `dsh-flash-ctx-mon` |
33
+ | Fields | `ctxApproxWindow`, `ctxThresholdInfo`, `ctxThresholdWarning`, `ctxThresholdError`, `ctxPollBase`, `ctxPollMin`, `modelContextWindows`, `modelContextWindowSources` |
34
+ | Routes | **none** |
35
+ | Host-side services | **none** (`inject: []`) |
36
+
37
+ **The host half exists only to hold the settings namespace and its schema.** There is no
38
+ collector and no route, because there is nothing to collect on the host: token usage
39
+ arrives through DSH session events (`ctx.get('sessions')`), the model catalog through
40
+ `remote.session.modelCatalog()`, and the skill catalog through `remote.skills.list()` —
41
+ all of it in the browser.
42
+
43
+ ### Browser half — `lib/client.js` (no build step)
44
+
45
+ | Registration | Through |
46
+ | --- | --- |
47
+ | Alert provider `dsh-flash-ctx-mon:context-alert` | `ctx.get('dockFlashAlerts').registerProvider()` |
48
+ | Panel switch `dsh-flash-ctx-mon:monitor-context` | `ctx.get('quickControl').registerSwitch()` |
49
+ | Session header chip `dsh-flash-ctx-mon-skills` | `ctx.inject(['slots'])` → `conversation.session.header.actions` |
50
+
51
+ Switch properties: `type: 'toggle'`, `group: 'system'`, `cluster: 'system-alerts'`,
52
+ `order: 59`, `icon: 'message'`, plus a **Configure** button. Its visibility follows
53
+ dock-flash's `dock-flash:system-alerts` master toggle — with the alert registry switched
54
+ off there is nothing for this switch to drive. It deliberately carries **no `subtitle`**:
55
+ the live "model · 12%" readout it used to print on the row is already the first thing in
56
+ the panel the Configure button opens.
57
+
58
+ > **Dual discovery, plus a fallback poll.** `ctx.get('quickControl')` /
59
+ > `ctx.get('dockFlashAlerts')` resolve asynchronously, so registration has three routes:
60
+ > the `dock-flash:ready` event (dock-flash loaded after us), a synchronous `ctx.get()`
61
+ > check (it loaded before us), and a fallback poll of up to 15 attempts 200 ms apart.
62
+ > The first to succeed sets `_registered`, so nothing registers twice.
63
+
64
+ ## The switch
65
+
66
+ In dock-flash's quick panel, under **⚙️ System → System Alerts**, find **Context
67
+ Monitor**:
68
+
69
+ - turning it **off** writes `'0'` to `localStorage['dsh-flash-ctx-mon:monitor-context']`
70
+ and calls `setProviderEnabled(…, false)` on the alert registry, so the provider stops
71
+ producing alerts;
72
+ - turning it **on** restores both.
73
+
74
+ **Who wins.** The browser key is the authority, and **absence means on**
75
+ (`getItem(key) !== '0'`). The host's own fields are all `volatile()`, so DSH resets them
76
+ to their defaults on every restart — the browser is what remembers your choice.
77
+
78
+ ## The config modal
79
+
80
+ **Configure** beside the switch opens the context monitor panel:
81
+
82
+ | Control | Range | Step | Default |
83
+ | --- | --- | --- | --- |
84
+ | Context Window Size `ctxApproxWindow` | 64000–512000 tokens | 8000 | 128000 |
85
+ | Info Threshold `ctxThresholdInfo` | 30–80 % | 1 | 70 |
86
+ | Warning Threshold `ctxThresholdWarning` | 50–92 % | 1 | 85 |
87
+ | Error Threshold `ctxThresholdError` | 70–98 % | 1 | 95 |
88
+ | Context Poll Base Interval `ctxPollBase` | 5000–60000 ms | 1000 | 20000 |
89
+ | Context Poll Min Interval `ctxPollMin` | 1000–10000 ms | 500 | 2000 |
90
+
91
+ Below the sliders the panel reports the live state — **Current Model**, **Window Size**,
92
+ **Window Source** (Session Event / Model Catalog / Error Parsed / Manual / Built-in /
93
+ Fuzzy Match / Fallback), **Data Source** (Precise / Awaiting data), **Input Tokens** and
94
+ **Context Pressure** — and carries the **Model Window Map** editor, a **Refresh
95
+ Catalog** action, and a preview of what the alert messages look like.
96
+
97
+ ## Alerts
98
+
99
+ Emitted only when the token source is `precise`; with no session-event usage data the
100
+ monitor reports "Awaiting data" and **emits nothing** — it does not guess from message
101
+ counts.
102
+
103
+ | Level | At | Title |
104
+ | --- | --- | --- |
105
+ | ℹ️ info | ≥ `ctxThresholdInfo` (70 %) | Session context getting long |
106
+ | 🟡 warning | ≥ `ctxThresholdWarning` (85 %) | Session context nearly exhausted |
107
+ | 🔴 error | ≥ `ctxThresholdError` (95 %) | Session context almost exhausted |
108
+
109
+ Each message carries the percentage and the reading it came from — `model ·
110
+ 12.4K/128K` in precise mode. Alerts are `dismissible` and flow through dock-flash's
111
+ registry, so they appear in the panel's alert list and as toasts like any other.
112
+
113
+ ## How a model's window is resolved
114
+
115
+ `_resolveWindow(model)` reads `modelContextWindows` — the **user-mapped** table in the
116
+ settings namespace — and labels each answer with its provenance from
117
+ `modelContextWindowSources`. A model that is not in the map resolves to `null`, and the
118
+ caller falls back to `ctxApproxWindow` (the "Fallback" source).
119
+
120
+ **The built-in window table has been retired.** `_KNOWN_WINDOWS` is empty and the
121
+ catalog auto-fill is a documented no-op, so today the map is the only source of a real
122
+ window size — which is why the panel's Model Window Map editor is how you teach it about
123
+ a model it gets wrong.
124
+
125
+ > Both map fields are `volatile()`, and that is **not** a statement that they are
126
+ > transient. The settings service rejects any patched path that is not under a volatile
127
+ > node (`Config field "…" is not volatile`), so a non-volatile `modelContextWindows`
128
+ > meant every mapping the panel wrote was rejected, rolled back and lost — a mapped
129
+ > model kept falling back to `ctxApproxWindow`. Volatile is what makes the write legal;
130
+ > the value still lands in the profile patch and survives a restart.
131
+
132
+ ## Polling
133
+
134
+ The monitor samples on an adaptive interval: `ctxPollBase` is the **ceiling** used while
135
+ usage is low, and the interval tightens as usage rises, never below `ctxPollMin`. In
136
+ precise mode the base interval is doubled automatically, because the event source pushes
137
+ updates rather than being polled. A skill being used forces an immediate poll, so its
138
+ notice is claimed while the use is still fresh.
139
+
140
+ ## The skills chip
141
+
142
+ A chip in the conversation header, registered into `conversation.session.header.actions`
143
+ as `dsh-flash-ctx-mon-skills` (order 15). It lists the skills **the current session** has
144
+ loaded — subagents and other sessions are excluded, because the slot hands the component
145
+ the id of the session it renders for.
146
+
147
+ Detection uses DSH's own marker: the `<skill_content name="X">` block emitted when a
148
+ skill body is loaded. It arrives on two paths — the model calling the `skill` tool, and a
149
+ user typing `/name` in the composer (which produces **no** tool call of its own) — and a
150
+ failed call carries no block, so failures are not counted. Names are resolved against the
151
+ skill catalog (`remote.skills.list()`), and the popup shows uses, first/last use and the
152
+ catalog description, with **Open in sidebar**.
153
+
154
+ The tracker is deliberately **independent of the monitor switch**: the chip keeps working
155
+ with the context monitor switched off.
156
+
157
+ ## Settings
158
+
159
+ | Field | Default | Notes |
160
+ | --- | --- | --- |
161
+ | `ctxApproxWindow` | 128000 | Fallback window for a model the map does not know |
162
+ | `ctxThresholdInfo` | 70 | % of the window |
163
+ | `ctxThresholdWarning` | 85 | % — must exceed the info threshold |
164
+ | `ctxThresholdError` | 95 | % — must exceed the warning threshold |
165
+ | `ctxPollBase` | 20000 | ms, the low-usage ceiling |
166
+ | `ctxPollMin` | 2000 | ms, the floor |
167
+ | `modelContextWindows` | `{}` | model id → window size |
168
+ | `modelContextWindowSources` | `{}` | model id → provenance tag |
169
+
170
+ ## Dependencies
171
+
172
+ | Package | Type | Purpose |
173
+ | --- | --- | --- |
174
+ | `@deepseek-ai/cordis` | peer | the plugin framework |
175
+ | `dock-flash` `>=1.5.0-0 <2.0.0-0 \|\| >=2.0.0-0 <3.0.0-0` | peer | supplies the `quickControl` and `dockFlashAlerts` services and the `dock-flash:ready` event |
176
+ | `dock-base` `>=0.1.2-0 <1.0.0-0 \|\| >=0.2.0-0 <1.0.0-0` | peer, optional | workbench mode only |
177
+ | `@deepseek-ai/schemastery` | dependency | the settings schema (`volatile()`) |
178
+
179
+ There is **no hard dependency** on dock-flash: every service is resolved through
180
+ `ctx.get(...)`, and compatibility is declared by the **peer range** — the same shape
181
+ dock-flash itself uses towards dock-base. The range carries one branch per tuple whose
182
+ prereleases must resolve, so both the 1.x and 2.x lines are accepted, prereleases
183
+ included.
184
+
185
+ ## Install
186
+
187
+ ```sh
188
+ dsh plugin --profile <profile> add dsh-flash-ctx-mon
189
+ ```
190
+
191
+ Requires **dock-flash ≥ 1.5** — it supplies the `quickControl` and
192
+ `dockFlashAlerts` services and the `dock-flash:ready` event, and any 2.x satisfies it.
193
+ Restart DSH after installing.
194
+
195
+ `cordis.patch.yml` inserts the host row. Its `name` is a **package name**, resolved
196
+ through the profile's `node_modules` — **never a relative path**.
197
+
198
+ The browser half needs no row: the module loader discovers it from `package.json`'s
199
+ `exports["./client"]` plus `dsh.client` and serves it at
200
+ `/plugins/dsh-flash-ctx-mon/client.js`.
201
+
202
+ ## Build
203
+
204
+ ```sh
205
+ pnpm install
206
+ pnpm run build # tsc → dist/index.js (host half only)
207
+ pnpm run typecheck
208
+ node scripts/verify-config-volatile.mjs ./dist/index.js
209
+ ```
210
+
211
+ `dist/index.js` is **tracked on purpose**, for the same reason dock-flash tracks its own:
212
+ a git install fetches sources and runs no build script, so a repository without `dist/`
213
+ would arrive missing the host entry point that `main` and `exports["."]` point at.
214
+ `lib/client.js` is a single file edited directly; it has no build step and takes effect
215
+ on page refresh.
216
+
217
+ `scripts/verify-config-volatile.mjs` loads the built host half and re-runs the settings
218
+ service's own volatile-path test against it — the invariant the two map fields must
219
+ satisfy, or every write to them is rejected.
220
+
221
+ ## Layout
222
+
223
+ ```
224
+ src/index.ts HOST half → tsc → dist/index.js
225
+ lib/client.js BROWSER half → no build, edited directly
226
+ dist/index.js compiled host half — tracked on purpose
227
+ cordis.patch.yml bundle layer: inserts the host row into the profile
228
+ scripts/ verify-config-volatile.mjs — the volatile-schema check
229
+ .github/workflows/sync-from-gitee.yml — the Gitee → GitHub mirror
230
+ ```
231
+
232
+ Gitee is the authoritative repository; GitHub (`github.com/tcgbp/dsh-flash-ctx-mon`) is a
233
+ mirror of it and the host of the release tarball.
234
+
235
+ ## Privacy and limits (deliberate)
236
+
237
+ - **Nothing leaves the machine.** The monitor reads DSH's own session events and two
238
+ client remotes; it has no host route, no collector and no outbound request.
239
+ - **Precise or nothing.** With no session-event usage data the monitor shows "Awaiting
240
+ data" and raises no alert, rather than estimating from message counts.
241
+ - **Per-session skills only.** The chip counts the current session; subagent sessions and
242
+ other conversations are excluded. A session that is no longer retained reports "Skill
243
+ catalog unavailable" with a Retry.
244
+ - **Bounded tracking.** Tracked skills are capped at 200 per session (defensive), and a
245
+ skill notice is only announced for a use within the last 15 seconds, so replaying a
246
+ session's history does not fire a burst of notices.
247
+ - **UI language**: the panel and the alert text carry both Chinese and English, and
248
+ follow DSH's language setting.
@@ -0,0 +1,216 @@
1
+ # dsh-flash-ctx-mon
2
+
3
+ > [dock-flash](https://gitee.com/lenin.guo/dock-flash) 的**上下文监控**配套插件 —— 它自己注册
4
+ > 告警提供者与面板开关,而不是住在 dock-flash 的 `apply()` 里。
5
+
6
+ Apache-2.0
7
+
8
+ **[English](./README.md)**
9
+
10
+ ## 它做什么
11
+
12
+ 两个能力,分别落在插件的两半:
13
+
14
+ 1. **上下文监控** —— 直接读取 DSH 会话事件流里已有的**精确** token 用量,把模型上下文窗口的
15
+ 压力按三档递进的阈值变成 dock-flash 告警。
16
+ 2. **会话技能芯片** —— 会话标题栏上的一枚芯片,列出**当前会话**真正加载过的技能,并用技能
17
+ 目录补全信息。
18
+
19
+ 监控**默认开启**(只有显式关闭才会关掉),并且**完全在客户端**。
20
+
21
+ ## 它注册了什么
22
+
23
+ ### 宿主半 —— `src/index.ts` → `dist/index.js`
24
+
25
+ 一个名为 `dsh-flash-ctx-mon` 的 Cordis 插件。
26
+
27
+ | 项目 | 说明 |
28
+ | --- | --- |
29
+ | 设置命名空间 | `dsh-flash-ctx-mon` |
30
+ | 字段 | `ctxApproxWindow`、`ctxThresholdInfo`、`ctxThresholdWarning`、`ctxThresholdError`、`ctxPollBase`、`ctxPollMin`、`modelContextWindows`、`modelContextWindowSources` |
31
+ | 路由 | **无** |
32
+ | 宿主侧服务 | **无**(`inject: []`) |
33
+
34
+ **宿主半存在的唯一目的是承载设置命名空间与它的 schema。** 没有采集器、没有路由,因为宿主侧
35
+ 本来就无可采集:token 用量来自 DSH 会话事件(`ctx.get('sessions')`),模型目录来自
36
+ `remote.session.modelCatalog()`,技能目录来自 `remote.skills.list()` —— 全都在浏览器里。
37
+
38
+ ### 浏览器半 —— `lib/client.js`(无构建步骤)
39
+
40
+ | 注册项 | 经由 |
41
+ | --- | --- |
42
+ | 告警提供者 `dsh-flash-ctx-mon:context-alert` | `ctx.get('dockFlashAlerts').registerProvider()` |
43
+ | 面板开关 `dsh-flash-ctx-mon:monitor-context` | `ctx.get('quickControl').registerSwitch()` |
44
+ | 会话标题芯片 `dsh-flash-ctx-mon-skills` | `ctx.inject(['slots'])` → `conversation.session.header.actions` |
45
+
46
+ 开关属性:`type: 'toggle'`、`group: 'system'`、`cluster: 'system-alerts'`、`order: 59`、
47
+ `icon: 'message'`,外加一个**配置**按钮。它的可见性跟随 dock-flash 的
48
+ `dock-flash:system-alerts` 总开关 —— 告警注册表关掉之后,这个开关没有东西可驱动。它故意
49
+ **不带 `subtitle`**:过去印在行上的 "模型 · 12%" 实时读数,现在就在「配置」按钮打开的
50
+ 面板第一屏。
51
+
52
+ > **双路发现,外加兜底轮询。** `ctx.get('quickControl')` / `ctx.get('dockFlashAlerts')` 是
53
+ > 异步解析的,所以注册有三条路径:`dock-flash:ready` 事件(dock-flash 比我们晚加载)、同步
54
+ > `ctx.get()` 检查(它比我们早加载)、以及最多 15 次、每次间隔 200 ms 的兜底轮询。先成功
55
+ > 的那个会置上 `_registered`,所以不会重复注册。
56
+
57
+ ## 开关
58
+
59
+ 在 dock-flash 快捷面板的 **⚙️ 系统 → 系统告警** 下,找到 **上下文监控**:
60
+
61
+ - **关**:向 `localStorage['dsh-flash-ctx-mon:monitor-context']` 写入 `'0'`,并对告警注册表
62
+ 调用 `setProviderEnabled(…, false)`,提供者随之停止产出告警;
63
+ - **开**:两者一起恢复。
64
+
65
+ **谁说了算。** 浏览器里那个键是权威,且**键不存在即为开**(`getItem(key) !== '0'`)。宿主
66
+ 侧的字段全部是 `volatile()`,每次重启 DSH 都会把它们重置为默认值 —— 记住你选择的是浏览器。
67
+
68
+ ## 配置面板
69
+
70
+ 开关旁的**配置**按钮打开上下文监控面板:
71
+
72
+ | 控件 | 范围 | 步长 | 默认 |
73
+ | --- | --- | --- | --- |
74
+ | 上下文窗口大小 `ctxApproxWindow` | 64000–512000 token | 8000 | 128000 |
75
+ | 信息阈值 `ctxThresholdInfo` | 30–80 % | 1 | 70 |
76
+ | 警告阈值 `ctxThresholdWarning` | 50–92 % | 1 | 85 |
77
+ | 错误阈值 `ctxThresholdError` | 70–98 % | 1 | 95 |
78
+ | 上下文轮询基础间隔 `ctxPollBase` | 5000–60000 ms | 1000 | 20000 |
79
+ | 上下文轮询最小间隔 `ctxPollMin` | 1000–10000 ms | 500 | 2000 |
80
+
81
+ 滑块下方是实时状态:**当前模型**、**窗口大小**、**窗口来源**(会话事件 / 模型目录 / 错误
82
+ 解析 / 手动配置 / 内置表 / 模糊匹配 / 估算默认)、**数据来源**(精确 / 等待数据)、
83
+ **输入Token** 与 **上下文压力**;此外还有**模型窗口映射**编辑器、**刷新目录**动作,以及
84
+ 告警消息样式的预览。
85
+
86
+ ## 告警
87
+
88
+ 仅在 token 来源为 `precise` 时产出;没有会话事件用量数据时,监控显示「等待数据」并且
89
+ **什么都不发** —— 它不会拿消息条数去猜。
90
+
91
+ | 级别 | 触发 | 标题 |
92
+ | --- | --- | --- |
93
+ | ℹ️ 信息 | ≥ `ctxThresholdInfo`(70 %) | 会话上下文较长 |
94
+ | 🟡 警告 | ≥ `ctxThresholdWarning`(85 %) | 会话上下文即将用尽 |
95
+ | 🔴 错误 | ≥ `ctxThresholdError`(95 %) | 会话上下文几乎用尽 |
96
+
97
+ 每条消息都带上百分比与它所依据的读数 —— 精确模式下形如 `模型 · 12.4K/128K`。告警是
98
+ `dismissible` 的,并且走 dock-flash 的注册表,所以它们和其它告警一样出现在面板告警列表与
99
+ 弹窗通知里。
100
+
101
+ ## 模型窗口是怎么解析出来的
102
+
103
+ `_resolveWindow(model)` 读取 `modelContextWindows` —— 设置命名空间里那张**用户映射**表 —— 并
104
+ 用 `modelContextWindowSources` 给每个结果标上来源。表里没有的模型解析结果为 `null`,调用方
105
+ 回退到 `ctxApproxWindow`(即「估算默认」来源)。
106
+
107
+ **内置窗口表已经退役。** `_KNOWN_WINDOWS` 为空,目录自动填充是一个有文档说明的空实现,所以
108
+ 今天映射表是真实窗口大小的唯一来源 —— 这也正是「模型窗口映射」编辑器存在的意义:教会它某个
109
+ 它算错的模型。
110
+
111
+ > 两个映射字段都是 `volatile()`,而这不代表它们是**临时**的。设置服务会拒绝任何不在 volatile
112
+ > 节点下的写入路径(`Config field "…" is not volatile`),所以 `modelContextWindows` 曾经因为
113
+ > 不是 volatile,导致面板写下的每一条映射都被宿主拒绝、被客户端回滚、然后丢失 —— 被映射过的
114
+ > 模型依旧回退到 `ctxApproxWindow`。volatile 是让写入合法的前提;值本身照旧落在 profile patch
115
+ > 里,重启后仍在。
116
+
117
+ ## 轮询
118
+
119
+ 监控按自适应间隔采样:`ctxPollBase` 是占比低时使用的**天花板**,占比越高间隔越密,但不会低于
120
+ `ctxPollMin`。精确模式下基础间隔会自动加倍,因为事件源是主动推送而不是被轮询。有技能被使用时
121
+ 会立刻触发一次采样,好让它的提示在「新鲜」窗口内被认领。
122
+
123
+ ## 技能芯片
124
+
125
+ 会话标题栏上的一枚芯片,以 `dsh-flash-ctx-mon-skills`(order 15)注册进
126
+ `conversation.session.header.actions`。它列出**当前会话**加载过的技能 —— 子代理与其它会话被
127
+ 排除在外,因为 slot 会把「它正在为哪个会话渲染」的 id 交给组件。
128
+
129
+ 检测用的是 DSH 自己的标记:技能正文被加载时输出的 `<skill_content name="X">` 块。它从两条
130
+ 路径到达 —— 模型调用 `skill` 工具,以及用户在输入框敲 `/name`(这条路径**不产生**工具调用)
131
+ —— 而失败的调用不带这个块,所以失败不会被计入。名字会用技能目录(`remote.skills.list()`)
132
+ 解析,弹层显示调用次数、首次/最近使用时间与目录描述,并提供**在侧栏打开**。
133
+
134
+ 该追踪器刻意**独立于监控开关**:上下文监控关掉之后,芯片照常工作。
135
+
136
+ ## 设置项
137
+
138
+ | 字段 | 默认 | 说明 |
139
+ | --- | --- | --- |
140
+ | `ctxApproxWindow` | 128000 | 映射表不认识的模型所用的回退窗口 |
141
+ | `ctxThresholdInfo` | 70 | 占窗口百分比 |
142
+ | `ctxThresholdWarning` | 85 | % —— 必须大于信息阈值 |
143
+ | `ctxThresholdError` | 95 | % —— 必须大于警告阈值 |
144
+ | `ctxPollBase` | 20000 | ms,占比低时的天花板 |
145
+ | `ctxPollMin` | 2000 | ms,地板 |
146
+ | `modelContextWindows` | `{}` | 模型 id → 窗口大小 |
147
+ | `modelContextWindowSources` | `{}` | 模型 id → 来源标记 |
148
+
149
+ ## 依赖
150
+
151
+ | 包 | 类型 | 用途 |
152
+ | --- | --- | --- |
153
+ | `@deepseek-ai/cordis` | peer | 插件框架 |
154
+ | `dock-flash` `>=1.5.0-0 <2.0.0-0 \|\| >=2.0.0-0 <3.0.0-0` | peer | 提供 `quickControl`、`dockFlashAlerts` 服务与 `dock-flash:ready` 事件 |
155
+ | `dock-base` `>=0.1.2-0 <1.0.0-0 \|\| >=0.2.0-0 <1.0.0-0` | peer,可选 | 仅 workbench 模式 |
156
+ | `@deepseek-ai/schemastery` | dependency | 设置 schema(`volatile()`) |
157
+
158
+ 对 dock-flash **没有硬依赖**:所有服务都通过 `ctx.get(...)` 解析,兼容性由 **peer 范围**声明
159
+ —— 与 dock-flash 自己对 dock-base 的写法同形。该范围按「预发布必须能解析的 tuple」逐条分支,
160
+ 因此 1.x 与 2.x 两条线都被接受,预发布版本也算在内。
161
+
162
+ ## 安装
163
+
164
+ ```sh
165
+ dsh plugin --profile <profile> add dsh-flash-ctx-mon
166
+ ```
167
+
168
+ 需要 **dock-flash ≥ 1.5**:它提供 `quickControl`、`dockFlashAlerts` 服务与 `dock-flash:ready` 事件,任何 2.x 都满足。安装后请重启 DSH。
169
+
170
+ `cordis.patch.yml` 负责插入宿主行。它的 `name` 是**包名**,经 profile 的 `node_modules`
171
+ 解析 —— **绝不是相对路径**。
172
+
173
+ 浏览器半不需要任何行:模块加载器从 `package.json` 的 `exports["./client"]` 配合 `dsh.client`
174
+ 发现它,并在 `/plugins/dsh-flash-ctx-mon/client.js` 提供。
175
+
176
+ ## 构建
177
+
178
+ ```sh
179
+ pnpm install
180
+ pnpm run build # tsc → dist/index.js (仅宿主半)
181
+ pnpm run typecheck
182
+ node scripts/verify-config-volatile.mjs ./dist/index.js
183
+ ```
184
+
185
+ `dist/index.js` 是**有意纳入版本控制**的,理由与 dock-flash 相同:git 安装只取源码、不跑任何
186
+ 构建脚本,所以没有 `dist/` 的仓库会缺少 `main` 与 `exports["."]` 指向的宿主入口。
187
+ `lib/client.js` 是单文件直接编辑,无构建步骤,刷新页面即生效。
188
+
189
+ `scripts/verify-config-volatile.mjs` 会加载构建好的宿主半,并对它重跑设置服务自己的
190
+ volatile 路径检查 —— 也就是那两个映射字段必须满足的不变量,不满足则每次写入都会被拒绝。
191
+
192
+ ## 目录结构
193
+
194
+ ```
195
+ src/index.ts HOST half → tsc → dist/index.js
196
+ lib/client.js BROWSER half → no build, edited directly
197
+ dist/index.js compiled host half — tracked on purpose
198
+ cordis.patch.yml bundle layer: inserts the host row into the profile
199
+ scripts/ verify-config-volatile.mjs — the volatile-schema check
200
+ .github/workflows/sync-from-gitee.yml — the Gitee → GitHub mirror
201
+ ```
202
+
203
+ Gitee 是权威仓库;GitHub(`github.com/tcgbp/dsh-flash-ctx-mon`)是它的镜像,也是发布用
204
+ tarball 的托管处。
205
+
206
+ ## 隐私与边界(有意为之)
207
+
208
+ - **不出本机。** 监控只读 DSH 自己的会话事件与两个客户端 remote;它没有宿主路由、没有采集器、
209
+ 也没有任何对外请求。
210
+ - **精确,否则不发。** 没有会话事件用量数据时显示「等待数据」且不发告警,而不是拿消息条数
211
+ 估算。
212
+ - **技能仅限本会话。** 芯片只统计当前会话;子代理会话与其它对话排除在外。已不再保留的会话
213
+ 会显示「技能目录读取失败」并给出重试。
214
+ - **有界追踪。** 每个会话最多追踪 200 个技能(防御性上限);只有当某次使用发生在最近 15 秒内
215
+ 时才会发出提示,所以重放一个会话的历史不会一次爆出一串提示。
216
+ - **界面语言**:面板与告警文案同时提供中英文,并跟随 DSH 的语言设置。