@falling-ts/dsh-force-compact 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.cn.md +174 -125
- package/README.md +95 -343
- package/package.json +1 -1
- package/src/core/ui-signal.js +34 -23
- package/web/client.js +9 -6
- package/web/swish.css +49 -47
package/README.md
CHANGED
|
@@ -1,174 +1,152 @@
|
|
|
1
1
|
# dsh-force-compact
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Aggressive, local-first context compaction for DeepSeek Harness agents.**
|
|
4
4
|
|
|
5
|
-
A DSH **Cordis function plugin** that keeps
|
|
6
|
-
can
|
|
7
|
-
|
|
8
|
-
costs or any data leaving your machine.
|
|
5
|
+
A DSH **Cordis function plugin** that keeps the agent's working context lean *by design*, so you
|
|
6
|
+
can deliver a **large-window experience** against a self-hosted llama.cpp serving `Qwen3.8‑27B`
|
|
7
|
+
at modest context — no API cost, no data egress.
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
[中文](README.cn.md)
|
|
11
10
|
|
|
12
11
|
---
|
|
13
12
|
|
|
14
|
-
## Why
|
|
13
|
+
## Why
|
|
15
14
|
|
|
16
|
-
Most harness setups bolt a big frontier model onto a short context budget. This plugin makes
|
|
17
|
-
opposite bet: **you own the weights, the endpoint, and the context budget.**
|
|
15
|
+
Most harness setups bolt a big frontier model onto a short context budget. This plugin makes
|
|
16
|
+
the opposite bet: **you own the weights, the endpoint, and the context budget.**
|
|
18
17
|
|
|
19
|
-
- **Self
|
|
18
|
+
- **Self-hosted inference.** Point the agent at a local OpenAI-compatible llama.cpp server
|
|
20
19
|
running `Qwen3.8‑27B` (GGUF / NVFP4 / MTP variants all work through the standard DeepSeek
|
|
21
|
-
adapter path — no separate llama.cpp adapter
|
|
22
|
-
- **
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"Backend‑agnostic thinking control").
|
|
31
|
-
- **Cheaper, private, yours.** No per‑token billing, no data egress, and you dial the exact
|
|
32
|
-
model/context tradeoff.
|
|
33
|
-
|
|
34
|
-
> **Net effect:** a big‑window *experience* (long sessions, many tools, multi‑turn goals)
|
|
35
|
-
> delivered by a locally‑served 27B model at low context. The compression is what makes it feel
|
|
36
|
-
> effortless — dramatically better compression efficiency means a dramatically smoother agent
|
|
37
|
-
> experience.
|
|
20
|
+
adapter path — no separate llama.cpp adapter needed).
|
|
21
|
+
- **Low context, high signal.** Rather than fight a small hard cap, the plugin **shrinks the
|
|
22
|
+
conversation itself**, so the agent reasons over a tight, high-signal prompt while effectively
|
|
23
|
+
reaching a much larger working memory.
|
|
24
|
+
- **Thinking-off by default.** `disableThinking: true` turns off reasoning effort on **every**
|
|
25
|
+
outbound call, enforced at two complementary seams (real DeepSeek honors one; llama.cpp honors
|
|
26
|
+
the other — see "Backend-agnostic thinking control" below).
|
|
27
|
+
- **Private & free.** No per-token billing, no egress, and the exact model/context tradeoff is
|
|
28
|
+
yours to dial.
|
|
38
29
|
|
|
39
30
|
---
|
|
40
31
|
|
|
41
|
-
## What
|
|
32
|
+
## What it does
|
|
42
33
|
|
|
43
34
|
Two compaction engines coexist behind one facade (`resolveCompaction`), transparent to callers:
|
|
44
35
|
|
|
45
|
-
| Engine |
|
|
36
|
+
| Engine | Used when | Notes |
|
|
46
37
|
|--------|-----------|-------|
|
|
47
|
-
| **Official** |
|
|
48
|
-
| **Builtin** | Automatic fallback when the service is realm
|
|
38
|
+
| **Official** | `compaction` service is resolvable in the agent realm | Preferred; delegates to `compaction/basic`. |
|
|
39
|
+
| **Builtin** | Automatic fallback when the service is realm-isolated (typical standard preset) | Self-contained persistent transaction using only `ctx.sessions` / `ctx.llm.stream` / `ctx.tokenMeter`. Reuses the official `compaction/*` event vocabulary, so it survives cross-build replay with no `ignorable` hacks. |
|
|
49
40
|
|
|
50
|
-
You never toggle
|
|
41
|
+
You never toggle — official wins when reachable, builtin takes over otherwise.
|
|
51
42
|
|
|
52
43
|
### Trigger points
|
|
53
44
|
|
|
54
|
-
- **Per
|
|
55
|
-
exact number the harness renders bottom
|
|
56
|
-
`autoThresholdTokens
|
|
57
|
-
|
|
58
|
-
- **Turn
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
when busy.
|
|
64
|
-
- **`session/flush` checkpoint** — the awaited durability checkpoint.
|
|
45
|
+
- **Per-request guard (`agent/pre-step`)** — reads the session's *projected* context tokens
|
|
46
|
+
(the exact number the harness renders bottom-right, provider-anchored). At
|
|
47
|
+
`autoThresholdTokens` it rejects the outgoing request and compacts the head instead, retaining
|
|
48
|
+
the latest `retainLatestTokens` verbatim.
|
|
49
|
+
- **Turn-end / idle (`agent/status` → `idle`)** — when the agent quiesces, optionally compacts
|
|
50
|
+
via `compactNow` (gate: `turnEndForceCompactionEnabled`).
|
|
51
|
+
- **Manual `/force-compact`** — immediate `compactNow` when idle; queues a process-local force
|
|
52
|
+
flag consumed at the next model step when busy.
|
|
53
|
+
- **`session/flush`** — the awaited durability checkpoint.
|
|
65
54
|
|
|
66
55
|
Every path funnels into the single *"compaction result landed in the session"* boundary — the
|
|
67
|
-
same
|
|
56
|
+
same point where the live-UI signal fires.
|
|
68
57
|
|
|
69
|
-
###
|
|
58
|
+
### Provider-anchored decisions
|
|
70
59
|
|
|
71
|
-
Decisions key off `projectedTokens`
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
LLM calls that provably could not pull the session below the threshold (eliminating the
|
|
75
|
-
low‑threshold dead loop).
|
|
60
|
+
Decisions key off `projectedTokens` (same figure shown in the UI corner), so the plugin never
|
|
61
|
+
drifts from what you see. Threshold-aware shrink gates skip summarizer calls that provably could
|
|
62
|
+
not pull the session below the threshold (kills the low-threshold dead loop).
|
|
76
63
|
|
|
77
|
-
###
|
|
64
|
+
### Meter-aligned shadow-price billing
|
|
78
65
|
|
|
79
66
|
The builtin transaction bills `shadowedTokenCount` from the **same** `tokenMeter.measure`
|
|
80
|
-
per
|
|
81
|
-
correctly — the bottom
|
|
67
|
+
per-node prices the official engine uses, so the meter's collapse protocol settles the drop
|
|
68
|
+
correctly — the bottom-right counter goes *down* after compaction instead of drifting upward.
|
|
82
69
|
|
|
83
|
-
### Backend
|
|
70
|
+
### Backend-agnostic thinking control
|
|
84
71
|
|
|
85
72
|
`disableThinking` is enforced at **two complementary seams**:
|
|
86
73
|
|
|
87
74
|
1. **Request seam** — `reasoningEffort:'off'` → the DeepSeek adapter serializes
|
|
88
75
|
`thinking:{type:'disabled'}` (real DeepSeek APIs honor it).
|
|
89
|
-
2. **Wire seam (`llm/stream`)** — the plugin appends top
|
|
90
|
-
post
|
|
91
|
-
(`server
|
|
76
|
+
2. **Wire seam (`llm/stream`)** — the plugin appends top-level `reasoning_effort:"none"`
|
|
77
|
+
post-serialization, which llama.cpp's OpenAI-compatible layer parses natively
|
|
78
|
+
(`server-common.cpp` maps it to `enable_thinking=false`, independent of template capability).
|
|
92
79
|
Real DeepSeek endpoints simply ignore the unknown key.
|
|
93
80
|
|
|
94
|
-
Result: thinking is genuinely off on **any** backend
|
|
95
|
-
|
|
81
|
+
Result: thinking is genuinely off on **any** backend, with no target-sniffing heuristic to miss
|
|
82
|
+
a route.
|
|
96
83
|
|
|
97
|
-
### Live
|
|
84
|
+
### Live-UI status
|
|
98
85
|
|
|
99
|
-
A tiny host→client messenger (a `liveUi` settings field
|
|
86
|
+
A tiny host→client messenger (a `liveUi` settings field mirrored live to the browser) paints a
|
|
100
87
|
badge beside the turn:
|
|
101
88
|
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
|
|
105
|
-
-
|
|
89
|
+
- **🟥 compressing** — pinned red `[强制压缩中>>>]`, fired just before a compaction commits;
|
|
90
|
+
- **🟢 done** — pinned green `[压缩完成!]`, fired the instant a compaction result lands; 3 s
|
|
91
|
+
later a fresh random "working" pair takes over;
|
|
92
|
+
- **🔵 working** — otherwise a playful random one-liner ("正在缝合上下文…", "正在憋大招…").
|
|
106
93
|
|
|
107
|
-
Publishers are fail
|
|
94
|
+
Publishers are fail-safe: a messenger glitch can never disturb the actual compaction.
|
|
108
95
|
|
|
109
96
|
---
|
|
110
97
|
|
|
111
98
|
## How it works
|
|
112
99
|
|
|
113
|
-
The plugin hooks the official model‑request Waterfalls so the decision happens **right before a
|
|
114
|
-
model request is made**, plus the durability checkpoint:
|
|
115
|
-
|
|
116
|
-
- **`agent/request`** — a Waterfall around the frozen call configuration. When `disableThinking`
|
|
117
|
-
is on, the returned config carries `reasoningEffort:'off'`. Settings are read **per request**,
|
|
118
|
-
so a `settings.yaml` edit is picked up on the next request.
|
|
119
|
-
- **`agent/pre-step`** — a Waterfall before each model step. Reads the session's *projected*
|
|
120
|
-
tokens; when `>= autoThresholdTokens` it returns `{ kind:'reject' }` (no model request) and
|
|
121
|
-
compacts the head while retaining the latest `retainLatestTokens`.
|
|
122
|
-
- **`session/flush`** — an awaited `parallel` checkpoint, so compaction completes before the
|
|
123
|
-
caller proceeds.
|
|
124
|
-
- **`/force-compact`** — a slash command acting without sending the line to the model:
|
|
125
|
-
immediate `compactNow` when idle; queued force flag when busy.
|
|
126
|
-
|
|
127
100
|
```
|
|
128
101
|
agent/request(payload, next) # every model request
|
|
129
102
|
disableThinking? -> { ...config, reasoningEffort: "off" }
|
|
130
103
|
|
|
131
104
|
agent/pre-step(payload, next) # before each model step
|
|
132
105
|
projectedTokens >= autoThresholdTokens?
|
|
133
|
-
no -> next()
|
|
106
|
+
no -> next() # let the model request proceed
|
|
134
107
|
yes -> compactRegion(head-before-retainLatestTokens, signal)
|
|
135
|
-
return { kind: "reject" }
|
|
108
|
+
return { kind: "reject" } # no model request this step
|
|
136
109
|
|
|
137
|
-
agent/status({ agent, status })
|
|
110
|
+
agent/status({ agent, status }) # lifecycle transition
|
|
138
111
|
status === "idle" && turnEndForceCompactionEnabled?
|
|
139
|
-
-> compactNow(agent, freshSignal)
|
|
112
|
+
-> compactNow(agent, freshSignal) # turn-end compaction
|
|
140
113
|
|
|
141
|
-
session/flush(session)
|
|
114
|
+
session/flush(session) # durability checkpoint
|
|
142
115
|
select region -> project messages -> preview + shrink gate
|
|
143
116
|
-> compaction.compactRegion(start, end, agent, signal)
|
|
144
117
|
```
|
|
145
118
|
|
|
146
119
|
Supporting modules:
|
|
147
120
|
|
|
148
|
-
- `src/hooks/guard.js` — per
|
|
121
|
+
- `src/hooks/guard.js` — per-request guard: thinking-off + threshold gate + forced flag.
|
|
149
122
|
- `src/hooks/command.js` — the `/force-compact` command.
|
|
150
|
-
- `src/hooks/idle.js` — turn
|
|
123
|
+
- `src/hooks/idle.js` — turn-end forced compaction.
|
|
151
124
|
- `src/hooks/wire-rewrite.js` — the `llm/stream` wire patch appending `reasoning_effort:"none"`.
|
|
152
|
-
- `src/engine/region.js` — head/tail
|
|
153
|
-
- `src/engine/summarizer.js` — the one
|
|
125
|
+
- `src/engine/region.js` — head/tail-anchored region selection (with the official pairing ledger).
|
|
126
|
+
- `src/engine/summarizer.js` — the one-shot LLM summarizer (fully aligned with official
|
|
127
|
+
`compaction-basic`: target resolution, prefix-cache alignment, `purpose:'compaction'` tag,
|
|
128
|
+
fail-closed finish classification, usage capture).
|
|
154
129
|
- `src/engine/builtin.js` — the builtin persistent transaction (official `compaction/*` vocab).
|
|
155
|
-
- `src/
|
|
156
|
-
- `src/core/
|
|
130
|
+
- `src/engine/checkpoint.js` — preview + shrink gate + delegation to the compaction service.
|
|
131
|
+
- `src/core/projected.js` — provider-anchored `projectedTokens` reading.
|
|
132
|
+
- `src/core/ui-signal.js` — the live-UI messenger.
|
|
157
133
|
|
|
158
134
|
---
|
|
159
135
|
|
|
160
|
-
## Install
|
|
136
|
+
## Install
|
|
161
137
|
|
|
162
138
|
As an installable bundle (recommended):
|
|
163
139
|
|
|
164
140
|
```sh
|
|
141
|
+
# from npm (published):
|
|
142
|
+
npm install @falling-ts/dsh-force-compact
|
|
165
143
|
# from git:
|
|
166
144
|
dsh plugin --profile web add github:falling-ts/dsh-force-compact
|
|
167
145
|
# from a local checkout:
|
|
168
146
|
dsh plugin --profile web add ./dsh-force-compact
|
|
169
147
|
```
|
|
170
148
|
|
|
171
|
-
|
|
149
|
+
Or, from a local checkout, as a `--patch` overlay without installing:
|
|
172
150
|
|
|
173
151
|
```sh
|
|
174
152
|
dsh web --patch dsh-force-compact/cordis.patch.yml
|
|
@@ -189,17 +167,19 @@ builtin compaction OK — replaced span seq[A..B] (N nodes, ~K tokens) with a P-
|
|
|
189
167
|
|
|
190
168
|
---
|
|
191
169
|
|
|
192
|
-
## Settings
|
|
170
|
+
## Settings
|
|
171
|
+
|
|
172
|
+
`$DSH_HOME/settings.yaml`, namespace `falling-ts-force-compact`:
|
|
193
173
|
|
|
194
174
|
| key | type | default | meaning |
|
|
195
175
|
|-----|------|---------|---------|
|
|
196
|
-
| `disableThinking` | boolean | `true` | Disable
|
|
197
|
-
| `autoThresholdTokens` | number ≥ 32000 | `32000` | Projected
|
|
198
|
-
| `retainLatestTokens` | positive int ≥ 8000 | `8000` | Retain the latest N tokens verbatim; send everything older to the summarizer in one batch. **Floor 8000
|
|
176
|
+
| `disableThinking` | boolean | `true` | Disable reasoning effort on **every** outbound call (both seams above). |
|
|
177
|
+
| `autoThresholdTokens` | number ≥ 32000 | `32000` | Projected-token trigger for the per-request gate. Lower ⇒ more aggressive. **Floor 32000** (stored values clamp back up at read time). |
|
|
178
|
+
| `retainLatestTokens` | positive int ≥ 8000 | `8000` | Retain the latest N tokens verbatim; send everything older to the summarizer in one batch. **Floor 8000.** Drives both the auto gate and the `/force-compact` path. |
|
|
199
179
|
| `turnEndForceCompactionEnabled` | boolean | `true` | Compact on the agent's `idle` transition. |
|
|
200
180
|
| `debug` | boolean | `true` | Emit `[force-compact]` diagnostics to the plugin log. |
|
|
201
181
|
| `logFile` | string | `~/.dsh/logs/dsh-force-compact.log` | Diagnostics destination (`~` expands to home dir). |
|
|
202
|
-
| `compactionMode` | `'realm' \| 'global'` | `'realm'` | Official
|
|
182
|
+
| `compactionMode` | `'realm' \| 'global'` | `'realm'` | Official-service resolution strategy (priority-1 path). |
|
|
203
183
|
| `builtinEnabled` | boolean | `true` | Gate for the builtin engine fallback. |
|
|
204
184
|
| `maxSummaryTokens` | integer (1024–200000) | `1024` | Cap on the summarizer LLM `maxTokens`. |
|
|
205
185
|
|
|
@@ -216,264 +196,36 @@ falling-ts-force-compact:
|
|
|
216
196
|
When the `settings` service is absent, the plugin falls back to the same defaults and still
|
|
217
197
|
compacts — the namespace is optional, never a hard dependency.
|
|
218
198
|
|
|
219
|
-
### Tuning for low
|
|
199
|
+
### Tuning for low-context llama.cpp
|
|
220
200
|
|
|
221
|
-
Serve Qwen3.8‑27B with a comfortable
|
|
222
|
-
effective window: keep `autoThresholdTokens` comfortably **below**
|
|
201
|
+
Serve `Qwen3.8‑27B` with a comfortable-but-modest context and let the plugin decide the
|
|
202
|
+
effective window: keep `autoThresholdTokens` comfortably **below** the served context so the
|
|
223
203
|
live prompt stays small and latency flat, while the agent retains deep memory through the
|
|
224
|
-
compressed head.
|
|
204
|
+
compressed head. Pressure is measured in *projected* tokens (provider-anchored), so the
|
|
225
205
|
threshold maps predictably onto what the UI shows you.
|
|
226
206
|
|
|
227
207
|
---
|
|
228
208
|
|
|
229
|
-
## Behavior notes
|
|
209
|
+
## Behavior notes
|
|
230
210
|
|
|
231
|
-
- **Runtime dependency:** the `compaction` service (preset plane
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
- **Optional dependencies:** `settings
|
|
235
|
-
`ctx.get(...)` with guards — a missing one degrades gracefully rather than crashing.
|
|
236
|
-
- **Per
|
|
211
|
+
- **Runtime dependency:** the `compaction` service (preset plane `agent-presets:compaction-basic`).
|
|
212
|
+
Read live via `ctx.get('compaction')`; when unreachable the forced-compaction path falls
|
|
213
|
+
through and lets the request proceed.
|
|
214
|
+
- **Optional dependencies:** `settings` / `tokenMeter` / `commands` / `llm` / `agents` are read
|
|
215
|
+
via `ctx.get(...)` with guards — a missing one degrades gracefully rather than crashing.
|
|
216
|
+
- **Per-request settings read:** parameters are read per model request, so edits take effect on
|
|
237
217
|
the next request without a restart.
|
|
238
218
|
- **Signals:** the `agent/*` Waterfalls forward the current turn's signal; the `session/flush`
|
|
239
219
|
checkpoint and the `agent/status` idle listener each mint a fresh `AbortController`.
|
|
240
|
-
- **Persistence:** the durable output is the compaction bracket events + a
|
|
241
|
-
`user/message` checkpoint, replay
|
|
242
|
-
- **Client half:** `web/client.js` adds a Settings section "强制压缩 / Force Compact" for
|
|
243
|
-
these values live (uSES
|
|
244
|
-
- **
|
|
245
|
-
|
|
246
|
-
process‑local `Map` force flag.
|
|
220
|
+
- **Persistence:** the durable output is the `compaction/*` bracket events + a
|
|
221
|
+
`surfaceOp:replace` `user/message` checkpoint, replay-safe across builds.
|
|
222
|
+
- **Client half:** `web/client.js` adds a Settings section "强制压缩 / Force Compact" for
|
|
223
|
+
editing these values live (uSES-safe mirror, no timers/state).
|
|
224
|
+
- **One intentional timer:** the 3 s `publishDone` fallback (presentation-only, documented
|
|
225
|
+
deviation). Otherwise the plugin is pure listeners + a process-local `Map` force flag.
|
|
247
226
|
|
|
248
227
|
---
|
|
249
228
|
|
|
250
229
|
## License
|
|
251
230
|
|
|
252
231
|
MIT (see LICENSE).
|
|
253
|
-
|
|
254
|
-
---
|
|
255
|
-
---
|
|
256
|
-
|
|
257
|
-
# dsh-force-compact —— 面向本地推理的「本地优先 · 激进压缩」插件
|
|
258
|
-
|
|
259
|
-
**为 DeepSeek Harness agent 提供的上下文压缩能力:本地优先、极简上下文、最大化 agent 使用体验。**
|
|
260
|
-
|
|
261
|
-
这是一个 DSH **Cordis 函数插件**:它让 agent 的工作上下文**始终保持在紧凑、高信号的区间**,从而
|
|
262
|
-
让你能用**自托管 llama.cpp 服务上的 Qwen3.8‑27B**(低上下文配置)跑出**接近大窗口**的体验——更低
|
|
263
|
-
延迟、更高可用、数据不出本机,且不产生任何 API 费用。
|
|
264
|
-
|
|
265
|
-
[English](README.md) | 中文
|
|
266
|
-
|
|
267
|
-
---
|
|
268
|
-
|
|
269
|
-
## 为什么要在 llama.cpp 上本地跑 Qwen3.8‑27B、并且刻意压低上下文?
|
|
270
|
-
|
|
271
|
-
主流做法是把大模型塞进短上下文预算里硬扛。本插件反其道而行:**权重、端点、上下文预算都由你自己
|
|
272
|
-
掌控。**
|
|
273
|
-
|
|
274
|
-
- **自托管推理。** 把 agent 指向一个本地 OpenAI 兼容的 llama.cpp 服务器,运行 `Qwen3.8‑27B`
|
|
275
|
-
(GGUF / NVFP4 / MTP 变体均可走标准 DeepSeek 适配器路径,**无需单独的 llama.cpp 适配器**)。
|
|
276
|
-
对话全程不离开本机。
|
|
277
|
-
- **低上下文也能又快又省。** llama.cpp 允许你用适中上下文服务 27B 模型,保持单步延迟与显存都可控。
|
|
278
|
-
激进压缩正是让它可行的关键:不与小硬上限较劲,而是**直接收缩会话本身**——agent 永远在一个紧凑、
|
|
279
|
-
高信号的小 prompt 上推理,却等效获得更大的工作记忆。
|
|
280
|
-
- **默认关闭思考。** `disableThinking: true` 对**每一次出站调用**(业务请求 + 摘要调用)都关闭模型
|
|
281
|
-
的内部推理努力——循环更快、token 消耗更低,并在两个互补缝上双重保障(见下文)。
|
|
282
|
-
- **更省钱、更私有、归你。** 无按 token 计费、无数据外泄,模型与上下文的取舍完全由你调。
|
|
283
|
-
|
|
284
|
-
> **净效果:** **大窗口的体验**(长会话、大量工具调用、多轮目标)由一个本地服务的 27B 模型 +
|
|
285
|
-
> 低上下文交付。**压缩效率的大幅提升,直接换来 agent 使用体验的大幅改善**——这就是本插件的核心价值。
|
|
286
|
-
|
|
287
|
-
---
|
|
288
|
-
|
|
289
|
-
## 插件做了什么
|
|
290
|
-
|
|
291
|
-
两条压缩引擎通过统一 facade(`resolveCompaction`)并存,对调用者透明:
|
|
292
|
-
|
|
293
|
-
| 引擎 | 何时使用 | 说明 |
|
|
294
|
-
|------|----------|------|
|
|
295
|
-
| **官方** | agent realm 内可解析到 `compaction` 服务时 | 首选,委托给 `compaction/basic`。 |
|
|
296
|
-
| **内置** | 官方服务被 realm 隔离时自动接管(典型标准预设) | 自包含持久事务,仅依赖 `ctx.sessions` / `ctx.llm.stream` / `ctx.tokenMeter`;复用官方 `compaction/*` 事件词汇,跨 build 重放存活、无需 `ignorable` hack。 |
|
|
297
|
-
|
|
298
|
-
你**无需手动切换**:官方可达就用官方,不可达才落到内置。
|
|
299
|
-
|
|
300
|
-
### 触发点
|
|
301
|
-
|
|
302
|
-
- **每请求门禁(`agent/pre-step`)** —— 读取会话的 *投影* 上下文 token(与 harness 右下角显示的同一
|
|
303
|
-
数值,provider 锚定)。达到 `autoThresholdTokens` 时,拒绝发起模型请求,改为压缩头段,并逐字保留
|
|
304
|
-
最新的 `retainLatestTokens`。
|
|
305
|
-
- **回合结束 / idle 压缩(`agent/status` → `idle`)** —— agent 静止(含子代理全部结束)时,可选地经
|
|
306
|
-
`compactNow` 压缩(开关:`turnEndForceCompactionEnabled`)。
|
|
307
|
-
- **手动 `/force-compact` 斜杠命令** —— 对忙/闲 agent 都能生效:空闲立即压缩;繁忙则排队一个
|
|
308
|
-
process‑local 强制标记,在下一个模型步骤消费。
|
|
309
|
-
- **`session/flush` 检查点** —— 等待型的持久化检查点。
|
|
310
|
-
|
|
311
|
-
每条路径最终都汇入唯一的「**压缩结果落入会话**」边界——也正是**发送 liveUI 信令**的位置。
|
|
312
|
-
|
|
313
|
-
### 判定基准是 *provider 锚定* 的
|
|
314
|
-
|
|
315
|
-
判定使用 `projectedTokens`(与 UI 角标同款数值),插件因此永不偏离你所见的数字。重度 CJK /
|
|
316
|
-
tool‑JSON 内容按米表 chars/token 密度计价以保持口径一致;阈值感知的缩容门禁会跳过「注定无法把会话
|
|
317
|
-
降到阈值以下」的摘要 LLM 调用(消灭低阈值死循环)。
|
|
318
|
-
|
|
319
|
-
### 影子价格记账与米表对齐
|
|
320
|
-
|
|
321
|
-
内置事务的 `shadowedTokenCount` 取自**与官方相同的** `tokenMeter.measure` 逐节点单价,使米表的折叠
|
|
322
|
-
协议正确结算下降——压缩后右下角计数是**下降**而非漂移上涨。
|
|
323
|
-
|
|
324
|
-
### 后端无关的思考控制
|
|
325
|
-
|
|
326
|
-
`disableThinking` 在**两个互补的缝**上强制执行:
|
|
327
|
-
|
|
328
|
-
1. **请求缝** —— `reasoningEffort:'off'` → DeepSeek 适配器序列化为 `thinking:{type:'disabled'}`
|
|
329
|
-
(真 DeepSeek API 认这个字段)。
|
|
330
|
-
2. **wire 缝(`llm/stream`)** —— 插件在序列化后追加顶层 `reasoning_effort:"none"`,llama.cpp 的
|
|
331
|
-
OpenAI 兼容层原生解析(`server‑common.cpp` 映射到 `enable_thinking=false`,与模板能力无关)。
|
|
332
|
-
真 DeepSeek 端点忽略未知键。
|
|
333
|
-
|
|
334
|
-
结果:在任何后端(包括本地 llama.cpp)上都**确实关闭了思考**,不依赖目标嗅探启发式而漏判路由。
|
|
335
|
-
|
|
336
|
-
### LiveUI 状态
|
|
337
|
-
|
|
338
|
-
一个极小的 host→client 信令通道(`liveUi` 设置字段,实时镜像到浏览器),在 turn 旁绘制徽标:
|
|
339
|
-
|
|
340
|
-
- 🟥 `compressing` —— 固定红字 `[强制压缩中>>>]`,在压缩提交前一刻发出;
|
|
341
|
-
- 🟢 `done` —— 固定绿字 `[压缩完成!]`,**在压缩结果落入会话的瞬间**发出,3 秒后回落为一组全新随机的
|
|
342
|
-
working 文案;
|
|
343
|
-
- 🔵 `working` —— 否则是一条玩梗式的随机短句("正在缝合上下文…"、"正在憋大招…")。
|
|
344
|
-
|
|
345
|
-
发布器绝对安全:信令故障永远不会干扰真实压缩事务。
|
|
346
|
-
|
|
347
|
-
---
|
|
348
|
-
|
|
349
|
-
## 工作原理
|
|
350
|
-
|
|
351
|
-
插件钩住官方的模型请求 Waterfall,使决策发生在**真正发起模型请求之前**,以及持久化检查点上:
|
|
352
|
-
|
|
353
|
-
- **`agent/request`** —— 围绕冻结调用配置的 Waterfall。`disableThinking` 开启时返回携带
|
|
354
|
-
`reasoningEffort:'off'` 的配置。参数**每次请求**读取,故 `settings.yaml` 改动下次请求即生效。
|
|
355
|
-
- **`agent/pre-step`** —— 每个模型步骤前的 Waterfall。读取 *投影* token,达到 `autoThresholdTokens`
|
|
356
|
-
时返回 `{ kind:'reject' }`(不发起模型请求),并压缩头段、逐字保留最新 `retainLatestTokens`。
|
|
357
|
-
- **`session/flush`** —— 等待型 `parallel` 检查点,保证压缩在调用方继续前完成。
|
|
358
|
-
- **`/force-compact`** —— 斜杠命令,不把该行发送给模型:空闲立即 `compactNow`,繁忙排队强制标记。
|
|
359
|
-
|
|
360
|
-
```
|
|
361
|
-
agent/request(payload, next) # 每次模型请求
|
|
362
|
-
disableThinking? -> { ...config, reasoningEffort: "off" }
|
|
363
|
-
|
|
364
|
-
agent/pre-step(payload, next) # 每个模型步骤前
|
|
365
|
-
projectedTokens >= autoThresholdTokens?
|
|
366
|
-
no -> next() # 放行模型请求
|
|
367
|
-
yes -> compactRegion(head-before-retainLatestTokens, signal)
|
|
368
|
-
return { kind: "reject" } # 本步不请求模型
|
|
369
|
-
|
|
370
|
-
agent/status({ agent, status }) # 生命周期过渡
|
|
371
|
-
status === "idle" && turnEndForceCompactionEnabled?
|
|
372
|
-
-> compactNow(agent, freshSignal) # 回合结束压缩
|
|
373
|
-
|
|
374
|
-
session/flush(session) # 持久化检查点
|
|
375
|
-
选区 -> 投影消息 -> 预览 + 缩容门禁
|
|
376
|
-
-> compaction.compactRegion(start, end, agent, signal)
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
支撑模块:
|
|
380
|
-
|
|
381
|
-
- `src/hooks/guard.js` —— 每请求门禁:关思考 + 阈值门 + 强制标记。
|
|
382
|
-
- `src/hooks/command.js` —— `/force-compact` 命令。
|
|
383
|
-
- `src/hooks/idle.js` —— 回合结束强制压缩。
|
|
384
|
-
- `src/hooks/wire-rewrite.js` —— `llm/stream` wire 补丁,追加 `reasoning_effort:"none"`。
|
|
385
|
-
- `src/engine/region.js` —— 头/尾锚定的选区(含官方配对账本)。
|
|
386
|
-
- `src/engine/summarizer.js` —— 一次性 LLM 摘要器。
|
|
387
|
-
- `src/engine/builtin.js` —— 内置持久事务(官方 `compaction/*` 词汇)。
|
|
388
|
-
- `src/core/projected.js` —— provider 锚定的 `projectedTokens` 读取。
|
|
389
|
-
- `src/core/ui-signal.js` —— liveUI 信令器。
|
|
390
|
-
|
|
391
|
-
---
|
|
392
|
-
|
|
393
|
-
## 安装与验证
|
|
394
|
-
|
|
395
|
-
作为可安装 bundle(推荐):
|
|
396
|
-
|
|
397
|
-
```sh
|
|
398
|
-
# 从 git:
|
|
399
|
-
dsh plugin --profile web add github:falling-ts/dsh-force-compact
|
|
400
|
-
# 从本地 checkout:
|
|
401
|
-
dsh plugin --profile web add ./dsh-force-compact
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
或本地 checkout 不经安装、仅作 `--patch` 叠加:
|
|
405
|
-
|
|
406
|
-
```sh
|
|
407
|
-
dsh web --patch dsh-force-compact/cordis.patch.yml
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
插件已加载 ⟺ `~/.dsh/logs/dsh-force-compact.log` 出现:
|
|
411
|
-
|
|
412
|
-
```
|
|
413
|
-
[force-compact] debug logging enabled — writing [force-compact] lines to <绝对路径>
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
验证压缩确实发生:
|
|
417
|
-
|
|
418
|
-
```
|
|
419
|
-
idle compaction (builtin) shadowed N nodes (~M tokens)
|
|
420
|
-
builtin compaction OK — replaced span seq[A..B] (N nodes, ~K tokens) with a P-char checkpoint
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
---
|
|
424
|
-
|
|
425
|
-
## 配置(`$DSH_HOME/settings.yaml`,命名空间 `falling-ts-force-compact`)
|
|
426
|
-
|
|
427
|
-
| 键 | 类型 | 默认 | 含义 |
|
|
428
|
-
|----|------|------|------|
|
|
429
|
-
| `disableThinking` | boolean | `true` | 每次出站调用关闭模型推理努力(上述两缝)。 |
|
|
430
|
-
| `autoThresholdTokens` | number ≥ 32000 | `32000` | 每请求门禁的投影 token 阈值。越低越激进、上下文越瘦。**下限 32000**(存储值读取时抬升)。 |
|
|
431
|
-
| `retainLatestTokens` | 正整数 ≥ 8000 | `8000` | 逐字保留最新 N tokens;更早内容一次性发给摘要器。**下限 8000**。同时驱动自动门禁与 `/force-compact`。 |
|
|
432
|
-
| `turnEndForceCompactionEnabled` | boolean | `true` | 在 agent `idle` 过渡时压缩。 |
|
|
433
|
-
| `debug` | boolean | `true` | 输出 `[force-compact]` 诊断到插件日志。 |
|
|
434
|
-
| `logFile` | string | `~/.dsh/logs/dsh-force-compact.log` | 诊断输出路径(`~` 展开为用户家目录)。 |
|
|
435
|
-
| `compactionMode` | `'realm' \| 'global'` | `'realm'` | 官方服务解析策略(priority‑1 路径)。 |
|
|
436
|
-
| `builtinEnabled` | boolean | `true` | 内置引擎后备闸门。 |
|
|
437
|
-
| `maxSummaryTokens` | 整数 (1024–200000) | `1024` | 摘要 LLM 调用的 `maxTokens` 上限。 |
|
|
438
|
-
|
|
439
|
-
示例——激进的**本地**配置:
|
|
440
|
-
|
|
441
|
-
```yaml
|
|
442
|
-
falling-ts-force-compact:
|
|
443
|
-
disableThinking: true
|
|
444
|
-
autoThresholdTokens: 40000 # 更早压缩 ⇒ 常驻 prompt 更小
|
|
445
|
-
retainLatestTokens: 8000
|
|
446
|
-
turnEndForceCompactionEnabled: true
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
当 `settings` 服务缺席时,插件回退到相同默认值并照常压缩——该命名空间是可选的,绝不成为硬依赖。
|
|
450
|
-
|
|
451
|
-
### 面向低上下文 llama.cpp 的调参建议
|
|
452
|
-
|
|
453
|
-
用舒适但适中的上下文服务 Qwen3.8‑27B,把有效窗口交给插件决定:将 `autoThresholdTokens` 设在**明显
|
|
454
|
-
低于**你服务的上下文,使常驻 prompt 保持小、延迟平稳,而 agent 仍通过被压缩的头段保留深层记忆。由于
|
|
455
|
-
压力按 *投影* token(provider 锚定)度量,阈值会可预测地对应到你 UI 上看到的数字。
|
|
456
|
-
|
|
457
|
-
---
|
|
458
|
-
|
|
459
|
-
## 行为说明与限制
|
|
460
|
-
|
|
461
|
-
- **运行时依赖:** `compaction` 服务(preset 平面 `agent-presets:compaction-basic`)。经
|
|
462
|
-
`ctx.get('compaction')` 实时读取;不可用时强制压缩路径放行、让请求继续。
|
|
463
|
-
- **可选依赖:** `settings` / `tokenMeter` / `commands` / `llm` / `agents` 均经 `ctx.get(...)` 读取并
|
|
464
|
-
守卫;缺任一都优雅降级而非崩溃。
|
|
465
|
-
- **每请求读参数:** 参数每次模型请求读取,故改动下次请求即生效、无需重启。
|
|
466
|
-
- **信号:** `agent/*` Waterfall 转发当前 turn 的 signal;`session/flush` 检查点与 `agent/status`
|
|
467
|
-
idle 监听器各自新建 `AbortController`。
|
|
468
|
-
- **持久性:** 持久产物为压缩括号事件 + 带 `surfaceOp:replace` 的 `user/message` 检查点,跨 build
|
|
469
|
-
重放安全。
|
|
470
|
-
- **客户端半部:** `web/client.js` 新增设置分区 "强制压缩 / Force Compact",支持实时改值(uSES 安全的
|
|
471
|
-
镜像,无 timer/状态)。
|
|
472
|
-
- **除一处外无 timer:** 唯一有意保留的是 3 s 的 `publishDone` 回落(纯表现层,已在文档声明)。其余均为
|
|
473
|
-
纯监听器 + 一个 process‑local `Map` 强制标记。
|
|
474
|
-
|
|
475
|
-
---
|
|
476
|
-
|
|
477
|
-
## License
|
|
478
|
-
|
|
479
|
-
MIT(见 LICENSE)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@falling-ts/dsh-force-compact",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "DSH Cordis plugin: hooks the core model-request seam (agent/pre-step + agent/request) to force-compact a session's context and disable thinking per the \"强制压缩配置\" settings namespace (disableThinking, autoThresholdTokens, retainLatestTokens, turnEndForceCompactionEnabled). When the threshold fires (or /force-compact queues while busy), the latest `retainLatestTokens` of the conversation's surface tokens are KEPT VERBATIM and everything before that cutoff is COMPACTED INTO A SINGLE SUMMARY NODE in one LLM call (original span entries become shadowed/skipped). Also compacts at each turn/end and at each session/flush durability checkpoint. Host half is a pure listener; a web client half registers a settings.section (强制压缩 / Force Compact) that reads and writes the same settings namespace.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
package/src/core/ui-signal.js
CHANGED
|
@@ -52,10 +52,10 @@ export const PINNED_TEXTS = Object.freeze({
|
|
|
52
52
|
[PHASE_DONE]: '[压缩完成!]',
|
|
53
53
|
})
|
|
54
54
|
|
|
55
|
-
/** Pinned colors matching {@link PINNED_TEXTS} — red while compacting, green on completion. */
|
|
55
|
+
/** Pinned colors matching {@link PINNED_TEXTS} — extra-dark tuned: deep burgundy-red while compacting, muted pine-green on completion. */
|
|
56
56
|
export const PINNED_COLORS = Object.freeze({
|
|
57
|
-
[PHASE_COMPRESSING]: '#
|
|
58
|
-
[PHASE_DONE]: '#
|
|
57
|
+
[PHASE_COMPRESSING]: '#9b1c2b',
|
|
58
|
+
[PHASE_DONE]: '#2f6f52',
|
|
59
59
|
})
|
|
60
60
|
|
|
61
61
|
/**
|
|
@@ -97,27 +97,38 @@ export const WORKING_TEXTS = Object.freeze([
|
|
|
97
97
|
* so repeated draws visibly vary BOTH dimensions).
|
|
98
98
|
* @readonly
|
|
99
99
|
*/
|
|
100
|
+
/**
|
|
101
|
+
* Extra-dark 20-color WORKING-phase palette (second darkening pass). Each
|
|
102
|
+
* entry sits one brightness step deeper than the prior dark-tuned set while
|
|
103
|
+
* preserving the full hue-wheel sweep (blue → indigo → violet → purple →
|
|
104
|
+
* plum → orchid → magenta → fuchsia → pink → rose → crimson → scarlet →
|
|
105
|
+
* vermilion → rust → ochre → gold → olive → moss → pine → fir → teal →
|
|
106
|
+
* cyan → azure → cobalt → navy). Saturation is held high enough that the
|
|
107
|
+
* badge reads as a distinct hue rather than desaturating toward grey.
|
|
108
|
+
* Random pairing with {@link WORKING_TEXTS} remains unchanged.
|
|
109
|
+
* @readonly
|
|
110
|
+
*/
|
|
100
111
|
export const WORKING_COLORS = Object.freeze([
|
|
101
|
-
'#
|
|
102
|
-
'#
|
|
103
|
-
'#
|
|
104
|
-
'#
|
|
105
|
-
'#
|
|
106
|
-
'#
|
|
107
|
-
'#
|
|
108
|
-
'#
|
|
109
|
-
'#
|
|
110
|
-
'#
|
|
111
|
-
'#
|
|
112
|
-
'#
|
|
113
|
-
'#
|
|
114
|
-
'#
|
|
115
|
-
'#
|
|
116
|
-
'#
|
|
117
|
-
'#
|
|
118
|
-
'#
|
|
119
|
-
'#
|
|
120
|
-
'#
|
|
112
|
+
'#1e40af', // royal blue
|
|
113
|
+
'#1e3a8a', // deep blue
|
|
114
|
+
'#312e81', // indigo
|
|
115
|
+
'#4c1d95', // violet
|
|
116
|
+
'#581c87', // purple
|
|
117
|
+
'#8318a3', // plum
|
|
118
|
+
'#86198f', // orchid
|
|
119
|
+
'#9d174d', // magenta
|
|
120
|
+
'#9f1239', // pink
|
|
121
|
+
'#991b1b', // rose
|
|
122
|
+
'#9a3412', // crimson
|
|
123
|
+
'#92400e', // scarlet
|
|
124
|
+
'#854d0e', // rust
|
|
125
|
+
'#4d7c0f', // ochre-gold
|
|
126
|
+
'#3f6212', // olive
|
|
127
|
+
'#166534', // moss
|
|
128
|
+
'#065f46', // pine
|
|
129
|
+
'#0e7490', // teal
|
|
130
|
+
'#155e75', // cyan
|
|
131
|
+
'#172554', // navy
|
|
121
132
|
])
|
|
122
133
|
|
|
123
134
|
/**
|