billion-context-dsh 0.1.3 → 0.1.5
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.en.md +5 -2
- package/README.md +8 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +301 -60
- package/dist/index.js.map +1 -1
- package/dist/messages.d.ts +8 -0
- package/dist/nudge.d.ts +7 -1
- package/dist/region.d.ts +88 -1
- package/dist/state.d.ts +7 -0
- package/dist/system-prompt.d.ts +1 -1
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[English](./README.en.md) | [中文](./README.md)
|
|
4
4
|
|
|
5
5
|
> **⚠️ Beta notice — not for production use**
|
|
6
|
-
> This project (**v0.1.
|
|
6
|
+
> This project (**v0.1.5**) is a work-in-progress beta. The [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) itself is also in **public beta**. **Do not use either in engineering / production environments** — expect breaking changes and rough edges.
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
9
9
|
<strong>Built with gratitude on top of these projects</strong> — please give them a ⭐:
|
|
@@ -50,6 +50,8 @@ This is the DeepSeek Harness port of [billion-context-pi](https://github.com/ran
|
|
|
50
50
|
|
|
51
51
|
## Install
|
|
52
52
|
|
|
53
|
+
> 💡 **Want DeepSeek Harness to install it for you?** This repo itself runs on DSH: hand [docs/INSTALL.md](docs/INSTALL.md) to an agent in a session and it will read the guide, inspect your profile, wire the composition, and verify the mount. Two preconditions: ① the config lives under `~/.dsh`, so you approve one file-permission prompt; ② afterwards ask it to call `acp_status` as proof.
|
|
54
|
+
|
|
53
55
|
```bash
|
|
54
56
|
npm install billion-context-dsh
|
|
55
57
|
```
|
|
@@ -103,6 +105,7 @@ DSH derives every model request from its append-only session log (the *surface*)
|
|
|
103
105
|
| `search_context` | scores block summaries + originals rebuilt from the log |
|
|
104
106
|
| `acp_status` | block ledger + context pressure |
|
|
105
107
|
| block state | in-memory kernel state + **log-rebuilt ledger** (no sidecar files) |
|
|
108
|
+
| tiered distillation (T2/T3) | re-compressing a block's summary node distills that block (tier 2); distilling a tier-2 block yields tier 3. Tier + kernel block ids are persisted to the log, so kernel state rehydrates from the log after a restart and stays distillable |
|
|
106
109
|
|
|
107
110
|
The load-bearing compression guidance (tools, philosophy, summary rules) is registered as a one-time system-prompt section, so nudges stay short. There is deliberately **no automatic summarization**: automatic policy only nudges the model (`compactIfNeeded` returns null).
|
|
108
111
|
|
|
@@ -116,7 +119,7 @@ A walkthrough of the ACP philosophy this project inherits — how active context
|
|
|
116
119
|
|
|
117
120
|
| Tool | What it does |
|
|
118
121
|
| --- | --- |
|
|
119
|
-
| `compress` | Replace a seq range with a dense summary you write (edges auto-balanced to tool-pair boundaries) |
|
|
122
|
+
| `compress` | Replace a seq range with a dense summary you write (edges auto-balanced to tool-pair boundaries); re-compressing a block's summary node distills it (tier 2/3) |
|
|
120
123
|
| `decompress` | Restore a previously compressed block's original content (read-only) |
|
|
121
124
|
| `search_context` | Search compressed block summaries and originals by keyword |
|
|
122
125
|
| `acp_status` | Context usage, compressed blocks, compressible ranges |
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[中文](./README.md) | [English](./README.en.md)
|
|
4
4
|
|
|
5
5
|
> **⚠️ 测试版声明——请勿用于生产环境**
|
|
6
|
-
> 本项目(**v0.1.
|
|
6
|
+
> 本项目(**v0.1.5**)仍处于开发中的测试版。[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 本身也处于**公开测试版**阶段。**请勿将两者用于工程化 / 生产环境**——预期会有破坏性变更与粗糙之处。
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
9
9
|
<strong>衷心感谢以下项目——请给它们一个 ⭐:</strong>
|
|
@@ -50,6 +50,11 @@
|
|
|
50
50
|
|
|
51
51
|
## 安装
|
|
52
52
|
|
|
53
|
+
> 💡 **想让 DeepSeek Harness 帮你装?** 本仓库本身就运行在 DSH 上:把
|
|
54
|
+
> [docs/INSTALL.md](docs/INSTALL.md) 交给会话里的 agent,它会读取指南、解析
|
|
55
|
+
> 你的 profile、编辑组合配置并验证挂载。前提:① 配置写在 `~/.dsh` 下,需要
|
|
56
|
+
> 你批准一次文件权限;② 装完让它调用 `acp_status` 自证。
|
|
57
|
+
|
|
53
58
|
```bash
|
|
54
59
|
npm install billion-context-dsh
|
|
55
60
|
```
|
|
@@ -102,6 +107,7 @@ DSH 的每个模型请求都派生自其 append-only 会话日志(*surface*)
|
|
|
102
107
|
| `search_context` | 对从日志重建的块摘要与原文打分 |
|
|
103
108
|
| `acp_status` | 块账本与上下文压力 |
|
|
104
109
|
| 块状态 | 内存内核状态 + **日志重建账本**(无旁车文件) |
|
|
110
|
+
| 分层蒸馏(T2/T3) | 再次压缩某块的摘要节点 = 蒸馏该块(tier 2),蒸馏 tier-2 块得 tier 3;tier 与内核块 id 持久化进日志,重启后内核状态从日志再水合、可继续蒸馏 |
|
|
105
111
|
|
|
106
112
|
承载性的压缩指引(工具、哲学、摘要规则)注册为一次性系统提示段,因此 nudge 保持简短。刻意**不做自动摘要**:自动策略只 nudge 模型(`compactIfNeeded` 返回 null)。
|
|
107
113
|
|
|
@@ -115,7 +121,7 @@ DSH 的每个模型请求都派生自其 append-only 会话日志(*surface*)
|
|
|
115
121
|
|
|
116
122
|
| 工具 | 作用 |
|
|
117
123
|
| --- | --- |
|
|
118
|
-
| `compress` | 用你书写的紧凑摘要替换 seq 范围(边界自动平衡到 tool-call/result
|
|
124
|
+
| `compress` | 用你书写的紧凑摘要替换 seq 范围(边界自动平衡到 tool-call/result 配对点);对某块的摘要节点再次压缩 = 分层蒸馏(tier 2/3) |
|
|
119
125
|
| `decompress` | 恢复已压缩块的原始内容(只读) |
|
|
120
126
|
| `search_context` | 按关键词搜索压缩块摘要与原文 |
|
|
121
127
|
| `acp_status` | 上下文占用、压缩块、可压缩范围 |
|
package/dist/index.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ export { ACP_SYSTEM_PROMPT, ACP_SYSTEM_PROMPT_ORDER } from './system-prompt.ts';
|
|
|
35
35
|
export { makeTools, type ToolEnvironment } from './tools.ts';
|
|
36
36
|
export { acpCommand } from './commands.ts';
|
|
37
37
|
export { buildNudge, type NudgeEnvironment, type NudgeOutcome } from './nudge.ts';
|
|
38
|
-
export { rebuildBlockLedger, resolveSurfaceRange, runCompactionTransaction, shadowedSeqsOf, findOpenTurn, assertNoActiveCompaction, type AcpBlockLedgerEntry, type CompactionTransactionInput, } from './region.ts';
|
|
38
|
+
export { rebuildBlockLedger, resolveSurfaceRange, runCompactionTransaction, shadowedSeqsOf, findOpenTurn, assertNoActiveCompaction, blockRegistry, blockRefForSummarySeq, compactionIdsOfKernelBlocks, summarySeqOfKernelBlock, expandShadowedSeqs, type AcpBlockLedgerEntry, type CompactionTransactionInput, } from './region.ts';
|
|
39
39
|
export { eventsToCoreMessages, projectEvent, surfaceEventsOf, extractEventText } from './messages.ts';
|
|
40
40
|
export interface AcpConfig {
|
|
41
41
|
/** The context window used for pressure decisions. Default 128000. */
|
package/dist/index.js
CHANGED
|
@@ -7,42 +7,6 @@ import { createCore } from "acp-kernel";
|
|
|
7
7
|
|
|
8
8
|
// src/state.ts
|
|
9
9
|
import { createInitialState } from "acp-kernel";
|
|
10
|
-
var AcpStateStore = class {
|
|
11
|
-
states = /* @__PURE__ */ new Map();
|
|
12
|
-
/** Kernel state for one session, initialised on first access. */
|
|
13
|
-
stateFor(session) {
|
|
14
|
-
const id = session.id;
|
|
15
|
-
const existing = this.states.get(id);
|
|
16
|
-
if (existing !== void 0) return existing;
|
|
17
|
-
const state = createInitialState();
|
|
18
|
-
this.states.set(id, state);
|
|
19
|
-
return state;
|
|
20
|
-
}
|
|
21
|
-
set(session, state) {
|
|
22
|
-
this.states.set(session.id, state);
|
|
23
|
-
}
|
|
24
|
-
delete(session) {
|
|
25
|
-
this.states.delete(session.id);
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
// src/tools.ts
|
|
30
|
-
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
31
|
-
import { defaultCountTokens as defaultCountTokens2 } from "acp-kernel";
|
|
32
|
-
|
|
33
|
-
// src/config.ts
|
|
34
|
-
import { defaultConfig } from "acp-kernel";
|
|
35
|
-
function kernelConfigFor(input) {
|
|
36
|
-
const nudgePatch = {};
|
|
37
|
-
if (input.nudgeMinContextLimitPct !== void 0) nudgePatch.minContextLimitPct = input.nudgeMinContextLimitPct;
|
|
38
|
-
if (input.nudgeMaxContextLimitPct !== void 0) nudgePatch.maxContextLimitPct = input.nudgeMaxContextLimitPct;
|
|
39
|
-
if (input.nudgeEmergencyThresholdPct !== void 0) nudgePatch.emergencyThresholdPct = input.nudgeEmergencyThresholdPct;
|
|
40
|
-
const overrides = { ...input.coreOverrides };
|
|
41
|
-
if (Object.keys(nudgePatch).length > 0) {
|
|
42
|
-
overrides.nudge = { ...defaultConfig(input.modelContextLimit).nudge, ...nudgePatch };
|
|
43
|
-
}
|
|
44
|
-
return defaultConfig(input.modelContextLimit, overrides);
|
|
45
|
-
}
|
|
46
10
|
|
|
47
11
|
// src/region.ts
|
|
48
12
|
import { randomUUID } from "crypto";
|
|
@@ -145,6 +109,9 @@ function eventsToCoreMessages(events) {
|
|
|
145
109
|
function surfaceEventsOf(session) {
|
|
146
110
|
return session.surface.nodes.map((seq) => session.events[seq]).filter((event) => event !== void 0);
|
|
147
111
|
}
|
|
112
|
+
function allLogMessages(session) {
|
|
113
|
+
return eventsToCoreMessages(session.events);
|
|
114
|
+
}
|
|
148
115
|
function extractEventText(event) {
|
|
149
116
|
switch (event.type) {
|
|
150
117
|
case "user/message":
|
|
@@ -245,6 +212,9 @@ function shadowedSeqsOf(session, start, end) {
|
|
|
245
212
|
const endIdx = nodes.indexOf(end);
|
|
246
213
|
return nodes.slice(startIdx, endIdx + 1);
|
|
247
214
|
}
|
|
215
|
+
function readCompactionSummary(event) {
|
|
216
|
+
return event.data;
|
|
217
|
+
}
|
|
248
218
|
function runCompactionTransaction(session, input) {
|
|
249
219
|
assertNoActiveCompaction(session.events);
|
|
250
220
|
const turn = findOpenTurn(session.events);
|
|
@@ -258,7 +228,12 @@ function runCompactionTransaction(session, input) {
|
|
|
258
228
|
shadowedSeqs: [...input.shadowedSeqs],
|
|
259
229
|
shadowedTokenCount: input.shadowedTokenCount,
|
|
260
230
|
provider: input.provider,
|
|
261
|
-
model: input.model
|
|
231
|
+
model: input.model,
|
|
232
|
+
tier: input.tier ?? 1,
|
|
233
|
+
...input.kernelBlockId === void 0 ? {} : { kernelBlockId: input.kernelBlockId },
|
|
234
|
+
...input.parentBlockIds === void 0 || input.parentBlockIds.length === 0 ? {} : { parentBlockIds: [...input.parentBlockIds] },
|
|
235
|
+
...input.directMessageIds === void 0 ? {} : { directMessageIds: [...input.directMessageIds] },
|
|
236
|
+
...input.effectiveMessageIds === void 0 ? {} : { effectiveMessageIds: [...input.effectiveMessageIds] }
|
|
262
237
|
}).seq);
|
|
263
238
|
const message = createUserMessage({
|
|
264
239
|
content: input.summary,
|
|
@@ -271,11 +246,19 @@ function runCompactionTransaction(session, input) {
|
|
|
271
246
|
seqs.push(session.append("compaction/end", { compactionId, turn }).seq);
|
|
272
247
|
return { compactionId, seqs };
|
|
273
248
|
}
|
|
249
|
+
function summarySeqOfCompaction(events, compactionId) {
|
|
250
|
+
for (const event of events) {
|
|
251
|
+
if (event.type !== "user/message") continue;
|
|
252
|
+
const source = event.data.source;
|
|
253
|
+
if (source?.plugin === "compact" && source.compactionId === compactionId) return event.seq;
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
274
257
|
function rebuildBlockLedger(events) {
|
|
275
258
|
const ledger = [];
|
|
276
259
|
for (const event of events) {
|
|
277
260
|
if (event.type !== "compaction/summary") continue;
|
|
278
|
-
const data = event
|
|
261
|
+
const data = readCompactionSummary(event);
|
|
279
262
|
let shadowedTokenCount = data.shadowedTokenCount;
|
|
280
263
|
if (shadowedTokenCount === 0) {
|
|
281
264
|
shadowedTokenCount = 0;
|
|
@@ -284,13 +267,25 @@ function rebuildBlockLedger(events) {
|
|
|
284
267
|
if (original !== void 0) shadowedTokenCount += defaultCountTokens(extractEventText(original));
|
|
285
268
|
}
|
|
286
269
|
}
|
|
270
|
+
const tier = data.tier === 2 || data.tier === 3 ? data.tier : 1;
|
|
271
|
+
const parentBlockIds = Array.isArray(data.parentBlockIds) ? [...data.parentBlockIds] : [];
|
|
272
|
+
const directMessageIds = Array.isArray(data.directMessageIds) ? [...data.directMessageIds] : void 0;
|
|
273
|
+
const effectiveMessageIds = Array.isArray(data.effectiveMessageIds) ? [...data.effectiveMessageIds] : void 0;
|
|
274
|
+
const summarySeq = summarySeqOfCompaction(events, data.compactionId);
|
|
287
275
|
ledger.push({
|
|
288
276
|
blockId: data.compactionId,
|
|
289
277
|
summary: extractText(data.summary),
|
|
290
278
|
shadowedSeqs: [...data.shadowedSeqs],
|
|
291
279
|
shadowedTokenCount,
|
|
292
280
|
start: data.shadowedRange.start,
|
|
293
|
-
end: data.shadowedRange.end
|
|
281
|
+
end: data.shadowedRange.end,
|
|
282
|
+
tier,
|
|
283
|
+
parentBlockIds,
|
|
284
|
+
...typeof data.kernelBlockId === "string" ? { kernelBlockId: data.kernelBlockId } : {},
|
|
285
|
+
...summarySeq === null ? {} : { summarySeq },
|
|
286
|
+
...directMessageIds === void 0 ? {} : { directMessageIds },
|
|
287
|
+
...effectiveMessageIds === void 0 ? {} : { effectiveMessageIds },
|
|
288
|
+
createdAt: event.time
|
|
294
289
|
});
|
|
295
290
|
}
|
|
296
291
|
return ledger;
|
|
@@ -354,6 +349,184 @@ function surfaceSummary(session) {
|
|
|
354
349
|
const last = nodes[nodes.length - 1];
|
|
355
350
|
return `${nodes.length} nodes, seqs ${first}..${last}`;
|
|
356
351
|
}
|
|
352
|
+
function blockRegistry(session) {
|
|
353
|
+
const ledger = rebuildBlockLedger(session.events);
|
|
354
|
+
const kernelIdOf = /* @__PURE__ */ new Map();
|
|
355
|
+
const raw = [];
|
|
356
|
+
let next = 1;
|
|
357
|
+
for (const entry of ledger) {
|
|
358
|
+
let kernelBlockId;
|
|
359
|
+
if (entry.kernelBlockId !== void 0 && /^b\d+$/.test(entry.kernelBlockId)) {
|
|
360
|
+
kernelBlockId = entry.kernelBlockId;
|
|
361
|
+
const num = Number(kernelBlockId.slice(1));
|
|
362
|
+
if (Number.isInteger(num)) next = Math.max(next, num + 1);
|
|
363
|
+
} else {
|
|
364
|
+
kernelBlockId = `b${next}`;
|
|
365
|
+
next += 1;
|
|
366
|
+
}
|
|
367
|
+
kernelIdOf.set(entry.blockId, kernelBlockId);
|
|
368
|
+
raw.push({
|
|
369
|
+
blockId: entry.blockId,
|
|
370
|
+
kernelBlockId,
|
|
371
|
+
tier: entry.tier,
|
|
372
|
+
summarySeq: entry.summarySeq ?? null,
|
|
373
|
+
active: true,
|
|
374
|
+
parentBlockIds: [...entry.parentBlockIds]
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
378
|
+
for (const entry of raw) {
|
|
379
|
+
for (const parent of entry.parentBlockIds) consumed.add(parent);
|
|
380
|
+
}
|
|
381
|
+
return raw.map((entry) => ({
|
|
382
|
+
...entry,
|
|
383
|
+
active: !consumed.has(entry.blockId)
|
|
384
|
+
}));
|
|
385
|
+
}
|
|
386
|
+
function blockRefForSummarySeq(session, seq) {
|
|
387
|
+
const event = session.events[seq];
|
|
388
|
+
if (event?.type !== "user/message") return null;
|
|
389
|
+
const source = event.data.source;
|
|
390
|
+
if (source?.plugin !== "compact" || source.compactionId === void 0) return null;
|
|
391
|
+
const entry = blockRegistry(session).find((r) => r.blockId === source.compactionId);
|
|
392
|
+
if (entry === void 0) return null;
|
|
393
|
+
return entry.kernelBlockId;
|
|
394
|
+
}
|
|
395
|
+
function compactionIdsOfKernelBlocks(session, kernelBlockIds) {
|
|
396
|
+
if (kernelBlockIds.length === 0) return [];
|
|
397
|
+
const byKernel = new Map(blockRegistry(session).map((r) => [r.kernelBlockId, r.blockId]));
|
|
398
|
+
return kernelBlockIds.map((id) => byKernel.get(id)).filter((id) => id !== void 0);
|
|
399
|
+
}
|
|
400
|
+
function summarySeqOfKernelBlock(session, kernelBlockId) {
|
|
401
|
+
const entry = blockRegistry(session).find((r) => r.kernelBlockId === kernelBlockId);
|
|
402
|
+
return entry?.active ? entry.summarySeq : null;
|
|
403
|
+
}
|
|
404
|
+
function checkpointBlockIdOf(events, seq) {
|
|
405
|
+
const event = events[seq];
|
|
406
|
+
if (event?.type !== "user/message") return null;
|
|
407
|
+
const source = event.data.source;
|
|
408
|
+
if (source?.plugin !== "compact" || source.compactionId === void 0) return null;
|
|
409
|
+
return source.compactionId;
|
|
410
|
+
}
|
|
411
|
+
function expandShadowedSeqs(session, blockId) {
|
|
412
|
+
const ledger = rebuildBlockLedger(session.events);
|
|
413
|
+
const byId = new Map(ledger.map((entry) => [entry.blockId, entry]));
|
|
414
|
+
const root = byId.get(blockId);
|
|
415
|
+
if (root === void 0) return [];
|
|
416
|
+
const out = [];
|
|
417
|
+
const seen = /* @__PURE__ */ new Set();
|
|
418
|
+
const visit = (entry) => {
|
|
419
|
+
if (seen.has(entry.blockId)) return;
|
|
420
|
+
seen.add(entry.blockId);
|
|
421
|
+
for (const seq of entry.shadowedSeqs) {
|
|
422
|
+
const childId = checkpointBlockIdOf(session.events, seq);
|
|
423
|
+
const child = childId === null ? void 0 : byId.get(childId);
|
|
424
|
+
if (child !== void 0) visit(child);
|
|
425
|
+
else out.push(seq);
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
visit(root);
|
|
429
|
+
return out;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/state.ts
|
|
433
|
+
function rebuildKernelBlocks(events) {
|
|
434
|
+
const ledger = rebuildBlockLedger(events);
|
|
435
|
+
if (ledger.length === 0) return [];
|
|
436
|
+
const kernelIdOf = /* @__PURE__ */ new Map();
|
|
437
|
+
const parentKernelIds = /* @__PURE__ */ new Map();
|
|
438
|
+
let next = 1;
|
|
439
|
+
for (const entry of ledger) {
|
|
440
|
+
let kernelBlockId;
|
|
441
|
+
if (entry.kernelBlockId !== void 0 && /^b\d+$/.test(entry.kernelBlockId)) {
|
|
442
|
+
kernelBlockId = entry.kernelBlockId;
|
|
443
|
+
const num = Number(kernelBlockId.slice(1));
|
|
444
|
+
if (Number.isInteger(num)) next = Math.max(next, num + 1);
|
|
445
|
+
} else {
|
|
446
|
+
kernelBlockId = `b${next}`;
|
|
447
|
+
next += 1;
|
|
448
|
+
}
|
|
449
|
+
kernelIdOf.set(entry.blockId, kernelBlockId);
|
|
450
|
+
parentKernelIds.set(
|
|
451
|
+
entry.blockId,
|
|
452
|
+
entry.parentBlockIds.map((parent) => kernelIdOf.get(parent)).filter((id) => id !== void 0)
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
456
|
+
for (const entry of ledger) {
|
|
457
|
+
for (const parent of entry.parentBlockIds) consumed.add(parent);
|
|
458
|
+
}
|
|
459
|
+
const blocks = [];
|
|
460
|
+
for (const entry of ledger) {
|
|
461
|
+
const blockId = kernelIdOf.get(entry.blockId);
|
|
462
|
+
const direct = entry.directMessageIds ?? [...entry.shadowedSeqs.map(String)];
|
|
463
|
+
const effective = entry.effectiveMessageIds ?? (entry.tier > 1 ? entry.summarySeq === void 0 ? [...entry.shadowedSeqs.map(String)] : [String(entry.summarySeq)] : [...entry.shadowedSeqs.map(String)]);
|
|
464
|
+
blocks.push({
|
|
465
|
+
blockId,
|
|
466
|
+
runId: `r${blocks.length + 1}`,
|
|
467
|
+
tier: entry.tier,
|
|
468
|
+
summary: entry.summary,
|
|
469
|
+
directMessageIds: [...direct],
|
|
470
|
+
effectiveMessageIds: [...effective],
|
|
471
|
+
directBlockIds: parentKernelIds.get(entry.blockId) ?? [],
|
|
472
|
+
compressedTokens: entry.shadowedTokenCount,
|
|
473
|
+
createdAt: entry.createdAt,
|
|
474
|
+
survivedCount: 0,
|
|
475
|
+
generation: "young",
|
|
476
|
+
active: !consumed.has(entry.blockId)
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
return blocks;
|
|
480
|
+
}
|
|
481
|
+
function nextBlockIdAfter(events) {
|
|
482
|
+
const blocks = rebuildKernelBlocks(events);
|
|
483
|
+
let max = 0;
|
|
484
|
+
for (const block of blocks) {
|
|
485
|
+
const num = Number(block.blockId.slice(1));
|
|
486
|
+
if (Number.isInteger(num)) max = Math.max(max, num);
|
|
487
|
+
}
|
|
488
|
+
return max + 1;
|
|
489
|
+
}
|
|
490
|
+
var AcpStateStore = class {
|
|
491
|
+
states = /* @__PURE__ */ new Map();
|
|
492
|
+
/** Kernel state for one session, initialised on first access. */
|
|
493
|
+
stateFor(session) {
|
|
494
|
+
const id = session.id;
|
|
495
|
+
const existing = this.states.get(id);
|
|
496
|
+
if (existing !== void 0) return existing;
|
|
497
|
+
const state = createInitialState();
|
|
498
|
+
if (session.events.some((event) => event.type === "compaction/summary")) {
|
|
499
|
+
state.blocks = rebuildKernelBlocks(session.events);
|
|
500
|
+
state.nextBlockId = nextBlockIdAfter(session.events);
|
|
501
|
+
}
|
|
502
|
+
this.states.set(id, state);
|
|
503
|
+
return state;
|
|
504
|
+
}
|
|
505
|
+
set(session, state) {
|
|
506
|
+
this.states.set(session.id, state);
|
|
507
|
+
}
|
|
508
|
+
delete(session) {
|
|
509
|
+
this.states.delete(session.id);
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
// src/tools.ts
|
|
514
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
515
|
+
import { defaultCountTokens as defaultCountTokens2 } from "acp-kernel";
|
|
516
|
+
|
|
517
|
+
// src/config.ts
|
|
518
|
+
import { defaultConfig } from "acp-kernel";
|
|
519
|
+
function kernelConfigFor(input) {
|
|
520
|
+
const nudgePatch = {};
|
|
521
|
+
if (input.nudgeMinContextLimitPct !== void 0) nudgePatch.minContextLimitPct = input.nudgeMinContextLimitPct;
|
|
522
|
+
if (input.nudgeMaxContextLimitPct !== void 0) nudgePatch.maxContextLimitPct = input.nudgeMaxContextLimitPct;
|
|
523
|
+
if (input.nudgeEmergencyThresholdPct !== void 0) nudgePatch.emergencyThresholdPct = input.nudgeEmergencyThresholdPct;
|
|
524
|
+
const overrides = { ...input.coreOverrides };
|
|
525
|
+
if (Object.keys(nudgePatch).length > 0) {
|
|
526
|
+
overrides.nudge = { ...defaultConfig(input.modelContextLimit).nudge, ...nudgePatch };
|
|
527
|
+
}
|
|
528
|
+
return defaultConfig(input.modelContextLimit, overrides);
|
|
529
|
+
}
|
|
357
530
|
|
|
358
531
|
// src/tools.ts
|
|
359
532
|
function textOutput() {
|
|
@@ -412,8 +585,9 @@ async function handleCompress(env, args, exec) {
|
|
|
412
585
|
const agent = requireAgent(exec);
|
|
413
586
|
const session = agent.session;
|
|
414
587
|
const state = env.store.stateFor(session);
|
|
415
|
-
const coreMessages =
|
|
416
|
-
const
|
|
588
|
+
const coreMessages = allLogMessages(session);
|
|
589
|
+
const surfaceMessages = eventsToCoreMessages(surfaceEventsOf(session));
|
|
590
|
+
const tokenCount = surfaceMessages.reduce((sum, message) => sum + defaultCountTokens2(message.text ?? ""), 0);
|
|
417
591
|
const config = kernelConfigFor(env);
|
|
418
592
|
const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount });
|
|
419
593
|
env.store.set(session, turn.state);
|
|
@@ -422,8 +596,10 @@ async function handleCompress(env, args, exec) {
|
|
|
422
596
|
const startSeq = parseSeq(range.startSeq);
|
|
423
597
|
const endSeq = parseSeq(range.endSeq);
|
|
424
598
|
const { start, end } = resolveSurfaceRange(session, startSeq, endSeq);
|
|
425
|
-
const
|
|
426
|
-
const
|
|
599
|
+
const startBlockRef = blockRefForSummarySeq(session, start);
|
|
600
|
+
const endBlockRef = blockRefForSummarySeq(session, end);
|
|
601
|
+
const startRef = startBlockRef ?? byRaw[String(start)];
|
|
602
|
+
const endRef = endBlockRef ?? byRaw[String(end)];
|
|
427
603
|
if (startRef === void 0 || endRef === void 0) {
|
|
428
604
|
throw new Error(
|
|
429
605
|
`billion-context-dsh: seq ${start}..${end} has no assigned ref \u2014 the range must be on the current surface (run acp_status for the live seq list)`
|
|
@@ -445,15 +621,44 @@ async function handleCompress(env, args, exec) {
|
|
|
445
621
|
messages: coreMessages,
|
|
446
622
|
state: turn.state,
|
|
447
623
|
config
|
|
624
|
+
// Deliberately NOT overriding protectedMessageIds: with the full log the
|
|
625
|
+
// kernel's recent/last-user protection is computed over the same
|
|
626
|
+
// non-block-covered messages as the visible feed, so default behavior is
|
|
627
|
+
// preserved. Any 'Excluded N protected message(s)' warning is surfaced.
|
|
448
628
|
});
|
|
449
629
|
if (applied.result.errors.length > 0) {
|
|
450
630
|
return { text: `compress failed: ${applied.result.errors.join("; ")}` };
|
|
451
631
|
}
|
|
452
632
|
env.store.set(session, applied.state);
|
|
633
|
+
const previousIds = new Set(turn.state.blocks.map((block) => block.blockId));
|
|
634
|
+
const newBlocks = applied.state.blocks.filter((block) => !previousIds.has(block.blockId));
|
|
635
|
+
const blockByRangeKey = new Map(newBlocks.map((block) => [`${block.startRef}::${block.endRef}`, block]));
|
|
636
|
+
const warningByRangeKey = /* @__PURE__ */ new Map();
|
|
637
|
+
const freeWarnings = [];
|
|
638
|
+
for (const warning of applied.result.warnings) {
|
|
639
|
+
const match = /^Skipped range \((.+?)\.\.(.+?)\)/.exec(warning);
|
|
640
|
+
if (match !== null) {
|
|
641
|
+
const key = `${match[1]}::${match[2]}`;
|
|
642
|
+
const list = warningByRangeKey.get(key) ?? [];
|
|
643
|
+
list.push(warning);
|
|
644
|
+
warningByRangeKey.set(key, list);
|
|
645
|
+
} else {
|
|
646
|
+
freeWarnings.push(warning);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
453
649
|
const lines = [];
|
|
650
|
+
let skippedRanges = 0;
|
|
454
651
|
for (let index = 0; index < ranges.length; index += 1) {
|
|
455
652
|
const range = ranges[index];
|
|
456
653
|
const original = args.content[index];
|
|
654
|
+
const key = `${range.startRef}::${range.endRef}`;
|
|
655
|
+
const block = blockByRangeKey.get(key);
|
|
656
|
+
if (block === void 0) {
|
|
657
|
+
skippedRanges += 1;
|
|
658
|
+
const warnings = warningByRangeKey.get(key) ?? [];
|
|
659
|
+
for (const warning of warnings) lines.push(` ${warning}`);
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
457
662
|
const { start, end } = range;
|
|
458
663
|
const shadowed = shadowedSeqsOf(session, start, end);
|
|
459
664
|
let shadowedTokens = 0;
|
|
@@ -461,6 +666,8 @@ async function handleCompress(env, args, exec) {
|
|
|
461
666
|
const event = session.events[seq];
|
|
462
667
|
if (event !== void 0) shadowedTokens += defaultCountTokens2(extractEventText(event));
|
|
463
668
|
}
|
|
669
|
+
const tier = block.tier === 2 || block.tier === 3 ? block.tier : 1;
|
|
670
|
+
const parentBlockIds = compactionIdsOfKernelBlocks(session, block.directBlockIds);
|
|
464
671
|
const { compactionId } = runCompactionTransaction(session, {
|
|
465
672
|
start,
|
|
466
673
|
end,
|
|
@@ -468,17 +675,27 @@ async function handleCompress(env, args, exec) {
|
|
|
468
675
|
summary: [{ type: "text", text: original.summary }],
|
|
469
676
|
shadowedTokenCount: shadowedTokens,
|
|
470
677
|
provider: agent.options.provider ?? "",
|
|
471
|
-
model: agent.options.model ?? ""
|
|
678
|
+
model: agent.options.model ?? "",
|
|
679
|
+
tier,
|
|
680
|
+
kernelBlockId: block.blockId,
|
|
681
|
+
...parentBlockIds.length === 0 ? {} : { parentBlockIds },
|
|
682
|
+
// Record the kernel block's raw coverage so a restarted engine
|
|
683
|
+
// rehydrates the SAME effective messages (a tier-2 block's coverage is
|
|
684
|
+
// its parents' originals, not the checkpoint node).
|
|
685
|
+
directMessageIds: block.directMessageIds,
|
|
686
|
+
effectiveMessageIds: block.effectiveMessageIds
|
|
472
687
|
});
|
|
473
688
|
const adjusted = start !== range.startSeq || end !== range.endSeq;
|
|
689
|
+
const tierLabel = tier === 1 ? "" : `, tier ${tier}`;
|
|
474
690
|
lines.push(
|
|
475
|
-
` block ${compactionId.slice(0, 8)}: seqs ${start}..${end}, ${shadowed.length} messages shadowed` + (adjusted ? ` (adjusted from ${range.startSeq}..${range.endSeq} to balanced edges)` : "")
|
|
691
|
+
` block ${compactionId.slice(0, 8)}: seqs ${start}..${end}, ${shadowed.length} messages shadowed${tierLabel}` + (adjusted ? ` (adjusted from ${range.startSeq}..${range.endSeq} to balanced edges)` : "")
|
|
476
692
|
);
|
|
477
693
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
${
|
|
481
|
-
}
|
|
694
|
+
const summaryLine = `Compressed ${applied.result.blocksCreated} block(s), ~${applied.result.tokensCompressed} tokens reclaimed.`;
|
|
695
|
+
const warningLines = [...freeWarnings.map((warning) => ` ${warning}`), ...lines];
|
|
696
|
+
const footer = skippedRanges > 0 ? ` (${skippedRanges} range(s) skipped \u2014 see warnings above)` : "";
|
|
697
|
+
return { text: `${summaryLine}
|
|
698
|
+
${[...warningLines, footer].filter((line) => line !== "").join("\n")}` };
|
|
482
699
|
}
|
|
483
700
|
var decompressParameters = {
|
|
484
701
|
blockId: { type: "string", required: true, description: "Block id from acp_status or search_context (the compaction id)." }
|
|
@@ -491,13 +708,14 @@ function handleDecompress(_env, args, exec) {
|
|
|
491
708
|
return { text: `decompress: block "${args.blockId}" not found (see acp_status for the block list)` };
|
|
492
709
|
}
|
|
493
710
|
const parts = [];
|
|
494
|
-
for (const seq of block.
|
|
711
|
+
for (const seq of expandShadowedSeqs(session, block.blockId)) {
|
|
495
712
|
const event = session.events[seq];
|
|
496
713
|
const text = event === void 0 ? "" : extractEventText(event);
|
|
497
714
|
if (text.length > 0) parts.push(`[seq ${seq}] ${text}`);
|
|
498
715
|
}
|
|
716
|
+
const tierNote = block.tier > 1 ? ` (tier ${block.tier}, distills ${block.parentBlockIds.length} block(s))` : "";
|
|
499
717
|
return {
|
|
500
|
-
text: `Block ${block.blockId} \u2014 ${block.summary}
|
|
718
|
+
text: `Block ${block.blockId} \u2014 ${block.summary}${tierNote}
|
|
501
719
|
|
|
502
720
|
${parts.join("\n\n") || "(no recoverable content)"}`
|
|
503
721
|
};
|
|
@@ -551,7 +769,7 @@ function makeTools(env) {
|
|
|
551
769
|
return [
|
|
552
770
|
defineTool({
|
|
553
771
|
name: "compress",
|
|
554
|
-
description: "Replace older conversation ranges with dense summaries you write. Each message seq is a surface reference. Single range: compress({ content: [{ startSeq, endSeq, summary }] }). Batch multiple unrelated ranges in one call. Never compress content the current step is actively using.",
|
|
772
|
+
description: "Replace older conversation ranges with dense summaries you write. Each message seq is a surface reference. Single range: compress({ content: [{ startSeq, endSeq, summary }] }). Batch multiple unrelated ranges in one call (each content entry becomes its own block); keep ranges disjoint. Never compress content the current step is actively using.",
|
|
555
773
|
parameters: compressParameters,
|
|
556
774
|
output: textOutput(),
|
|
557
775
|
async execute(args, exec) {
|
|
@@ -604,7 +822,8 @@ function statusText(env, agent) {
|
|
|
604
822
|
` estimated context: ${estimated} / ${limit} (${Math.round(estimated / limit * 100)}%)`
|
|
605
823
|
];
|
|
606
824
|
for (const block of ledger.slice(0, 10)) {
|
|
607
|
-
|
|
825
|
+
const tier = block.tier > 1 ? ` [T${block.tier}]` : "";
|
|
826
|
+
lines.push(` - ${block.blockId.slice(0, 8)}${tier}: seqs ${block.start}..${block.end} \u2014 ${block.summary.slice(0, 80)}`);
|
|
608
827
|
}
|
|
609
828
|
return lines.join("\n");
|
|
610
829
|
}
|
|
@@ -620,6 +839,9 @@ function compressText(env, agent, args) {
|
|
|
620
839
|
}
|
|
621
840
|
const session = agent.session;
|
|
622
841
|
const { start, end } = resolveSurfaceRange(session, startSeq, endSeq);
|
|
842
|
+
if (blockRefForSummarySeq(session, start) !== null || blockRefForSummarySeq(session, end) !== null) {
|
|
843
|
+
return "/acp compress: the range touches a compressed block summary node \u2014 distill it with the compress tool (seq-based batch), not /acp compress";
|
|
844
|
+
}
|
|
623
845
|
const shadowed = shadowedSeqsOf(session, startSeq, endSeq);
|
|
624
846
|
let shadowedTokens = 0;
|
|
625
847
|
for (const seq of shadowed) {
|
|
@@ -643,7 +865,7 @@ function decompressText(_env, agent, args) {
|
|
|
643
865
|
const ledger = rebuildBlockLedger(session.events);
|
|
644
866
|
const block = ledger.find((entry) => entry.blockId.startsWith(args[0]));
|
|
645
867
|
if (block === void 0) return `block "${args[0]}" not found (see /acp status)`;
|
|
646
|
-
const parts = block.
|
|
868
|
+
const parts = expandShadowedSeqs(session, block.blockId).map((seq) => extractEventText(session.events[seq])).filter((text) => text.length > 0);
|
|
647
869
|
return `Block ${block.blockId} \u2014 ${block.summary}
|
|
648
870
|
|
|
649
871
|
${parts.join("\n\n") || "(no recoverable content)"}`;
|
|
@@ -680,9 +902,9 @@ function rangeTable(session) {
|
|
|
680
902
|
return [
|
|
681
903
|
"",
|
|
682
904
|
`Surface: ${surfaceSummary(session)}`,
|
|
683
|
-
"Compressible ranges (refs are surface seqs):",
|
|
905
|
+
"Compressible ranges (suggestions only \u2014 compress any consumed span; refs are surface seqs):",
|
|
684
906
|
...lines,
|
|
685
|
-
"Compress with: compress({ content: [{ startSeq, endSeq, summary }] })"
|
|
907
|
+
"Compress with: compress({ content: [{ startSeq, endSeq, summary }] }) \u2014 content is an array: batch multiple unrelated segments in one call, each entry its own block. Keep ranges disjoint."
|
|
686
908
|
].join("\n");
|
|
687
909
|
}
|
|
688
910
|
function measuredTokenCount(agent, coreMessages) {
|
|
@@ -694,8 +916,9 @@ function measuredTokenCount(agent, coreMessages) {
|
|
|
694
916
|
function buildNudge(agent, env, lastNudgeTurn) {
|
|
695
917
|
const session = agent.session;
|
|
696
918
|
const state = env.store.stateFor(session);
|
|
697
|
-
const coreMessages =
|
|
698
|
-
const
|
|
919
|
+
const coreMessages = allLogMessages(session);
|
|
920
|
+
const surfaceMessages = eventsToCoreMessages(surfaceEventsOf(session));
|
|
921
|
+
const tokenCount = measuredTokenCount(agent, surfaceMessages);
|
|
699
922
|
const config = kernelConfigFor(env);
|
|
700
923
|
const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount });
|
|
701
924
|
env.store.set(session, turn.state);
|
|
@@ -717,7 +940,18 @@ function buildNudgeText(nudge, emergency, session) {
|
|
|
717
940
|
const pct = Math.round(Math.min(nudge.contextUsage, 1) * 100);
|
|
718
941
|
const frame = emergency ? `\u26A0\uFE0F Context usage is at ${pct}% of the window \u2014 nearly full. Consider compressing consumed ranges soon so working context stays available; the choice and timing are yours.` : `Context usage is at ${pct}%. This is a suggestion, not a requirement \u2014 you decide whether and when to compress.`;
|
|
719
942
|
const guidance = "Compress by need, not by percentage: replace only ranges you have genuinely consumed, with dense self-contained summaries.";
|
|
720
|
-
|
|
943
|
+
const parts = [frame, "", guidance];
|
|
944
|
+
if ((nudge.tier === 2 || nudge.tier === 3) && (nudge.tierTargetBlocks?.length ?? 0) > 0) {
|
|
945
|
+
const targets = nudge.tierTargetBlocks;
|
|
946
|
+
const summarySeqs = targets.map((block) => summarySeqOfKernelBlock(session, block.blockId)).filter((seq) => seq !== null);
|
|
947
|
+
const pending = nudge.tier === 2 ? nudge.breakdown?.pendingT2 : nudge.breakdown?.pendingT3;
|
|
948
|
+
const tokens = typeof pending === "number" ? pending : 0;
|
|
949
|
+
parts.push(
|
|
950
|
+
`Tier ${nudge.tier}: ${targets.length} tier-${nudge.tier - 1} block(s) distillable (${tokens} tokens) \u2014 compress their summary node(s) [seqs ${summarySeqs.join(", ")}] to reclaim the original messages.`
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
parts.push(rangeTable(session));
|
|
954
|
+
return parts.join("\n");
|
|
721
955
|
}
|
|
722
956
|
|
|
723
957
|
// src/system-prompt.ts
|
|
@@ -729,11 +963,13 @@ YOU decide whether and when to compress context. Nothing forces you: the injecte
|
|
|
729
963
|
${COMPRESS_PHILOSOPHY}
|
|
730
964
|
|
|
731
965
|
Compression tools (refs are SURFACE SEQS, not ids):
|
|
732
|
-
- compress: replace
|
|
966
|
+
- compress: replace one or more seq ranges, each with your own dense summary. Single range: compress({ content: [{ startSeq, endSeq, summary }] }). Batch multiple unrelated segments in one call (each entry becomes its own block): compress({ content: [{ startSeq: 1, endSeq: 5, summary: '...' }, { startSeq: 12, endSeq: 18, summary: '...' }] }). Keep ranges disjoint \u2014 overlapping entries in one batch are skipped. Edges are auto-balanced to tool-call/result boundaries; a trailing #callId fragment in a seq is ignored. Ranges must be on the current surface \u2014 stale seqs fail with guidance.
|
|
733
967
|
- decompress: recover a compressed block's original content, read-only. decompress({ blockId }).
|
|
734
968
|
- search_context: find information inside compressed blocks BEFORE decompressing. search_context({ query }).
|
|
735
969
|
- acp_status: current context usage and the live compressible-range list. Run it before compressing when in doubt.
|
|
736
970
|
|
|
971
|
+
Tiered compression: each compressed block appears on the surface as one summary node. Compressing that node again DISTILLS the block (tier 2): the parent summary folds into your new summary and the original messages are freed. Distilling a tier-2 block yields tier 3. Distill when a summary itself is consumed \u2014 decompress on the tier-2 block recovers the full originals.
|
|
972
|
+
|
|
737
973
|
When you write a summary, it becomes the ONLY record of that range: keep file paths, signatures, exact values, decisions, and error strings verbatim so a later reader (or you, after decompress) can continue without the original. Never reuse historical seqs \u2014 the surface moves as messages land and compress; verify with acp_status.`;
|
|
738
974
|
var ACP_SYSTEM_PROMPT_ORDER = 150;
|
|
739
975
|
|
|
@@ -850,9 +1086,13 @@ export {
|
|
|
850
1086
|
AcpStateStore,
|
|
851
1087
|
acpCommand,
|
|
852
1088
|
assertNoActiveCompaction,
|
|
1089
|
+
blockRefForSummarySeq,
|
|
1090
|
+
blockRegistry,
|
|
853
1091
|
buildNudge,
|
|
1092
|
+
compactionIdsOfKernelBlocks,
|
|
854
1093
|
index_default as default,
|
|
855
1094
|
eventsToCoreMessages,
|
|
1095
|
+
expandShadowedSeqs,
|
|
856
1096
|
extractEventText,
|
|
857
1097
|
findOpenTurn,
|
|
858
1098
|
kernelConfigFor,
|
|
@@ -863,6 +1103,7 @@ export {
|
|
|
863
1103
|
resolveSurfaceRange,
|
|
864
1104
|
runCompactionTransaction,
|
|
865
1105
|
shadowedSeqsOf,
|
|
1106
|
+
summarySeqOfKernelBlock,
|
|
866
1107
|
surfaceEventsOf
|
|
867
1108
|
};
|
|
868
1109
|
//# sourceMappingURL=index.js.map
|