@falling-ts/dsh-force-compact 0.2.0 → 0.2.1
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/README.cn.md
CHANGED
|
@@ -1,170 +1,219 @@
|
|
|
1
1
|
# dsh-force-compact
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**面向本地推理的「本地优先 · 激进压缩」插件。**
|
|
4
4
|
|
|
5
|
-
`@falling-ts/dsh-force-compact` 是一个 DSH **Cordis
|
|
6
|
-
|
|
5
|
+
`@falling-ts/dsh-force-compact` 是一个 DSH **Cordis 函数插件**,它让 agent 的工作上下文**始终
|
|
6
|
+
保持在紧凑、高信号的区间**,从而让你能用自托管 llama.cpp 服务上的 `Qwen3.8‑27B`(低上下文
|
|
7
|
+
配置)跑出**接近大窗口**的体验——更低延迟、数据不出本机、无 API 费用。
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
- 当会话上下文总 tokens 数**达到** `autoThresholdTokens` 时,**不请求模型**,
|
|
10
|
-
而是**强制执行强制压缩**。
|
|
9
|
+
[English](README.md)
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 为什么做这件事
|
|
14
|
+
|
|
15
|
+
主流做法是把大模型塞进短上下文预算里硬扛。本插件反其道而行:**权重、端点、上下文预算都由你
|
|
16
|
+
自己掌控。**
|
|
17
|
+
|
|
18
|
+
- **自托管推理。** 把 agent 指向本地 OpenAI 兼容的 llama.cpp 服务器,运行 `Qwen3.8‑27B`
|
|
19
|
+
(GGUF / NVFP4 / MTP 变体均可走标准 DeepSeek 适配器路径,**无需单独的 llama.cpp 适配器**)。
|
|
20
|
+
- **低上下文、高信号。** 不与小硬上限较劲,而是**直接收缩会话本身**——agent 永远在紧凑、
|
|
21
|
+
高信号的小 prompt 上推理,却等效获得更大的工作记忆。
|
|
22
|
+
- **默认关闭思考。** `disableThinking: true` 对**每一次出站调用**(业务请求 + 插件自己的摘要
|
|
23
|
+
调用)都关闭模型的内部推理努力,并在两个互补缝上双重保障(见下文「后端无关的思考控制」)。
|
|
24
|
+
- **私有且免费。** 无按 token 计费、无数据外泄,模型与上下文的取舍完全由你调。
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 插件做了什么
|
|
29
|
+
|
|
30
|
+
两条压缩引擎通过统一 facade(`resolveCompaction`)并存,对调用者透明:
|
|
31
|
+
|
|
32
|
+
| 引擎 | 何时使用 | 说明 |
|
|
33
|
+
|------|----------|------|
|
|
34
|
+
| **官方** | agent realm 内可解析到 `compaction` 服务时 | 首选,委托给 `compaction/basic`。 |
|
|
35
|
+
| **内置** | 官方服务被 realm 隔离时自动接管(典型标准预设) | 自包含持久事务,仅依赖 `ctx.sessions` / `ctx.llm.stream` / `ctx.tokenMeter`;复用官方 `compaction/*` 事件词汇,跨 build 重放存活、无需 `ignorable` hack。 |
|
|
36
|
+
|
|
37
|
+
你**无需手动切换**:官方可达就用官方,不可达才落到内置。
|
|
38
|
+
|
|
39
|
+
### 触发点
|
|
40
|
+
|
|
41
|
+
- **每请求门禁(`agent/pre-step`)** —— 读取会话的 *投影* 上下文 token(与 harness 右下角显示
|
|
42
|
+
的同一数值,provider 锚定)。达到 `autoThresholdTokens` 时,拒绝发起模型请求,改为压缩头段,
|
|
43
|
+
并逐字保留最新的 `retainLatestTokens`。
|
|
44
|
+
- **回合结束 / idle(`agent/status` → `idle`)** —— agent 静止(含子代理全部结束)时,可选地
|
|
45
|
+
经 `compactNow` 压缩(开关:`turnEndForceCompactionEnabled`)。
|
|
46
|
+
- **手动 `/force-compact` 斜杠命令** —— 对忙/闲 agent 都能生效:空闲立即压缩;繁忙则排队一个
|
|
47
|
+
process-local 强制标记,在下一个模型步骤消费。
|
|
48
|
+
- **`session/flush` 检查点** —— 等待型的持久化检查点。
|
|
49
|
+
|
|
50
|
+
每条路径最终都汇入唯一的「**压缩结果落入会话**」边界——也正是**发送 liveUI 信令**的位置。
|
|
51
|
+
|
|
52
|
+
### 判定基准是 provider 锚定的
|
|
53
|
+
|
|
54
|
+
判定使用 `projectedTokens`(与 UI 角标同款数值),插件因此永不偏离你所见的数字。阈值感知的
|
|
55
|
+
缩容门禁会跳过「注定无法把会话降到阈值以下」的摘要 LLM 调用(消灭低阈值死循环)。
|
|
56
|
+
|
|
57
|
+
### 影子价格记账与米表对齐
|
|
58
|
+
|
|
59
|
+
内置事务的 `shadowedTokenCount` 取自**与官方相同的** `tokenMeter.measure` 逐节点单价,使米表
|
|
60
|
+
的折叠协议正确结算下降——压缩后右下角计数是**下降**而非漂移上涨。
|
|
61
|
+
|
|
62
|
+
### 后端无关的思考控制
|
|
63
|
+
|
|
64
|
+
`disableThinking` 在**两个互补的缝**上强制执行:
|
|
65
|
+
|
|
66
|
+
1. **请求缝** —— `reasoningEffort:'off'` → DeepSeek 适配器序列化为
|
|
67
|
+
`thinking:{type:'disabled'}`(真 DeepSeek API 认这个字段)。
|
|
68
|
+
2. **wire 缝(`llm/stream`)** —— 插件在序列化后追加顶层 `reasoning_effort:"none"`,llama.cpp
|
|
69
|
+
的 OpenAI 兼容层原生解析(`server-common.cpp` 映射到 `enable_thinking=false`,与模板能力
|
|
70
|
+
无关)。真 DeepSeek 端点忽略未知键。
|
|
71
|
+
|
|
72
|
+
结果:在任何后端(包括本地 llama.cpp)上都**确实关闭了思考**,不依赖目标嗅探启发式而漏判路由。
|
|
73
|
+
|
|
74
|
+
### LiveUI 状态
|
|
75
|
+
|
|
76
|
+
一个极小的 host→client 信令通道(`liveUi` 设置字段,实时镜像到浏览器),在 turn 旁绘制徽标:
|
|
77
|
+
|
|
78
|
+
- **🟥 compressing** —— 固定红色 `[强制压缩中>>>]`,在压缩提交前一刻发出;
|
|
79
|
+
- **🟢 done** —— 固定绿色 `[压缩完成!]`,**在压缩结果落入会话的瞬间**发出,3 秒后回落为一组
|
|
80
|
+
全新的随机 working 文案;
|
|
81
|
+
- **🔵 working** —— 否则是一条玩梗式的随机短句("正在缝合上下文…"、"正在憋大招…"),颜色为
|
|
82
|
+
20 色深色色板随机抽取。
|
|
83
|
+
|
|
84
|
+
发布器绝对安全:信令故障永远不会干扰真实压缩事务。
|
|
85
|
+
|
|
86
|
+
---
|
|
14
87
|
|
|
15
88
|
## 工作原理
|
|
16
89
|
|
|
17
|
-
插件钩住官方的模型请求 Waterfall
|
|
18
|
-
核心模型请求"的需求),并保留持久化检查点:
|
|
19
|
-
|
|
20
|
-
- **`agent/request`** —— 围绕冻结调用配置的 Waterfall。当 `disableThinking` 开启时,
|
|
21
|
-
返回的 `LlmCallConfig` 携带 `reasoningEffort: 'off'`,LLM 适配器将其映射为
|
|
22
|
-
`thinking: { type: 'disabled' }`,即进程内**每次模型请求**都关闭思考。设置在此
|
|
23
|
-
(每次请求)读取,因此 `settings.yaml` 的改动在下一次请求即生效。
|
|
24
|
-
- **`agent/pre-step`** —— 每个模型步骤之前的 Waterfall。通过 `tokenMeter` 服务
|
|
25
|
-
读取会话**上下文总 tokens 数**;当其**达到或超过** `autoThresholdTokens` 时,
|
|
26
|
-
返回 `{ kind: 'reject' }` **不发起模型请求**,并通过 compaction 服务的
|
|
27
|
-
`compactRegion`(经 `ctx.get('compaction')` 实时读取)**保留最新的
|
|
28
|
-
`retainLatestTokens` 个 token 逐字不变**,并将其余**头段**一次性浓缩为一个摘要节点,
|
|
29
|
-
让循环以更小的上下文重试。
|
|
30
|
-
- **`session/flush`** —— 一个被等待(awaited)的 `parallel` 持久化检查点。检查点
|
|
31
|
-
会等待所有监听器完成,因此压缩在调用方继续之前就已结束,摘要保证落盘。
|
|
32
|
-
- **`/force-compact`** —— 通过 `/` 选择执行的斜杠命令,强制压缩该 Agent 的会话
|
|
33
|
-
上下文。其 handler **不发送模型请求**:Agent **空闲**时经 `compactNow`
|
|
34
|
-
(owner `null`,空闲手动入口,引擎自身区间选择)立即压缩;**繁忙**时
|
|
35
|
-
`compactNow` 被拒绝,handler **插入一个 process-local 强制标记**(JS 内存记录,
|
|
36
|
-
无持久态、无 timer),由 `agent/pre-step` 钩子在下一个模型步骤读取。
|
|
37
|
-
读到强制标记时,该步骤**跳过 token 阈值门禁**,按 `retainLatestTokens`
|
|
38
|
-
语义选区(保留最新 N 个 token、头段一次性压缩)并经 `compactRegion`(current-turn owner,可在 mid-turn 执行)执行,并返回 `{ kind: 'reject' }`
|
|
39
|
-
|
|
40
|
-
- **`agent/status`** —— agent 生命周期迁移监听器。当 agent 转入 `idle`
|
|
41
|
-
(所有轮次结束,含子代理,下一次人为对话之前)且 `turnEndForceCompactionEnabled`
|
|
42
|
-
为 `true` 时,经 `compactNow`(owner `null`,空闲手动入口)压缩会话——使用
|
|
43
|
-
引擎自身的区间选择(空闲路径无法选择自定义 token 比例,故无一轮结束比例参数)。
|
|
44
|
-
|
|
45
|
-
支撑模块:
|
|
46
|
-
|
|
47
|
-
- **`src/hooks/guard.js`** —— 每次请求的门禁:`agent/request` 关闭思考 +
|
|
48
|
-
`agent/pre-step` 阈值门禁 + 强制压缩 + `/force-compact` 的 process-local 强制标记。
|
|
49
|
-
- **`src/hooks/command.js`** —— `/force-compact` 斜杠命令:Agent 空闲时经 `compactNow`
|
|
50
|
-
压缩;繁忙时插入强制标记,待下一个模型步骤消费。
|
|
51
|
-
- **`src/hooks/idle.js`** —— 一轮结束强制压缩:`agent/status` 上的 `idle` 监听器,
|
|
52
|
-
经 `compactNow`(引擎自身区间选择)压缩。
|
|
53
|
-
- **`src/engine/region.js`** —— 插件自己的 head-anchored 区间选择:`selectRegion`(检查点
|
|
54
|
-
路径)与 `selectEarliestByTokens`(供 `agent/pre-step` 使用):前者按 surface
|
|
55
|
-
节点数保留最近尾段,且都把区间末端对齐到 `user/message` 边界(始终是一个平衡
|
|
56
|
-
边界)。`idle` / `/force-compact` 路径改用 `compactNow` 的引擎自身区间选择。
|
|
57
|
-
- **`src/engine/summarizer.js`** —— 插件自己的一次性 LLM 摘要器:回放区间消息,把压缩
|
|
58
|
-
指令作为最后一条 user 消息追加,通过 `ctx.llm` 流式生成,返回浓缩后的检查点。
|
|
59
|
-
- **`src/engine/checkpoint.js`** —— 检查点编排器:选区间 → 投影区间消息 → 运行预览 + 收缩
|
|
60
|
-
门禁 → 把持久变更委托给 compaction 服务的 **`compactRegion(start, end,
|
|
61
|
-
agent, signal)`**(经 `ctx.get('compaction')` 实时读取;权威摘要器)。
|
|
90
|
+
插件钩住官方的模型请求 Waterfall,使决策发生在**真正发起模型请求之前**,以及持久化检查点上:
|
|
62
91
|
|
|
63
92
|
```
|
|
64
93
|
agent/request(payload, next) # 每次模型请求
|
|
65
|
-
|
|
66
|
-
return { ...config, reasoningEffort: "off" } # 关闭思考
|
|
94
|
+
disableThinking? -> { ...config, reasoningEffort: "off" }
|
|
67
95
|
|
|
68
|
-
agent/pre-step(payload, next) #
|
|
69
|
-
|
|
70
|
-
否 -> next()
|
|
71
|
-
是 -> compactRegion(head-before-retainLatestTokens, signal)
|
|
72
|
-
|
|
96
|
+
agent/pre-step(payload, next) # 每个模型步骤前
|
|
97
|
+
projectedTokens >= autoThresholdTokens?
|
|
98
|
+
否 -> next() # 放行模型请求
|
|
99
|
+
是 -> compactRegion(head-before-retainLatestTokens, signal)
|
|
100
|
+
return { kind: "reject" } # 本步不请求模型
|
|
73
101
|
|
|
74
|
-
agent/status({ agent, status })
|
|
102
|
+
agent/status({ agent, status }) # 生命周期过渡
|
|
75
103
|
status === "idle" && turnEndForceCompactionEnabled?
|
|
76
|
-
|
|
77
|
-
否 -> 跳过
|
|
104
|
+
-> compactNow(agent, freshSignal) # 回合结束压缩
|
|
78
105
|
|
|
79
106
|
session/flush(session) # 持久化检查点
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
projectRegionMessages() -> 区间消息
|
|
83
|
-
summarizer.summarize() -> 预览 + 收缩门禁
|
|
84
|
-
compaction.compactRegion(start, end, agent, signal)
|
|
85
|
-
null -> 无操作(没有可压缩内容)
|
|
86
|
-
result -> 已将区间压缩为一个摘要节点
|
|
107
|
+
选区 -> 投影消息 -> 预览 + 缩容门禁
|
|
108
|
+
-> compaction.compactRegion(start, end, agent, signal)
|
|
87
109
|
```
|
|
88
110
|
|
|
89
|
-
|
|
111
|
+
支撑模块:
|
|
90
112
|
|
|
91
|
-
|
|
113
|
+
- `src/hooks/guard.js` —— 每请求门禁:关思考 + 阈值门 + 强制标记。
|
|
114
|
+
- `src/hooks/command.js` —— `/force-compact` 命令。
|
|
115
|
+
- `src/hooks/idle.js` —— 回合结束强制压缩。
|
|
116
|
+
- `src/hooks/wire-rewrite.js` —— `llm/stream` wire 补丁,追加 `reasoning_effort:"none"`。
|
|
117
|
+
- `src/engine/region.js` —— 头/尾锚定的选区(含官方工具配对账本)。
|
|
118
|
+
- `src/engine/summarizer.js` —— 一次性 LLM 摘要器(与官方 `compaction-basic` 全面对齐:
|
|
119
|
+
三级 target 解析、前缀缓存对齐、`purpose:'compaction'` 标签、fail-closed finish 分类、
|
|
120
|
+
usage 采集)。
|
|
121
|
+
- `src/engine/builtin.js` —— 内置持久事务(官方 `compaction/*` 词汇)。
|
|
122
|
+
- `src/engine/checkpoint.js` —— 预览 + 缩容门禁 + 委托 compaction 服务。
|
|
123
|
+
- `src/core/projected.js` —— provider 锚定的 `projectedTokens` 读取。
|
|
124
|
+
- `src/core/ui-signal.js` —— liveUI 信令器。
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 安装与验证
|
|
129
|
+
|
|
130
|
+
作为可安装 bundle(推荐):
|
|
92
131
|
|
|
93
132
|
```sh
|
|
94
|
-
# 从
|
|
133
|
+
# 从 npm(已发布):
|
|
134
|
+
npm install @falling-ts/dsh-force-compact
|
|
135
|
+
# 从 git:
|
|
95
136
|
dsh plugin --profile web add github:falling-ts/dsh-force-compact
|
|
96
|
-
#
|
|
137
|
+
# 从本地检出:
|
|
97
138
|
dsh plugin --profile web add ./dsh-force-compact
|
|
98
139
|
```
|
|
99
140
|
|
|
100
|
-
|
|
141
|
+
或从本地检出,以 `--patch` overlay 挂载(不安装):
|
|
101
142
|
|
|
102
143
|
```sh
|
|
103
144
|
dsh web --patch dsh-force-compact/cordis.patch.yml
|
|
104
145
|
```
|
|
105
146
|
|
|
106
|
-
|
|
107
|
-
配置。
|
|
147
|
+
插件已加载 ⟺ `~/.dsh/logs/dsh-force-compact.log` 出现:
|
|
108
148
|
|
|
109
|
-
|
|
149
|
+
```
|
|
150
|
+
[force-compact] debug logging enabled — writing [force-compact] lines to <绝对路径>
|
|
151
|
+
```
|
|
110
152
|
|
|
111
|
-
|
|
112
|
-
挂载它)时,插件会注册 `falling-ts-force-compact` 设置命名空间,使五个参数可从
|
|
113
|
-
`$DSH_HOME/settings.yaml` 配置(`falling-ts-` 前缀用于防止与其他插件的键冲突):
|
|
153
|
+
验证压缩确实发生:
|
|
114
154
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
155
|
+
```
|
|
156
|
+
idle compaction (builtin) shadowed N nodes (~M tokens)
|
|
157
|
+
builtin compaction OK — replaced span seq[A..B] (N nodes, ~K tokens) with a P-char checkpoint
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 配置
|
|
163
|
+
|
|
164
|
+
`$DSH_HOME/settings.yaml`,命名空间 `falling-ts-force-compact`:
|
|
122
165
|
|
|
123
|
-
|
|
166
|
+
| 键 | 类型 | 默认 | 含义 |
|
|
167
|
+
|----|------|------|------|
|
|
168
|
+
| `disableThinking` | boolean | `true` | 每次出站调用关闭模型推理努力(上述两缝)。 |
|
|
169
|
+
| `autoThresholdTokens` | number ≥ 32000 | `32000` | 每请求门禁的投影 token 阈值。越低越激进、上下文越瘦。**下限 32000**(存储值读取时抬升)。 |
|
|
170
|
+
| `retainLatestTokens` | 正整数 ≥ 8000 | `8000` | 逐字保留最新 N tokens;更早内容一次性发给摘要器。**下限 8000**。同时驱动自动门禁与 `/force-compact`。 |
|
|
171
|
+
| `turnEndForceCompactionEnabled` | boolean | `true` | 在 agent `idle` 过渡时压缩。 |
|
|
172
|
+
| `debug` | boolean | `true` | 输出 `[force-compact]` 诊断到插件日志。 |
|
|
173
|
+
| `logFile` | string | `~/.dsh/logs/dsh-force-compact.log` | 诊断输出路径(`~` 展开为用户家目录)。 |
|
|
174
|
+
| `compactionMode` | `'realm' \| 'global'` | `'realm'` | 官方服务解析策略(priority‑1 路径)。 |
|
|
175
|
+
| `builtinEnabled` | boolean | `true` | 内置引擎后备闸门。 |
|
|
176
|
+
| `maxSummaryTokens` | 整数 (1024–200000) | `1024` | 摘要 LLM 调用的 `maxTokens` 上限。 |
|
|
177
|
+
|
|
178
|
+
示例——激进的**本地**配置:
|
|
124
179
|
|
|
125
180
|
```yaml
|
|
126
181
|
falling-ts-force-compact:
|
|
127
182
|
disableThinking: true
|
|
128
|
-
autoThresholdTokens:
|
|
183
|
+
autoThresholdTokens: 40000 # 更早压缩 ⇒ 常驻 prompt 更小
|
|
129
184
|
retainLatestTokens: 8000
|
|
130
185
|
turnEndForceCompactionEnabled: true
|
|
131
186
|
```
|
|
132
187
|
|
|
133
|
-
当 `settings`
|
|
134
|
-
|
|
188
|
+
当 `settings` 服务缺席时,插件回退到相同默认值并照常压缩——该命名空间是可选的,绝不成为硬依赖。
|
|
189
|
+
|
|
190
|
+
### 面向低上下文 llama.cpp 的调参建议
|
|
191
|
+
|
|
192
|
+
用舒适但适中的上下文服务 `Qwen3.8‑27B`,把有效窗口交给插件决定:将 `autoThresholdTokens` 设在
|
|
193
|
+
**明显低于**你服务的上下文,使常驻 prompt 保持小、延迟平稳,而 agent 仍通过被压缩的头段保留
|
|
194
|
+
深层记忆。由于压力按 *投影* token(provider 锚定)度量,阈值会可预测地对应到你 UI 上看到的
|
|
195
|
+
数字。
|
|
196
|
+
|
|
197
|
+
---
|
|
135
198
|
|
|
136
199
|
## 行为说明
|
|
137
200
|
|
|
138
|
-
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
-
|
|
143
|
-
|
|
144
|
-
`
|
|
145
|
-
-
|
|
146
|
-
|
|
147
|
-
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
跳过;若该时序对你重要,可改为监听 `agent/disposed`(其 payload 直接携带
|
|
158
|
-
`Agent`)。
|
|
159
|
-
- 插件自己的摘要器是**预提交预览 + 收缩门禁**;持久摘要内容由 `compaction`
|
|
160
|
-
服务权威生成。
|
|
161
|
-
- 强制压缩门禁在达到阈值时**拒绝所提议的模型步骤**,随后依赖循环以更小的
|
|
162
|
-
上下文重试。若 `compactRegion` 找不到安全区间(例如已无可压缩的有用内容),
|
|
163
|
-
则让请求按原样继续,而非循环。
|
|
164
|
-
- `idle` 与 `/force-compact` 路径使用 `compactNow`(引擎的空闲手动入口),其
|
|
165
|
-
区间选择是引擎自身的(基于 `retainTokens`),而非插件可调比例。插件可调比例
|
|
166
|
-
(`retainLatestTokens` 驱动 `agent/pre-step` 钩子
|
|
167
|
-
(current-turn owner `compactRegion`)遵守。
|
|
168
|
-
- 不注册任何 client/browser UI;插件是纯 Host 插件。参数可通过
|
|
169
|
-
`falling-ts-force-compact` 设置命名空间调参(未来某个动态 client 插件可读取它
|
|
170
|
-
来提供设置页面),并可通过 `[force-compact]` 日志行与持久日志观察。
|
|
201
|
+
- **运行时依赖:** `compaction` 服务(preset 平面 `agent-presets:compaction-basic`)。经
|
|
202
|
+
`ctx.get('compaction')` 实时读取;不可用时强制压缩路径放行、让请求继续。
|
|
203
|
+
- **可选依赖:** `settings` / `tokenMeter` / `commands` / `llm` / `agents` 均经 `ctx.get(...)`
|
|
204
|
+
读取并守卫;缺任一都优雅降级而非崩溃。
|
|
205
|
+
- **每请求读参数:** 参数每次模型请求读取,故改动下次请求即生效、无需重启。
|
|
206
|
+
- **信号:** `agent/*` Waterfall 转发当前 turn 的 signal;`session/flush` 检查点与
|
|
207
|
+
`agent/status` idle 监听器各自新建 `AbortController`。
|
|
208
|
+
- **持久性:** 持久产物为 `compaction/*` 括号事件 + 带 `surfaceOp:replace` 的
|
|
209
|
+
`user/message` 检查点,跨 build 重放安全。
|
|
210
|
+
- **客户端半部:** `web/client.js` 新增设置分区 "强制压缩 / Force Compact",支持实时改值
|
|
211
|
+
(uSES 安全的镜像,无 timer/状态)。
|
|
212
|
+
- **除一处外无 timer:** 唯一有意保留的是 3 s 的 `publishDone` 回落(纯表现层,已在文档声明)。
|
|
213
|
+
其余均为纯监听器 + 一个 process-local `Map` 强制标记。
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
MIT(见 LICENSE)。
|
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.1",
|
|
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
|
/**
|