dsh-compact-arsenal 0.3.0 → 0.4.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 CHANGED
@@ -4,6 +4,24 @@ All notable changes to this project are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.4.0] — 2026-11-11
8
+
9
+ ### Fixed
10
+
11
+ - **The pressure readout never appeared while all sections were disabled.**
12
+ `rewriteAsync()` short-circuited before measuring, so a plugin with every
13
+ switch off reported nothing. Measurement now happens first, and the panel
14
+ endpoint re-reads it from the live agent on every request instead of relying
15
+ on the once-per-turn hook value.
16
+
17
+ ### Added
18
+
19
+ - **Version badge in the panel header** (`v0.4.0`), sourced from the running
20
+ plugin rather than `package.json`, so a stale install is visible at a glance.
21
+ - A disabled **检查更新** button beside it, reserving the seat for a future
22
+ update check; the market owns package updates, so this stays inert until one
23
+ exists.
24
+
7
25
  ## [0.3.0] — 2026-11-11
8
26
 
9
27
  ### Fixed
package/client.js CHANGED
@@ -270,6 +270,7 @@ window.__ModuleLoader__.load({
270
270
  const [archiveRoot, setArchiveRoot] = useState(null)
271
271
  const [lastArchivePath, setLastArchivePath] = useState(null)
272
272
  const [pressure, setPressure] = useState(null)
273
+ const [runtimeVersion, setRuntimeVersion] = useState(null)
273
274
  const timer = useRef(null)
274
275
  const latest = useRef(emptyConfig)
275
276
 
@@ -287,6 +288,7 @@ window.__ModuleLoader__.load({
287
288
  setArchiveRoot(body.archiveRoot ?? null)
288
289
  setLastArchivePath(body.lastArchivePath ?? null)
289
290
  setPressure(body.pressure ?? null)
291
+ setRuntimeVersion(body.version ?? null)
290
292
  setStatus(body.editable === false ? 'readonly' : 'ready')
291
293
  })
292
294
  .catch((error) => {
@@ -317,6 +319,7 @@ window.__ModuleLoader__.load({
317
319
  if (body.archiveRoot) setArchiveRoot(body.archiveRoot)
318
320
  if (body.lastArchivePath !== undefined) setLastArchivePath(body.lastArchivePath)
319
321
  if (body.pressure !== undefined) setPressure(body.pressure)
322
+ if (body.version) setRuntimeVersion(body.version)
320
323
  setTotalApplied(body.totalApplied === undefined ? null : body.totalApplied)
321
324
  setStatus('ready')
322
325
  setMessage('已保存')
@@ -506,13 +509,49 @@ window.__ModuleLoader__.load({
506
509
  : status === 'readonly' ? '配置写入不可用(只读)'
507
510
  : (message || '已连接')
508
511
 
512
+ /**
513
+ * Version badge beside the title.
514
+ *
515
+ * The update affordance is deliberately inert for now: the market owns
516
+ * package updates, so this only reserves the seat and the label. When an
517
+ * update check exists, it flips `updateAvailable` and the same button
518
+ * becomes actionable without moving anything.
519
+ */
520
+ const versionBadge = h('div', {
521
+ key: 'version',
522
+ style: { display: 'flex', alignItems: 'center', gap: 6 },
523
+ }, [
524
+ h('span', {
525
+ key: 'v',
526
+ title: runtimeVersion ? `运行中:${runtimeVersion}` : '运行中版本未知',
527
+ style: {
528
+ fontSize: 11, padding: '1px 7px', borderRadius: 999, color: MUTED,
529
+ border: `1px solid ${LINE}`, fontVariantNumeric: 'tabular-nums',
530
+ },
531
+ }, runtimeVersion ? `v${runtimeVersion}` : 'v?'),
532
+ h('button', {
533
+ key: 'check',
534
+ type: 'button',
535
+ disabled: true,
536
+ title: '检查更新(预留:将由插件市场提供)',
537
+ style: {
538
+ fontSize: 11, padding: '1px 8px', borderRadius: 999,
539
+ border: `1px solid ${LINE}`, background: 'transparent',
540
+ color: MUTED, cursor: 'not-allowed', opacity: 0.55,
541
+ },
542
+ }, '检查更新'),
543
+ ])
544
+
509
545
  const header = h('header', {
510
546
  key: 'hdr',
511
547
  style: { display: 'flex', flexDirection: 'column', gap: 8 },
512
548
  }, [
513
549
  h('div', { key: 'row', style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 } }, [
514
550
  h('div', { key: 't', style: { display: 'flex', flexDirection: 'column', gap: 2 } }, [
515
- h('h1', { key: 'h1', style: { margin: 0, fontSize: 18, fontWeight: 600 } }, '上下文压缩'),
551
+ h('div', { key: 'title', style: { display: 'flex', alignItems: 'center', gap: 8 } }, [
552
+ h('h1', { key: 'h1', style: { margin: 0, fontSize: 18, fontWeight: 600 } }, '上下文压缩'),
553
+ versionBadge,
554
+ ]),
516
555
  h('p', { key: 'p', style: { margin: 0, fontSize: 12, color: MUTED, lineHeight: 1.6 } },
517
556
  '开关即生效,参数改完自动保存。每个板块有自己的触发阈值。'),
518
557
  ]),
package/index.js CHANGED
@@ -27,6 +27,12 @@ import { archiveRewrite, archiveRoot } from './lib/archive.js'
27
27
 
28
28
  export const name = 'compact-arsenal'
29
29
 
30
+ /**
31
+ * Bumped in lockstep with `package.json` so the panel can show which build is
32
+ * actually running — a stale install is otherwise invisible.
33
+ */
34
+ export const PACKAGE_VERSION = '0.4.0'
35
+
30
36
  /** Host services this plugin reads; all ship with dsh-base or dsh-web-app. */
31
37
  export const inject = ['sessions', 'tokenMeter', 'webServer', 'configEditor']
32
38
 
@@ -174,6 +180,33 @@ function isLegacyModelPrompt(prompt) {
174
180
  return typeof prompt === 'string' && prompt.includes('只输出要点') && prompt.includes('不超过 200 字')
175
181
  }
176
182
 
183
+ /**
184
+ * Recompute the pressure snapshot from whichever agent is live.
185
+ *
186
+ * The step hook updates the same object at most once per turn; a panel read
187
+ * should reflect the session as it stands now, including when every section is
188
+ * disabled and the hook short-circuits.
189
+ *
190
+ * @param {any} ctx - plugin context.
191
+ * @param {{ reported: number | null, estimated: number, window: number | null }} pressure - snapshot to update.
192
+ * @returns {void}
193
+ */
194
+ function refreshPressure(ctx, pressure) {
195
+ try {
196
+ const agents = ctx.get('agents')?.list?.() ?? []
197
+ const agent = agents[agents.length - 1]
198
+ if (agent === undefined) return
199
+ const session = ctx.sessions.get(agent.id)
200
+ if (session === undefined) return
201
+ const snapshot = pressureSnapshot(ctx, session)
202
+ pressure.reported = snapshot.reported ?? null
203
+ pressure.estimated = snapshot.estimated
204
+ pressure.window = session.requestContext?.()?.contextWindow ?? null
205
+ } catch {
206
+ // A missing agent registry only means "nothing live to measure".
207
+ }
208
+ }
209
+
177
210
  /**
178
211
  * Register the rewrite pass on the per-step hook.
179
212
  * @param {any} ctx - plugin context.
@@ -250,7 +283,11 @@ function registerPanelRoutes(ctx, config, stats, archive, pressure) {
250
283
 
251
284
  route(`${API_PREFIX}/state`, (req, res) => {
252
285
  if (req.method !== 'GET') return send(res, 405, { error: 'method-not-allowed' })
286
+ // Fold any live agent into the snapshot at read time: the step hook only
287
+ // updates it once per turn, and the panel should not show a stale number.
288
+ refreshPressure(ctx, pressure)
253
289
  send(res, 200, {
290
+ version: PACKAGE_VERSION,
254
291
  config,
255
292
  stats,
256
293
  editable: configEditor !== undefined,
@@ -488,21 +525,22 @@ function rewrite(ctx, config, payload, lastProcessedTurn, stats, archive, pressu
488
525
  * @returns {Promise<void>} resolves when every rewrite of this turn landed.
489
526
  */
490
527
  async function rewriteAsync(ctx, config, payload, lastProcessedTurn, stats, archive, pressure) {
491
- if (!config.total.enabled && !config.reasoning.enabled && !config.tools.enabled) return
492
-
493
528
  const session = ctx.sessions.get(payload.agent.id)
494
529
  if (session === undefined) return
495
530
 
496
- if (lastProcessedTurn.get(session.id) === payload.turn) return
497
-
498
- lastProcessedTurn.set(session.id, payload.turn)
499
-
500
- // Snapshot the inputs once per turn so the panel can explain a gate decision.
531
+ // Measure before any gate: the panel must be able to explain why nothing ran,
532
+ // so a fully disabled plugin still reports the current pressure.
501
533
  const snapshot = pressureSnapshot(ctx, session)
502
534
  pressure.reported = snapshot.reported ?? null
503
535
  pressure.estimated = snapshot.estimated
504
536
  pressure.window = session.requestContext?.()?.contextWindow ?? null
505
537
 
538
+ if (!config.total.enabled && !config.reasoning.enabled && !config.tools.enabled) return
539
+
540
+ if (lastProcessedTurn.get(session.id) === payload.turn) return
541
+
542
+ lastProcessedTurn.set(session.id, payload.turn)
543
+
506
544
  if (config.total.enabled && underPressure(ctx, session, config.total.pressureRatio)) {
507
545
  await totalPass(ctx, session, config.total, payload.turn, payload.signal, stats, archive)
508
546
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-compact-arsenal",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Configurable context compaction for DeepSeek Harness: three independent sections — whole-context checkpoint, reasoning compression and tool-output pruning, each with its own switch, threshold and method, plus a left-sidebar panel.",
5
5
  "icon": "icon.svg",
6
6
  "author": "TacitSay",