billion-context-dsh 0.1.7 → 0.1.8
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 +1 -1
- package/README.md +1 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +97 -73
- package/dist/index.js.map +1 -1
- package/dist/nudge.d.ts +15 -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.8**) 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 ⭐:
|
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.8**)仍处于开发中的测试版。[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 本身也处于**公开测试版**阶段。**请勿将两者用于工程化 / 生产环境**——预期会有破坏性变更与粗糙之处。
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
9
9
|
<strong>衷心感谢以下项目——请给它们一个 ⭐:</strong>
|
package/dist/index.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export { kernelConfigFor, type KernelConfigInput } from './config.ts';
|
|
|
36
36
|
export { ACP_SYSTEM_PROMPT, ACP_SYSTEM_PROMPT_ORDER } from './system-prompt.ts';
|
|
37
37
|
export { makeTools, type ToolEnvironment } from './tools.ts';
|
|
38
38
|
export { acpCommand } from './commands.ts';
|
|
39
|
-
export { buildNudge, type NudgeEnvironment, type NudgeOutcome } from './nudge.ts';
|
|
39
|
+
export { buildNudge, resolveTokenCount, type NudgeEnvironment, type NudgeOutcome } from './nudge.ts';
|
|
40
40
|
export { DEFAULT_CONTEXT_WINDOW, detectContextWindow, windowSourceLabel, type AcpWindow, } from './window.ts';
|
|
41
41
|
export { AlreadyCompressedRangeError, rebuildBlockLedger, resolveSurfaceRange, runCompactionTransaction, shadowedSeqsOf, findOpenTurn, assertNoActiveCompaction, blockRegistry, blockRefForSummarySeq, compactionIdsOfKernelBlocks, summarySeqOfKernelBlock, expandShadowedSeqs, type AcpBlockLedgerEntry, type CompactionTransactionInput, type ResolvedSurfaceRange, } from './region.ts';
|
|
42
42
|
export { eventsToCoreMessages, projectEvent, surfaceEventsOf, extractEventText } from './messages.ts';
|
|
@@ -69,6 +69,18 @@ export interface AcpConfig {
|
|
|
69
69
|
readonly nudgeEmergencyThresholdPct?: number;
|
|
70
70
|
/** Any other acp-kernel Config override (billion-context-pi's `coreOverrides` escape hatch). */
|
|
71
71
|
readonly coreOverrides?: Partial<import('acp-kernel').Config>;
|
|
72
|
+
/**
|
|
73
|
+
* Custom token-count function for the kernel's internal estimation.
|
|
74
|
+
* Defaults to the kernel's `defaultCountTokens` (CJK: 1 char = 1 token,
|
|
75
|
+
* other: 4 chars = 1 token — aligns with billion-context-pi).
|
|
76
|
+
* Can be overridden for provider-specific tokenization, e.g. DeepSeek's
|
|
77
|
+
* official coefficient: 1 CJK char ≈ 0.6 tokens, 1 other char ≈ 0.3 tokens.
|
|
78
|
+
* Only affects the kernel's internal estimation (compressible range sizing,
|
|
79
|
+
* nudge text, growth branch pending); the `projectedTokens` reading from
|
|
80
|
+
* `sessionProjections` (used for nudge pressure decisions and acp_status)
|
|
81
|
+
* is provider-anchored and unaffected by this function.
|
|
82
|
+
*/
|
|
83
|
+
readonly countTokens?: (text: string) => number;
|
|
72
84
|
/** Register the four model tools on `ctx.tools`. Default true. */
|
|
73
85
|
readonly autoTools: boolean;
|
|
74
86
|
/** Register the `/acp` command on `ctx.commands`. Default true. */
|
package/dist/index.js
CHANGED
|
@@ -564,7 +564,7 @@ var AcpStateStore = class {
|
|
|
564
564
|
|
|
565
565
|
// src/tools.ts
|
|
566
566
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
567
|
-
import { defaultCountTokens as
|
|
567
|
+
import { defaultCountTokens as defaultCountTokens3 } from "acp-kernel";
|
|
568
568
|
|
|
569
569
|
// src/config.ts
|
|
570
570
|
import { defaultConfig } from "acp-kernel";
|
|
@@ -580,6 +580,77 @@ function kernelConfigFor(input) {
|
|
|
580
580
|
return defaultConfig(input.modelContextLimit, overrides);
|
|
581
581
|
}
|
|
582
582
|
|
|
583
|
+
// src/nudge.ts
|
|
584
|
+
import {
|
|
585
|
+
defaultCountTokens as defaultCountTokens2
|
|
586
|
+
} from "acp-kernel";
|
|
587
|
+
import { createUserMessage as createUserMessage2 } from "@deepseek-ai/dsh-llm";
|
|
588
|
+
function resolveTokenCount(agent, coreMessages) {
|
|
589
|
+
const projections = agent.ctx?.get?.("sessionProjections");
|
|
590
|
+
const projected = projections?.snapshot?.(agent.session)?.values?.contextPressure?.projectedTokens;
|
|
591
|
+
if (typeof projected === "number" && projected > 0) return projected;
|
|
592
|
+
const meter = agent.ctx?.get?.("tokenMeter");
|
|
593
|
+
const surface = meter?.measure?.(agent.session)?.surfaceTokens;
|
|
594
|
+
if (typeof surface === "number" && surface > 0) return surface;
|
|
595
|
+
return coreMessages.reduce((sum, message) => sum + defaultCountTokens2(message.text ?? ""), 0);
|
|
596
|
+
}
|
|
597
|
+
function rangeTable(session) {
|
|
598
|
+
const ranges = buildCompressibleSeqRanges(session).slice(0, 6);
|
|
599
|
+
if (ranges.length === 0) return "";
|
|
600
|
+
const lines = ranges.map((range) => ` - seq ${range.start}..${range.end} \u2014 ${range.count} messages, ~${range.tokens} tokens`);
|
|
601
|
+
return [
|
|
602
|
+
"",
|
|
603
|
+
`Surface: ${surfaceSummary(session)}`,
|
|
604
|
+
"Compressible ranges (suggestions only \u2014 compress any consumed span; refs are surface seqs):",
|
|
605
|
+
...lines,
|
|
606
|
+
"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.",
|
|
607
|
+
"Snapshot taken at nudge time: the seqs go stale once the surface moves (a later compress shadows them), so re-run acp_status for fresh refs before compressing."
|
|
608
|
+
].join("\n");
|
|
609
|
+
}
|
|
610
|
+
function measuredTokenCount(agent, coreMessages) {
|
|
611
|
+
return resolveTokenCount(agent, coreMessages);
|
|
612
|
+
}
|
|
613
|
+
function buildNudge(agent, env, lastNudgeTurn) {
|
|
614
|
+
const session = agent.session;
|
|
615
|
+
const state = env.store.stateFor(session);
|
|
616
|
+
const coreMessages = allLogMessages(session);
|
|
617
|
+
const surfaceMessages = eventsToCoreMessages(surfaceEventsOf(session));
|
|
618
|
+
const tokenCount = measuredTokenCount(agent, surfaceMessages);
|
|
619
|
+
const config = kernelConfigFor(env);
|
|
620
|
+
const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount });
|
|
621
|
+
env.store.set(session, turn.state);
|
|
622
|
+
const nudge = turn.nudge;
|
|
623
|
+
if (nudge === void 0 || !nudge.shouldInject) return null;
|
|
624
|
+
const emergency = nudge.breakdown?.emergencyOverride === 1;
|
|
625
|
+
const turnNumber = findOpenTurn(session.events) ?? 0;
|
|
626
|
+
const alreadyShown = !emergency && lastNudgeTurn.get(session.id) === turnNumber;
|
|
627
|
+
if (alreadyShown) return null;
|
|
628
|
+
lastNudgeTurn.set(session.id, turnNumber);
|
|
629
|
+
const text = buildNudgeText(nudge, emergency, session);
|
|
630
|
+
const message = createUserMessage2({
|
|
631
|
+
content: [{ type: "text", text }],
|
|
632
|
+
source: { kind: "plugin", plugin: "acp-nudge" }
|
|
633
|
+
});
|
|
634
|
+
return { message, emergency };
|
|
635
|
+
}
|
|
636
|
+
function buildNudgeText(nudge, emergency, session) {
|
|
637
|
+
const pct = Math.round(Math.min(nudge.contextUsage, 1) * 100);
|
|
638
|
+
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.`;
|
|
639
|
+
const guidance = "Compress by need, not by percentage: replace only ranges you have genuinely consumed, with dense self-contained summaries.";
|
|
640
|
+
const parts = [frame, "", guidance];
|
|
641
|
+
if ((nudge.tier === 2 || nudge.tier === 3) && (nudge.tierTargetBlocks?.length ?? 0) > 0) {
|
|
642
|
+
const targets = nudge.tierTargetBlocks;
|
|
643
|
+
const summarySeqs = targets.map((block) => summarySeqOfKernelBlock(session, block.blockId)).filter((seq) => seq !== null);
|
|
644
|
+
const pending = nudge.tier === 2 ? nudge.breakdown?.pendingT2 : nudge.breakdown?.pendingT3;
|
|
645
|
+
const tokens = typeof pending === "number" ? pending : 0;
|
|
646
|
+
parts.push(
|
|
647
|
+
`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.`
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
parts.push(rangeTable(session));
|
|
651
|
+
return parts.join("\n");
|
|
652
|
+
}
|
|
653
|
+
|
|
583
654
|
// src/window.ts
|
|
584
655
|
var DEFAULT_CONTEXT_WINDOW = 128e3;
|
|
585
656
|
function windowSourceLabel(window) {
|
|
@@ -661,7 +732,7 @@ async function handleCompress(env, args, exec) {
|
|
|
661
732
|
const state = env.store.stateFor(session);
|
|
662
733
|
const coreMessages = allLogMessages(session);
|
|
663
734
|
const surfaceMessages = eventsToCoreMessages(surfaceEventsOf(session));
|
|
664
|
-
const tokenCount =
|
|
735
|
+
const tokenCount = resolveTokenCount(agent, surfaceMessages);
|
|
665
736
|
const config = kernelConfigFor(env);
|
|
666
737
|
const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount });
|
|
667
738
|
env.store.set(session, turn.state);
|
|
@@ -757,7 +828,7 @@ async function handleCompress(env, args, exec) {
|
|
|
757
828
|
let shadowedTokens = 0;
|
|
758
829
|
for (const seq of shadowed) {
|
|
759
830
|
const event = session.events[seq];
|
|
760
|
-
if (event !== void 0) shadowedTokens +=
|
|
831
|
+
if (event !== void 0) shadowedTokens += defaultCountTokens3(extractEventText(event));
|
|
761
832
|
}
|
|
762
833
|
const tier = block.tier === 2 || block.tier === 3 ? block.tier : 1;
|
|
763
834
|
const parentBlockIds = compactionIdsOfKernelBlocks(session, block.directBlockIds);
|
|
@@ -847,7 +918,7 @@ async function handleStatus(env, _args, exec) {
|
|
|
847
918
|
const ledger = rebuildBlockLedger(session.events);
|
|
848
919
|
const totalTokens = ledger.reduce((sum, block) => sum + block.shadowedTokenCount, 0);
|
|
849
920
|
const coreMessages = eventsToCoreMessages(surfaceEventsOf(session));
|
|
850
|
-
const estimated =
|
|
921
|
+
const estimated = resolveTokenCount(agent, coreMessages);
|
|
851
922
|
const window = env.windowFor === void 0 ? { limit: env.modelContextLimit, source: "explicit" } : await env.windowFor(agent);
|
|
852
923
|
const limit = window.limit;
|
|
853
924
|
const lines = [
|
|
@@ -905,13 +976,13 @@ function makeTools(env) {
|
|
|
905
976
|
}
|
|
906
977
|
|
|
907
978
|
// src/commands.ts
|
|
908
|
-
import { defaultCountTokens as
|
|
979
|
+
import { defaultCountTokens as defaultCountTokens4 } from "acp-kernel";
|
|
909
980
|
async function statusText(env, agent) {
|
|
910
981
|
const session = agent.session;
|
|
911
982
|
const ledger = rebuildBlockLedger(session.events);
|
|
912
983
|
const totalTokens = ledger.reduce((sum, block) => sum + block.shadowedTokenCount, 0);
|
|
913
984
|
const coreMessages = eventsToCoreMessages(surfaceEventsOf(session));
|
|
914
|
-
const estimated =
|
|
985
|
+
const estimated = resolveTokenCount(agent, coreMessages);
|
|
915
986
|
const window = env.windowFor === void 0 ? { limit: env.modelContextLimit, source: "explicit" } : await env.windowFor(agent);
|
|
916
987
|
const limit = window.limit;
|
|
917
988
|
const lines = [
|
|
@@ -946,7 +1017,7 @@ function compressText(env, agent, args) {
|
|
|
946
1017
|
let shadowedTokens = 0;
|
|
947
1018
|
for (const seq of shadowed) {
|
|
948
1019
|
const event = session.events[seq];
|
|
949
|
-
if (event !== void 0) shadowedTokens +=
|
|
1020
|
+
if (event !== void 0) shadowedTokens += defaultCountTokens4(extractEventText(event));
|
|
950
1021
|
}
|
|
951
1022
|
const { compactionId } = runCompactionTransaction(session, {
|
|
952
1023
|
start,
|
|
@@ -990,71 +1061,6 @@ function acpCommand(env) {
|
|
|
990
1061
|
};
|
|
991
1062
|
}
|
|
992
1063
|
|
|
993
|
-
// src/nudge.ts
|
|
994
|
-
import {
|
|
995
|
-
defaultCountTokens as defaultCountTokens4
|
|
996
|
-
} from "acp-kernel";
|
|
997
|
-
import { createUserMessage as createUserMessage2 } from "@deepseek-ai/dsh-llm";
|
|
998
|
-
function rangeTable(session) {
|
|
999
|
-
const ranges = buildCompressibleSeqRanges(session).slice(0, 6);
|
|
1000
|
-
if (ranges.length === 0) return "";
|
|
1001
|
-
const lines = ranges.map((range) => ` - seq ${range.start}..${range.end} \u2014 ${range.count} messages, ~${range.tokens} tokens`);
|
|
1002
|
-
return [
|
|
1003
|
-
"",
|
|
1004
|
-
`Surface: ${surfaceSummary(session)}`,
|
|
1005
|
-
"Compressible ranges (suggestions only \u2014 compress any consumed span; refs are surface seqs):",
|
|
1006
|
-
...lines,
|
|
1007
|
-
"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.",
|
|
1008
|
-
"Snapshot taken at nudge time: the seqs go stale once the surface moves (a later compress shadows them), so re-run acp_status for fresh refs before compressing."
|
|
1009
|
-
].join("\n");
|
|
1010
|
-
}
|
|
1011
|
-
function measuredTokenCount(agent, coreMessages) {
|
|
1012
|
-
const meter = agent.ctx?.get?.("tokenMeter");
|
|
1013
|
-
const surface = meter?.measure?.(agent.session)?.surfaceTokens;
|
|
1014
|
-
if (typeof surface === "number" && surface > 0) return surface;
|
|
1015
|
-
return coreMessages.reduce((sum, message) => sum + defaultCountTokens4(message.text ?? ""), 0);
|
|
1016
|
-
}
|
|
1017
|
-
function buildNudge(agent, env, lastNudgeTurn) {
|
|
1018
|
-
const session = agent.session;
|
|
1019
|
-
const state = env.store.stateFor(session);
|
|
1020
|
-
const coreMessages = allLogMessages(session);
|
|
1021
|
-
const surfaceMessages = eventsToCoreMessages(surfaceEventsOf(session));
|
|
1022
|
-
const tokenCount = measuredTokenCount(agent, surfaceMessages);
|
|
1023
|
-
const config = kernelConfigFor(env);
|
|
1024
|
-
const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount });
|
|
1025
|
-
env.store.set(session, turn.state);
|
|
1026
|
-
const nudge = turn.nudge;
|
|
1027
|
-
if (nudge === void 0 || !nudge.shouldInject) return null;
|
|
1028
|
-
const emergency = nudge.breakdown?.emergencyOverride === 1;
|
|
1029
|
-
const turnNumber = findOpenTurn(session.events) ?? 0;
|
|
1030
|
-
const alreadyShown = !emergency && lastNudgeTurn.get(session.id) === turnNumber;
|
|
1031
|
-
if (alreadyShown) return null;
|
|
1032
|
-
lastNudgeTurn.set(session.id, turnNumber);
|
|
1033
|
-
const text = buildNudgeText(nudge, emergency, session);
|
|
1034
|
-
const message = createUserMessage2({
|
|
1035
|
-
content: [{ type: "text", text }],
|
|
1036
|
-
source: { kind: "plugin", plugin: "acp-nudge" }
|
|
1037
|
-
});
|
|
1038
|
-
return { message, emergency };
|
|
1039
|
-
}
|
|
1040
|
-
function buildNudgeText(nudge, emergency, session) {
|
|
1041
|
-
const pct = Math.round(Math.min(nudge.contextUsage, 1) * 100);
|
|
1042
|
-
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.`;
|
|
1043
|
-
const guidance = "Compress by need, not by percentage: replace only ranges you have genuinely consumed, with dense self-contained summaries.";
|
|
1044
|
-
const parts = [frame, "", guidance];
|
|
1045
|
-
if ((nudge.tier === 2 || nudge.tier === 3) && (nudge.tierTargetBlocks?.length ?? 0) > 0) {
|
|
1046
|
-
const targets = nudge.tierTargetBlocks;
|
|
1047
|
-
const summarySeqs = targets.map((block) => summarySeqOfKernelBlock(session, block.blockId)).filter((seq) => seq !== null);
|
|
1048
|
-
const pending = nudge.tier === 2 ? nudge.breakdown?.pendingT2 : nudge.breakdown?.pendingT3;
|
|
1049
|
-
const tokens = typeof pending === "number" ? pending : 0;
|
|
1050
|
-
parts.push(
|
|
1051
|
-
`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.`
|
|
1052
|
-
);
|
|
1053
|
-
}
|
|
1054
|
-
parts.push(rangeTable(session));
|
|
1055
|
-
return parts.join("\n");
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
1064
|
// src/system-prompt.ts
|
|
1059
1065
|
import { COMPRESS_PHILOSOPHY } from "acp-kernel";
|
|
1060
1066
|
var ACP_SYSTEM_PROMPT = `Active Context Pruning \u2014 model-driven context management
|
|
@@ -1104,7 +1110,8 @@ var AcpCompactionEngine = class extends CompactionEngine {
|
|
|
1104
1110
|
constructor(ctx, config = {}) {
|
|
1105
1111
|
super(ctx);
|
|
1106
1112
|
this.config = resolveAcpConfig(config);
|
|
1107
|
-
this.
|
|
1113
|
+
const ports = this.config.countTokens !== void 0 ? { countTokens: this.config.countTokens } : {};
|
|
1114
|
+
this.kernel = createCore(ports);
|
|
1108
1115
|
this.store = new AcpStateStore();
|
|
1109
1116
|
const env = {
|
|
1110
1117
|
kernel: this.kernel,
|
|
@@ -1166,6 +1173,22 @@ var AcpCompactionEngine = class extends CompactionEngine {
|
|
|
1166
1173
|
order: ACP_SYSTEM_PROMPT_ORDER,
|
|
1167
1174
|
text: ACP_SYSTEM_PROMPT
|
|
1168
1175
|
});
|
|
1176
|
+
} else {
|
|
1177
|
+
let done = false;
|
|
1178
|
+
const registerSystemPrompt = () => {
|
|
1179
|
+
if (done) return;
|
|
1180
|
+
const registry = ctx.get("systemPrompt");
|
|
1181
|
+
if (registry === void 0) return;
|
|
1182
|
+
done = true;
|
|
1183
|
+
registry.section({
|
|
1184
|
+
name: "billion-context-dsh",
|
|
1185
|
+
order: ACP_SYSTEM_PROMPT_ORDER,
|
|
1186
|
+
text: ACP_SYSTEM_PROMPT
|
|
1187
|
+
});
|
|
1188
|
+
};
|
|
1189
|
+
ctx.on("internal/service", (name) => {
|
|
1190
|
+
if (name === "systemPrompt") registerSystemPrompt();
|
|
1191
|
+
});
|
|
1169
1192
|
}
|
|
1170
1193
|
}
|
|
1171
1194
|
/**
|
|
@@ -1243,6 +1266,7 @@ export {
|
|
|
1243
1266
|
rebuildBlockLedger,
|
|
1244
1267
|
resolveAcpConfig,
|
|
1245
1268
|
resolveSurfaceRange,
|
|
1269
|
+
resolveTokenCount,
|
|
1246
1270
|
runCompactionTransaction,
|
|
1247
1271
|
shadowedSeqsOf,
|
|
1248
1272
|
summarySeqOfKernelBlock,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/state.ts","../src/region.ts","../src/messages.ts","../src/tools.ts","../src/config.ts","../src/window.ts","../src/commands.ts","../src/nudge.ts","../src/system-prompt.ts"],"sourcesContent":["/**\n * billion-context-dsh — Active Context Pruning (ACP) for the DeepSeek Harness,\n * delivered as a `CompactionEngine` backend.\n *\n * The model decides when and what to compress (pure ACP semantics):\n * - the `compress` tool durably shadows a surface range with the model-written\n * summary (no second LLM summarization call — the ACP cost win);\n * - the original events stay in the append-only session log, so `decompress`,\n * `search_context`, and replay always work;\n * - refs are surface seqs carried by the injected nudge's range table (DSH\n * has no in-memory message rewrite hook — see docs/dsh-porting-verification.md);\n * - automatic policy never summarizes by itself: it nudges the model.\n *\n * Mount it wherever a compaction backend is expected:\n *\n * ```yaml\n * - id: compaction-billion-context\n * name: 'billion-context-dsh'\n * config:\n * modelContextLimit: 128000\n * ```\n *\n * The package registers `ctx.compaction` plus the four model tools and the\n * `/acp` command when the hosting composition provides `ctx.tools` /\n * `ctx.commands`.\n * @module billion-context-dsh\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport {\n CompactionEngine,\n ManualCompactionError,\n type CompactionAgentContext,\n type CompactionResult,\n type CompactionTrigger,\n type ManualCompactAgentContext,\n} from '@deepseek-ai/dsh-compaction'\nimport { createCore, type CompressionCore } from 'acp-kernel'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport { AcpStateStore } from './state.ts'\nimport { makeTools, type ToolEnvironment } from './tools.ts'\nimport { acpCommand } from './commands.ts'\nimport { buildNudge } from './nudge.ts'\nimport { ACP_SYSTEM_PROMPT, ACP_SYSTEM_PROMPT_ORDER } from './system-prompt.ts'\nimport { DEFAULT_CONTEXT_WINDOW, detectContextWindow, type AcpWindow } from './window.ts'\n\nexport { AcpStateStore } from './state.ts'\nexport { kernelConfigFor, type KernelConfigInput } from './config.ts'\nexport { ACP_SYSTEM_PROMPT, ACP_SYSTEM_PROMPT_ORDER } from './system-prompt.ts'\nexport { makeTools, type ToolEnvironment } from './tools.ts'\nexport { acpCommand } from './commands.ts'\nexport { buildNudge, type NudgeEnvironment, type NudgeOutcome } from './nudge.ts'\nexport {\n DEFAULT_CONTEXT_WINDOW,\n detectContextWindow,\n windowSourceLabel,\n type AcpWindow,\n} from './window.ts'\nexport {\n AlreadyCompressedRangeError,\n rebuildBlockLedger,\n resolveSurfaceRange,\n runCompactionTransaction,\n shadowedSeqsOf,\n findOpenTurn,\n assertNoActiveCompaction,\n blockRegistry,\n blockRefForSummarySeq,\n compactionIdsOfKernelBlocks,\n summarySeqOfKernelBlock,\n expandShadowedSeqs,\n type AcpBlockLedgerEntry,\n type CompactionTransactionInput,\n type ResolvedSurfaceRange,\n} from './region.ts'\nexport { eventsToCoreMessages, projectEvent, surfaceEventsOf, extractEventText } from './messages.ts'\n\nexport interface AcpConfig {\n /**\n * The context window used for pressure decisions, in tokens. When omitted,\n * `autoModelContextLimit` (default true) probes the model's real window via\n * `agent.ctx.llm.resolveModelInfo(provider, model)`; an explicit value\n * always wins and disables the probe.\n */\n readonly modelContextLimit?: number\n /** Probe the model's real context window from the LLM runtime. Default true. */\n readonly autoModelContextLimit: boolean\n /** Nudge window lower bound (usage fraction; validation only — the growth-driven trigger has no percentage floor). Kernel default 0.45 — same as billion-context-pi. */\n readonly nudgeMinContextLimitPct?: number\n /**\n * Nudge window upper bound — over-limit guarantee line: above this the\n * kernel injects a nudge regardless of growth or cadence. Engine default\n * 0.70 (deliberately BELOW the kernel/billion-context-pi default 0.75 and\n * the host compaction-basic auto-compaction line 0.80, so the forced nudge\n * always fires first); an explicit value wins.\n */\n readonly nudgeMaxContextLimitPct?: number\n /**\n * Emergency nudge threshold (bypasses the per-turn dedup). Engine default\n * 0.85 (down from the kernel/billion-context-pi default 0.95: 95% leaves\n * the model no room to act before the API rejects, and the host's 80%\n * compaction-basic line shadows it in standard/code/cordis modes).\n */\n readonly nudgeEmergencyThresholdPct?: number\n /** Any other acp-kernel Config override (billion-context-pi's `coreOverrides` escape hatch). */\n readonly coreOverrides?: Partial<import('acp-kernel').Config>\n /** Register the four model tools on `ctx.tools`. Default true. */\n readonly autoTools: boolean\n /** Register the `/acp` command on `ctx.commands`. Default true. */\n readonly autoCommand: boolean\n /** Inject the nudge into `agent/pre-step` when the kernel recommends it. Default true. */\n readonly autoNudge: boolean\n}\n\nconst DEFAULT_CONFIG: AcpConfig = {\n autoModelContextLimit: true,\n autoTools: true,\n autoCommand: true,\n autoNudge: true,\n // Nudge thresholds: engine defaults 0.70/0.85 — deliberately below the\n // kernel/billion-context-pi 0.75/0.95. 0.95 leaves no room to act before\n // the API rejects, and the host's compaction-basic line (thresholdRatio\n // 0.80) shadows it in standard/code/cordis modes; 0.70 keeps the forced\n // over-limit nudge ahead of that 80% line. Explicit values always win.\n nudgeMaxContextLimitPct: 0.7,\n nudgeEmergencyThresholdPct: 0.85,\n}\n\nexport function resolveAcpConfig(config: Partial<AcpConfig> = {}): AcpConfig {\n return { ...DEFAULT_CONFIG, ...config }\n}\n\n/**\n * The ACP compaction backend. Subclasses the seam exactly like\n * `dsh-compaction-basic`; swaps summarization-driven compaction for\n * model-driven block compression without touching the agent loop.\n */\nexport class AcpCompactionEngine extends CompactionEngine {\n /** The framework-agnostic ACP compression core, reused verbatim. */\n readonly kernel: CompressionCore\n /** Per-session kernel state. */\n readonly store: AcpStateStore\n /** Resolved engine configuration. */\n readonly config: AcpConfig\n\n private readonly lastNudgeTurn = new Map<string, number>()\n /** Per provider/model route the resolved window (probe failures cached too). */\n private readonly windowCache = new Map<string, AcpWindow>()\n\n constructor(ctx: Context, config: Partial<AcpConfig> = {}) {\n super(ctx)\n this.config = resolveAcpConfig(config)\n this.kernel = createCore({})\n this.store = new AcpStateStore()\n\n const env: ToolEnvironment = {\n kernel: this.kernel,\n store: this.store,\n // Initial value before any probe; windowFor() replaces it per pre-step.\n modelContextLimit: this.config.modelContextLimit ?? DEFAULT_CONTEXT_WINDOW,\n nudgeMinContextLimitPct: this.config.nudgeMinContextLimitPct,\n nudgeMaxContextLimitPct: this.config.nudgeMaxContextLimitPct,\n nudgeEmergencyThresholdPct: this.config.nudgeEmergencyThresholdPct,\n coreOverrides: this.config.coreOverrides,\n windowFor: (agent) => this.windowFor(agent),\n }\n\n // Tools and commands may not be registered yet on cold start: cordis\n // starts unrelated composition rows concurrently, so the first\n // `ctx.get('tools')` can legitimately be undefined even though the row\n // ships later in the file. HMR-style reloads always see them (already\n // present), but a fresh process races — the tools silently vanished on\n // restart. Register eagerly, then re-attempt when the service appears\n // (`internal/service`) or the app finishes booting (`ready`); guard so a\n // late callback never double-registers.\n const tools = ctx.get('tools')\n if (tools !== undefined) {\n for (const tool of makeTools(env)) tools.register(tool)\n } else {\n let done = false\n const registerTools = (): void => {\n if (done) return\n const registry = ctx.get('tools')\n if (registry === undefined) return\n done = true\n for (const tool of makeTools(env)) registry.register(tool)\n }\n ctx.on('internal/service', (name: unknown) => {\n if (name === 'tools') registerTools()\n })\n }\n const commands = ctx.get('commands')\n if (commands !== undefined) {\n commands.register(acpCommand(env))\n } else {\n let done = false\n const registerCommand = (): void => {\n if (done) return\n const registry = ctx.get('commands')\n if (registry === undefined) return\n done = true\n registry.register(acpCommand(env))\n }\n ctx.on('internal/service', (name: unknown) => {\n if (name === 'commands') registerCommand()\n })\n }\n if (this.config.autoNudge) {\n ctx.on('agent/pre-step', async (payload, next) => {\n const decision = await next()\n if (decision.kind === 'reject') return decision\n const window = await this.windowFor(payload.agent)\n const outcome = buildNudge(payload.agent, { ...env, modelContextLimit: window.limit }, this.lastNudgeTurn)\n if (outcome === null) return decision\n return { kind: 'enter', messages: [...decision.messages, outcome.message] }\n })\n }\n // The load-bearing ACP guidance lives in the system prompt ONCE; nudges\n // stay short and advisory (model-driven: the model decides).\n const systemPrompt = ctx.get('systemPrompt')\n if (systemPrompt !== undefined) {\n systemPrompt.section({\n name: 'billion-context-dsh',\n order: ACP_SYSTEM_PROMPT_ORDER,\n text: ACP_SYSTEM_PROMPT,\n })\n }\n }\n\n /**\n * Resolve the effective context window for an agent. An explicitly\n * configured `modelContextLimit` always wins (no probe). Otherwise probe the\n * model's real window via `agent.ctx.llm.resolveModelInfo` (cached per\n * provider/model route, probe failures cached too) and fall back to\n * DEFAULT_CONTEXT_WINDOW when auto-detection is disabled or unavailable.\n */\n async windowFor(agent: Agent): Promise<AcpWindow> {\n if (this.config.modelContextLimit !== undefined) {\n return { limit: this.config.modelContextLimit, source: 'explicit' }\n }\n const provider = agent.options.provider ?? ''\n const model = agent.options.model ?? ''\n const key = `${provider}\\0${model}`\n const cached = this.windowCache.get(key)\n if (cached !== undefined) return cached\n let window: AcpWindow\n if (!this.config.autoModelContextLimit) {\n window = { limit: DEFAULT_CONTEXT_WINDOW, source: 'default', provider, model }\n } else {\n const detected = await detectContextWindow(agent, provider, model)\n window = detected === null\n ? { limit: DEFAULT_CONTEXT_WINDOW, source: 'default', provider, model }\n : { limit: detected, source: 'auto', provider, model }\n }\n this.windowCache.set(key, window)\n return window\n }\n\n /** ACP is model-driven: automatic pressure policy never summarizes by itself. */\n override async compactIfNeeded(\n _agent: CompactionAgentContext,\n _trigger: CompactionTrigger,\n signal: AbortSignal,\n ): Promise<CompactionResult | null> {\n signal.throwIfAborted()\n return null\n }\n\n /** Explicit idle-session compaction: ACP leaves the decision to the model. */\n override async compactNow(\n _agent: ManualCompactAgentContext,\n signal: AbortSignal,\n ): Promise<CompactionResult | null> {\n signal.throwIfAborted()\n return null\n }\n\n /**\n * The model-driven path lands through the `compress` tool, which runs the\n * full durable transaction directly. This seam method rejects with guidance:\n * automatic summarization is exactly what ACP replaces.\n */\n override async compactRegion(\n _start: number,\n _end: number,\n _agent: CompactionAgentContext,\n signal?: AbortSignal,\n ): Promise<CompactionResult> {\n signal?.throwIfAborted()\n throw new ManualCompactionError(\n 'summary',\n 'billion-context-dsh is model-driven: use the compress tool instead of automatic summarization',\n )\n }\n}\n\nexport default AcpCompactionEngine\n","/**\n * M2 — per-session ACP kernel state.\n *\n * The in-memory map holds the exact acp-kernel `CompressionState` while a\n * session is live. Durability does not rely on a sidecar file: every durable\n * compression writes a `compaction/summary` event whose shadowed range and\n * summary re-derive the block ledger (`rebuildBlockLedger` in region.ts), so a\n * restarted engine can answer decompress/search/status from the session log\n * alone — DSH's \"log is the source of truth\" model.\n *\n * Tier-2/3 distillation additionally requires the kernel state to KNOW the\n * blocks: `syncBlocks` deactivates a block whose consumed messages are absent\n * from the message array, and `resolveBoundaries` refuses to anchor a block\n * ref it cannot find — so on first access for a session that already has\n * durable blocks (e.g. after a server restart), the kernel blocks are\n * REHYDRATED from the ledger before use. Live updates continue through `set`.\n * @module billion-context-dsh/state\n */\n\nimport type { Session, SessionEvent } from '@deepseek-ai/dsh-session'\nimport { createInitialState, type CompressionBlock, type CompressionState } from 'acp-kernel'\nimport { rebuildBlockLedger } from './region.ts'\n\n/** Rebuild kernel `CompressionBlock`s from the durable ledger (no kernel run needed). */\nfunction rebuildKernelBlocks(events: readonly SessionEvent[]): CompressionBlock[] {\n const ledger = rebuildBlockLedger(events)\n if (ledger.length === 0) return []\n // Durable compactionId → kernel block ref (bN), recorded or synthesised.\n const kernelIdOf = new Map<string, string>()\n const parentKernelIds = new Map<string, string[]>()\n let next = 1\n for (const entry of ledger) {\n let kernelBlockId: string\n if (entry.kernelBlockId !== undefined && /^b\\d+$/.test(entry.kernelBlockId)) {\n kernelBlockId = entry.kernelBlockId\n const num = Number(kernelBlockId.slice(1))\n if (Number.isInteger(num)) next = Math.max(next, num + 1)\n } else {\n kernelBlockId = `b${next}`\n next += 1\n }\n kernelIdOf.set(entry.blockId, kernelBlockId)\n parentKernelIds.set(\n entry.blockId,\n entry.parentBlockIds\n .map((parent) => kernelIdOf.get(parent))\n .filter((id): id is string => id !== undefined),\n )\n }\n const consumed = new Set<string>()\n for (const entry of ledger) {\n for (const parent of entry.parentBlockIds) consumed.add(parent)\n }\n const blocks: CompressionBlock[] = []\n for (const entry of ledger) {\n const blockId = kernelIdOf.get(entry.blockId)!\n // The kernel anchors a block by its effectiveMessageIds. Since the tier\n // feature, the transaction records the kernel block's raw coverage\n // (direct/effective message ids) verbatim, so rehydration is faithful —\n // a tier-2 block's coverage is its parents' ORIGINALS, not the checkpoint\n // node it shadows. Legacy blocks fall back to the shadowed seqs (tier 1)\n // or the checkpoint node (tier > 1; multi-tool-call assistant messages in\n // legacy blocks lose bare-seq coverage — a documented legacy limitation).\n const direct = entry.directMessageIds ?? [...entry.shadowedSeqs.map(String)]\n const effective = entry.effectiveMessageIds\n ?? (entry.tier > 1\n ? (entry.summarySeq === undefined ? [...entry.shadowedSeqs.map(String)] : [String(entry.summarySeq)])\n : [...entry.shadowedSeqs.map(String)])\n blocks.push({\n blockId,\n runId: `r${blocks.length + 1}`,\n tier: entry.tier,\n summary: entry.summary,\n directMessageIds: [...direct],\n effectiveMessageIds: [...effective],\n directBlockIds: parentKernelIds.get(entry.blockId) ?? [],\n compressedTokens: entry.shadowedTokenCount,\n createdAt: entry.createdAt,\n survivedCount: 0,\n generation: 'young',\n active: !consumed.has(entry.blockId),\n })\n }\n return blocks\n}\n\n/** The next kernel block id after the rehydrated blocks (or the initial 1). */\nfunction nextBlockIdAfter(events: readonly SessionEvent[]): number {\n const blocks = rebuildKernelBlocks(events)\n let max = 0\n for (const block of blocks) {\n const num = Number(block.blockId.slice(1))\n if (Number.isInteger(num)) max = Math.max(max, num)\n }\n return max + 1\n}\n\nexport class AcpStateStore {\n private readonly states = new Map<string, CompressionState>()\n\n /** Kernel state for one session, initialised on first access. */\n stateFor(session: Session): CompressionState {\n const id = session.id\n const existing = this.states.get(id)\n if (existing !== undefined) return existing\n const state = createInitialState()\n if (session.events.some((event) => event.type === 'compaction/summary')) {\n state.blocks = rebuildKernelBlocks(session.events)\n state.nextBlockId = nextBlockIdAfter(session.events)\n }\n this.states.set(id, state)\n return state\n }\n\n set(session: Session, state: CompressionState): void {\n this.states.set(session.id, state)\n }\n\n delete(session: Session): void {\n this.states.delete(session.id)\n }\n}\n","/**\n * M5 — durable region transaction and the log-rebuilt block ledger.\n *\n * Modeled on `dsh-compaction-basic/src/region.ts` (which is package-internal\n * and not exported by the seam): validate the surface range and tool-call/result\n * pairing, take the durable `compaction/start` lock, record `compaction/summary`\n * as the shadow price, land the `user/message` surface replacement carrying the\n * summary under `compactCheckpointSource`, and release the lock with\n * `compaction/end`. The original events stay in the append-only log, so\n * decompress/search/status can rebuild everything from the log.\n * @module billion-context-dsh/region\n */\n\nimport { randomUUID } from 'node:crypto'\nimport type { Session, SessionEvent, SessionEventMap } from '@deepseek-ai/dsh-session'\nimport {\n CompactionId,\n compactCheckpointSource,\n toolPairingBalancedAfter,\n toolPairingBalancedBefore,\n} from '@deepseek-ai/dsh-compaction'\nimport { createUserMessage, type ContentBlock } from '@deepseek-ai/dsh-llm'\nimport { defaultCountTokens } from 'acp-kernel'\nimport { extractEventText, extractText } from './messages.ts'\n\n/** One durable ACP block as rebuilt from the session log. */\nexport interface AcpBlockLedgerEntry {\n /** The compaction transaction id (stable block identity). */\n readonly blockId: string\n readonly summary: string\n readonly shadowedSeqs: readonly number[]\n readonly shadowedTokenCount: number\n readonly start: number\n readonly end: number\n /** Compression tier: 1 (message range), 2 (distills tier-1 blocks), 3 (distills tier-2 blocks). Legacy blocks default to 1. */\n readonly tier: 1 | 2 | 3\n /** Compaction ids of the blocks this block distilled (parents). Empty for tier-1 blocks. */\n readonly parentBlockIds: readonly string[]\n /** The acp-kernel block id (`bN`) created for this transaction — absent for legacy blocks (synthesised by order). */\n readonly kernelBlockId?: string\n /** The surface seq of this block's checkpoint summary node (derived from the log; null when the node is gone). */\n readonly summarySeq?: number\n /** The kernel block's raw direct/effective message ids at creation (recorded since the tier feature; absent for legacy). */\n readonly directMessageIds?: readonly string[]\n readonly effectiveMessageIds?: readonly string[]\n /** Unix epoch ms of the compaction/summary event. */\n readonly createdAt: number\n}\n\n/** The open turn number, or null when the log ends between turns. */\nexport function findOpenTurn(events: readonly SessionEvent[]): number | null {\n let open: number | null = null\n for (const event of events) {\n if (event.type === 'turn/start') open = event.data.turn\n else if (event.type === 'turn/end' && event.data.turn === open) open = null\n }\n return open\n}\n\n/** Reject a second concurrent compaction for the same session. */\nexport function assertNoActiveCompaction(events: readonly SessionEvent[]): void {\n let active = false\n for (const event of events) {\n if (event.type === 'compaction/start') active = true\n else if (event.type === 'compaction/end') active = false\n }\n if (active) {\n throw new Error('billion-context-dsh: another compaction is already active for this session')\n }\n}\n\n/**\n * Whether the surface node at `seq` projects to CoreMessage(s) whose ref key\n * is the bare seq — user messages, tool results, and text-only or SINGLE\n * tool-call assistant messages all do. Multi-tool-call assistant messages\n * project to `${seq}#${callId}` ids (projectEvent) and therefore carry NO\n * bare-`${seq}` ref, so compress's byRaw lookup can never resolve them as\n * range edges. resolveSurfaceRange treats such edges as unbalanced and shifts\n * them to the nearest clean cut.\n */\nfunction hasPlainRef(session: Session, seq: number): boolean {\n const event = session.events[seq]\n if (event === undefined) return false\n switch (event.type) {\n case 'user/message':\n case 'tool/result':\n return extractEventText(event).trim().length > 0\n case 'assistant/message': {\n const content = (event.data as { message?: { content?: unknown } }).message?.content\n const calls = Array.isArray(content)\n ? content.filter(\n (block) => block !== null && typeof block === 'object' && (block as { type?: string }).type === 'tool-call',\n )\n : []\n if (calls.length > 1) return false\n // One tool-call: projectEvent emits a bare-seq CoreMessage unconditionally.\n // Zero: only when the text is non-empty.\n return calls.length === 1 || extractEventText(event).trim().length > 0\n }\n default:\n return false\n }\n}\n\n/**\n * A requested range whose EVERY live message was already shadowed by one or\n * more blocks. The compress tool catches this and reports the range as already\n * compressed (with the covering block ids) instead of folding block summary\n * nodes as plain messages or erroring out. Distillation stays an explicit act:\n * target a LIVE checkpoint seq directly to distill (tier 2/3).\n */\nexport class AlreadyCompressedRangeError extends Error {\n constructor(\n readonly start: number,\n readonly end: number,\n readonly coveringBlockIds: readonly string[],\n ) {\n super(\n `billion-context-dsh: seq ${start}..${end} already compressed — `\n + 'no live content remains in that span',\n )\n this.name = 'AlreadyCompressedRangeError'\n }\n}\n\ntype StaleRangeRecovery =\n | { kind: 'ok'; start: number; end: number }\n | { kind: 'already-compressed'; coveringBlockIds: string[] }\n | { kind: 'unresolvable'; failedEdge: number }\n\n/**\n * Rebuild a requested range whose edges are no longer on the current surface.\n * The dominant cause is staleness: the seqs came from an older nudge table or\n * a previous compress result, and an earlier compression SHADOWED them (they\n * stay in the append-only log, but are gone from the surface). The recovery:\n *\n * 1. An edge that does not exist in the log at all (invented, or from another\n * session) is unresolvable — there is no way to guess what it meant.\n * 2. The still-LIVE surface nodes inside the requested span, in VALUE order\n * (the surface can be locally non-monotonic after replacements, so value\n * order is the only coherent span). If there are none, the whole span was\n * already compressed → 'already-compressed' with the covering block ids.\n * 3. Otherwise the range snaps to the first..last live PLAIN node in the\n * span. Block checkpoint nodes are deliberately excluded: distilling a\n * block on a STALE reference would silently change block structure the\n * model never intended to touch — distillation requires targeting a live\n * checkpoint seq directly.\n */\nfunction recoverStaleRange(session: Session, start: number, end: number): StaleRangeRecovery {\n if (session.events[start] === undefined || session.events[end] === undefined) {\n const failedEdge = session.events[start] === undefined ? start : end\n return { kind: 'unresolvable', failedEdge }\n }\n const liveInside = session.surface.nodes\n .filter((seq) => seq >= start && seq <= end)\n .sort((a, b) => a - b)\n const plain = liveInside.filter((seq) => !isCheckpointNode(session.events[seq]!))\n if (plain.length === 0) {\n const coveringBlockIds = rebuildBlockLedger(session.events)\n .filter((entry) => entry.shadowedSeqs.some((seq) => seq >= start && seq <= end))\n .map((entry) => entry.blockId)\n return { kind: 'already-compressed', coveringBlockIds }\n }\n return { kind: 'ok', start: plain[0]!, end: plain[plain.length - 1]! }\n}\n\nexport interface ResolvedSurfaceRange {\n readonly start: number\n readonly end: number\n /**\n * True when the requested edges were not on the current surface and were\n * remapped to the still-live content of the requested span (an earlier\n * compression shadowed them). Callers surface this so the model sees what\n * was actually compressed instead of silently shadowing a different span.\n */\n readonly recovered?: boolean\n}\n\n/**\n * Validate one inclusive surface span and adjust its edges to a\n * tool-pairing-balanced range whose boundaries carry a bare-seq ref. Reversed\n * ranges throw. An edge that sits inside a tool-call/result pair — or on a\n * multi-tool-call assistant message that has no bare-seq ref — is first nudged\n * inward to the nearest clean cut; if that collapses the range (e.g. the model\n * asked for a SINGLE tool result, which can never be balanced alone), the\n * range EXPANDS outward to the enclosing clean pair instead — a lone tool\n * message is almost always a \"consumed output\" the model genuinely wants to\n * compress. The returned range is what a caller should actually shadow.\n *\n * Missing edges are NOT an immediate error: the seqs were probably shadowed by\n * an earlier compression (stale nudge table / old compress result). The span\n * is rebuilt from its still-live remainder via recoverStaleRange — a fully\n * shadowed span throws AlreadyCompressedRangeError, a genuinely unknown edge\n * throws the not-in-surface guidance error. The returned range is what a\n * caller should actually shadow.\n */\nexport function resolveSurfaceRange(\n session: Session,\n start: number,\n end: number,\n): ResolvedSurfaceRange {\n const nodes = session.surface.nodes\n if (start > end) {\n throw new Error(`billion-context-dsh: reversed range ${start}..${end}`)\n }\n let requestedStartIdx = nodes.indexOf(start)\n let requestedEndIdx = nodes.indexOf(end)\n let recovered = false\n if (requestedStartIdx < 0 || requestedEndIdx < 0) {\n const stale = recoverStaleRange(session, start, end)\n if (stale.kind === 'unresolvable') {\n throw new Error(\n `billion-context-dsh: seq ${start}..${end} not in the current surface — `\n + `edge seq ${stale.failedEdge} is not in this session's log. `\n + 'Surface seqs are sparse message nodes (only user/message, assistant/message, '\n + 'tool/result events); consult acp_status for the current surface range',\n )\n }\n if (stale.kind === 'already-compressed') {\n throw new AlreadyCompressedRangeError(start, end, stale.coveringBlockIds)\n }\n start = stale.start\n end = stale.end\n recovered = true\n requestedStartIdx = nodes.indexOf(start)\n requestedEndIdx = nodes.indexOf(end)\n if (requestedStartIdx < 0 || requestedEndIdx < 0) {\n // Unreachable in practice (recovery returns live nodes), but never let\n // a negative index reach the balancing passes.\n throw new Error(\n `billion-context-dsh: seq ${start}..${end} not in the current surface — `\n + 'consult acp_status for the current surface range',\n )\n }\n }\n if (requestedStartIdx > requestedEndIdx) {\n throw new Error(`billion-context-dsh: reversed range ${start}..${end}`)\n }\n // Belt-and-braces: the surface can be locally out of order after surface\n // replacements, so index order alone does not guarantee value order.\n if (start > end) {\n throw new Error(`billion-context-dsh: reversed range ${start}..${end}`)\n }\n // A boundary must be BOTH tool-pairing-balanced AND carry a bare-seq ref.\n const cleanBefore = (index: number): boolean =>\n toolPairingBalancedBefore(session, nodes[index]!) && hasPlainRef(session, nodes[index]!)\n const cleanAfter = (index: number): boolean =>\n toolPairingBalancedAfter(session, nodes[index]!) && hasPlainRef(session, nodes[index]!)\n let startIdx = requestedStartIdx\n let endIdx = requestedEndIdx\n // First pass: nudge inward to the nearest clean cuts.\n while (startIdx <= endIdx && !cleanBefore(startIdx)) {\n startIdx += 1\n }\n while (endIdx >= startIdx && !cleanAfter(endIdx)) {\n endIdx -= 1\n }\n if (startIdx <= endIdx && nodes[startIdx]! <= nodes[endIdx]!) {\n return recovered\n ? { start: nodes[startIdx]!, end: nodes[endIdx]!, recovered: true }\n : { start: nodes[startIdx]!, end: nodes[endIdx]! }\n }\n // A recovered span NEVER expands across block checkpoints: the model's\n // requested edges were stale, so growing the span into block territory could\n // fold content it never intended to touch. If the live remainder cannot be\n // balanced by shrinking alone, give up with guidance instead.\n if (recovered) {\n throw new Error(\n `billion-context-dsh: no tool-pairing-balanced live remainder around seq ${start}..${end} — `\n + 'narrow the range or consult acp_status for the current surface',\n )\n }\n // Second pass: the inward pass collapsed (a lone tool message) — expand\n // outward from the REQUESTED span to the smallest clean enclosing pair.\n startIdx = requestedStartIdx\n endIdx = requestedEndIdx\n while (startIdx > 0 && !cleanBefore(startIdx)) {\n startIdx -= 1\n }\n while (endIdx < nodes.length - 1 && !cleanAfter(endIdx)) {\n endIdx += 1\n }\n // Value order guard: the surface is locally non-monotonic after replacements\n // (a checkpoint seq inserted ahead of older residual nodes), so index order\n // alone is not enough — never return a span whose end seq is numerically\n // BEFORE its start seq. The caller (nudge / compress) skips such a span.\n if (cleanBefore(startIdx) && cleanAfter(endIdx) && nodes[startIdx]! <= nodes[endIdx]!) {\n return { start: nodes[startIdx]!, end: nodes[endIdx]! }\n }\n throw new Error(\n `billion-context-dsh: no tool-pairing-balanced range around seq ${start}..${end} — `\n + 'narrow the range or consult acp_status for the current surface',\n )\n}\n\n/** The surface seqs shadowed by the inclusive positional span. */\nexport function shadowedSeqsOf(session: Session, start: number, end: number): number[] {\n const nodes = session.surface.nodes\n const startIdx = nodes.indexOf(start)\n const endIdx = nodes.indexOf(end)\n return nodes.slice(startIdx, endIdx + 1)\n}\n\nexport interface CompactionTransactionInput {\n readonly start: number\n readonly end: number\n readonly shadowedSeqs: readonly number[]\n readonly summary: ContentBlock[]\n readonly shadowedTokenCount: number\n readonly provider: string\n readonly model: string\n /** Compression tier of this block (default 1). */\n readonly tier?: 1 | 2 | 3\n /** The acp-kernel block id (`bN`) created by the kernel for this transaction. */\n readonly kernelBlockId?: string\n /** Compaction ids of the blocks distilled into this one. */\n readonly parentBlockIds?: readonly string[]\n /** The kernel block's direct/effective message ids (raw CoreMessage ids) — recorded for faithful rehydration. */\n readonly directMessageIds?: readonly string[]\n readonly effectiveMessageIds?: readonly string[]\n}\n\n/**\n * ACP tier extension fields carried on `compaction/summary` events. The\n * upstream dsh-compaction event type does not know them, so reads and writes\n * go through this precise intersection (never `any`).\n */\nexport interface AcpCompactionSummaryFields {\n /** Compression tier (1/2/3) — 1 = message range, 2 = distills tier-1, 3 = distills tier-2. */\n readonly tier?: 1 | 2 | 3\n /** The acp-kernel block id (`bN`) created for this transaction. */\n readonly kernelBlockId?: string\n /** Durable compaction ids of the blocks distilled into this one. */\n readonly parentBlockIds?: readonly string[]\n /**\n * The kernel block's direct message ids (raw CoreMessage ids) at creation —\n * recorded so a restarted engine rehydrates the SAME coverage (a tier-2\n * block's coverage is its parents' originals, not the checkpoint node).\n */\n readonly directMessageIds?: readonly string[]\n /** The kernel block's effective message ids (raw CoreMessage ids) at creation. */\n readonly effectiveMessageIds?: readonly string[]\n}\n\ntype CompactionSummaryData = SessionEventMap['compaction/summary']\n\n/** Read a `compaction/summary` event's data including the ACP tier extension fields. */\nexport function readCompactionSummary(event: SessionEvent): CompactionSummaryData & AcpCompactionSummaryFields {\n return event.data as CompactionSummaryData & AcpCompactionSummaryFields\n}\n\n/**\n * Run one durable compression transaction. Throws on invalid state; on success\n * the four events are in the log and the surface has one summary node.\n */\nexport function runCompactionTransaction(\n session: Session,\n input: CompactionTransactionInput,\n): { compactionId: string; seqs: number[] } {\n assertNoActiveCompaction(session.events)\n const turn = findOpenTurn(session.events)\n const compactionId = CompactionId(randomUUID())\n const seqs: number[] = []\n\n seqs.push(session.append('compaction/start', { compactionId, turn }).seq)\n seqs.push(session.append('compaction/summary', {\n compactionId,\n summary: input.summary,\n shadowedRange: { start: input.start, end: input.end },\n shadowedSeqs: [...input.shadowedSeqs],\n shadowedTokenCount: input.shadowedTokenCount,\n provider: input.provider,\n model: input.model,\n tier: input.tier ?? 1,\n ...(input.kernelBlockId === undefined ? {} : { kernelBlockId: input.kernelBlockId }),\n ...(input.parentBlockIds === undefined || input.parentBlockIds.length === 0\n ? {}\n : { parentBlockIds: [...input.parentBlockIds] }),\n ...(input.directMessageIds === undefined ? {} : { directMessageIds: [...input.directMessageIds] }),\n ...(input.effectiveMessageIds === undefined ? {} : { effectiveMessageIds: [...input.effectiveMessageIds] }),\n } as CompactionSummaryData & AcpCompactionSummaryFields).seq)\n\n const message = createUserMessage({\n content: input.summary,\n source: compactCheckpointSource(compactionId),\n })\n seqs.push(session.append('user/message', message, {\n surfaceOp: { op: 'replace', start: input.start, end: input.end },\n sourceEventSeqs: [...input.shadowedSeqs],\n }).seq)\n\n seqs.push(session.append('compaction/end', { compactionId, turn }).seq)\n return { compactionId, seqs }\n}\n\n/** The seq of a compaction's checkpoint summary node in the log (visible or shadowed). */\nfunction summarySeqOfCompaction(events: readonly SessionEvent[], compactionId: string): number | null {\n for (const event of events) {\n if (event.type !== 'user/message') continue\n const source = (event.data as { source?: { plugin?: string; compactionId?: string } }).source\n if (source?.plugin === 'compact' && source.compactionId === compactionId) return event.seq\n }\n return null\n}\n\n/** Rebuild the block ledger from the durable log (no kernel state needed). */\nexport function rebuildBlockLedger(events: readonly SessionEvent[]): AcpBlockLedgerEntry[] {\n const ledger: AcpBlockLedgerEntry[] = []\n for (const event of events) {\n if (event.type !== 'compaction/summary') continue\n const data = readCompactionSummary(event)\n // Blocks written before the token-accounting fix carry shadowedTokenCount\n // 0; backfill from the shadowed originals still in the log so acp_status\n // reports real reclaimed tokens.\n let shadowedTokenCount = data.shadowedTokenCount\n if (shadowedTokenCount === 0) {\n shadowedTokenCount = 0\n for (const seq of data.shadowedSeqs) {\n const original = events[seq]\n if (original !== undefined) shadowedTokenCount += defaultCountTokens(extractEventText(original))\n }\n }\n const tier = data.tier === 2 || data.tier === 3 ? data.tier : 1\n const parentBlockIds: string[] = Array.isArray(data.parentBlockIds) ? [...data.parentBlockIds] : []\n const directMessageIds: string[] | undefined = Array.isArray(data.directMessageIds) ? [...data.directMessageIds] : undefined\n const effectiveMessageIds: string[] | undefined = Array.isArray(data.effectiveMessageIds) ? [...data.effectiveMessageIds] : undefined\n const summarySeq = summarySeqOfCompaction(events, data.compactionId)\n ledger.push({\n blockId: data.compactionId,\n summary: extractText(data.summary),\n shadowedSeqs: [...data.shadowedSeqs],\n shadowedTokenCount,\n start: data.shadowedRange.start,\n end: data.shadowedRange.end,\n tier,\n parentBlockIds,\n ...(typeof data.kernelBlockId === 'string' ? { kernelBlockId: data.kernelBlockId } : {}),\n ...(summarySeq === null ? {} : { summarySeq }),\n ...(directMessageIds === undefined ? {} : { directMessageIds }),\n ...(effectiveMessageIds === undefined ? {} : { effectiveMessageIds }),\n createdAt: event.time,\n })\n }\n return ledger\n}\n\n/** One self-computed compressible span of the current surface. */\nexport interface SeqCompressibleRange {\n readonly start: number\n readonly end: number\n readonly count: number\n readonly tokens: number\n}\n\n/** Whether a surface user message is a compaction checkpoint node (already compressed). */\nfunction isCheckpointNode(event: SessionEvent): boolean {\n if (event.type !== 'user/message') return false\n const source = (event.data as { source?: { plugin?: string } }).source\n return source?.plugin === 'compact'\n}\n\n/**\n * Compute compressible spans directly from the surface — independent of the\n * kernel's ref map, which can drift after surface replacements in long\n * sessions and hide large tool results from the nudge range table. Skips the\n * recent protected tail, the last user message, and compaction checkpoints;\n * edges are then balanced through resolveSurfaceRange. Ranges are ordered by\n * size (largest reclaimed first).\n */\nexport function buildCompressibleSeqRanges(\n session: Session,\n opts: { preserveRecent?: number } = {},\n): SeqCompressibleRange[] {\n const nodes = session.surface.nodes\n const preserve = opts.preserveRecent ?? 5\n const protectedSeqs = new Set<number>()\n for (const seq of nodes.slice(-preserve)) protectedSeqs.add(seq)\n for (let index = nodes.length - 1; index >= 0; index -= 1) {\n const event = session.events[nodes[index]!]\n if (event?.type === 'user/message' && !isCheckpointNode(event)) {\n protectedSeqs.add(nodes[index]!)\n break\n }\n }\n const raw: SeqCompressibleRange[] = []\n let cur: SeqCompressibleRange | null = null\n const flush = (): void => {\n if (cur !== null) raw.push(cur)\n cur = null\n }\n for (const seq of nodes) {\n const event = session.events[seq]\n if (event === undefined || protectedSeqs.has(seq) || isCheckpointNode(event)) {\n flush()\n continue\n }\n // Surface nodes can be locally out of order after surface replacements in\n // long sessions; a node with a SMALLER seq than the running segment would\n // produce a reversed range (e.g. 110295..106762). Break the segment so\n // ranges always stay start <= end.\n if (cur !== null && seq < cur.start) {\n flush()\n cur = null\n }\n const tokens = defaultCountTokens(extractEventText(event))\n if (cur === null) {\n cur = { start: seq, end: seq, count: 1, tokens }\n } else {\n cur = { start: cur.start, end: seq, count: cur.count + 1, tokens: cur.tokens + tokens }\n }\n }\n flush()\n const out: SeqCompressibleRange[] = []\n for (const range of raw) {\n try {\n const { start, end } = resolveSurfaceRange(session, range.start, range.end)\n const count = range.count\n out.push({ start, end, count, tokens: range.tokens })\n } catch {\n // Cannot be balanced into a compressible span — skip.\n }\n }\n return out.sort((a, b) => b.tokens - a.tokens)\n}\n\n/**\n * A compact human-readable description of the current surface for the model:\n * node count plus the first/last message seqs. Surface seqs are sparse (the\n * event log interleaves non-message events and expanded delta batches), so a\n * model that never saw the nudge range table — e.g. low-pressure sessions\n * where no nudge fires — cannot guess its own seq space. acp_status and the\n * nudge's range table both surface this so compress edges can be located\n * without blind probing.\n */\nexport function surfaceSummary(session: Session): string {\n const nodes = session.surface.nodes\n if (nodes.length === 0) return 'empty'\n const first = nodes[0]!\n const last = nodes[nodes.length - 1]!\n return `${nodes.length} nodes, seqs ${first}..${last}`\n}\n\n/** One block as seen by the tier machinery: durable id ↔ kernel ref (`bN`). */\nexport interface AcpBlockRegistryEntry {\n /** The durable compaction id. */\n readonly blockId: string\n /** The acp-kernel block ref (`bN`); synthesised by log order for legacy blocks. */\n readonly kernelBlockId: string\n readonly tier: 1 | 2 | 3\n /** The surface seq of this block's checkpoint summary node (null when gone). */\n readonly summarySeq: number | null\n /** True until a LATER block distills this one. Only active blocks are distillable. */\n readonly active: boolean\n readonly parentBlockIds: readonly string[]\n}\n\n/**\n * Rebuild the compactionId ↔ kernel-block-ref registry from the durable log.\n * Legacy blocks (pre-tier, no recorded `kernelBlockId`) are synthesised as\n * `b1`, `b2`, … in log order; recorded ids are kept as-is. A block is active\n * until a later block lists it as a parent.\n */\nexport function blockRegistry(session: Session): AcpBlockRegistryEntry[] {\n const ledger = rebuildBlockLedger(session.events)\n const kernelIdOf = new Map<string, string>()\n const raw: AcpBlockRegistryEntry[] = []\n let next = 1\n for (const entry of ledger) {\n let kernelBlockId: string\n if (entry.kernelBlockId !== undefined && /^b\\d+$/.test(entry.kernelBlockId)) {\n kernelBlockId = entry.kernelBlockId\n const num = Number(kernelBlockId.slice(1))\n if (Number.isInteger(num)) next = Math.max(next, num + 1)\n } else {\n kernelBlockId = `b${next}`\n next += 1\n }\n kernelIdOf.set(entry.blockId, kernelBlockId)\n raw.push({\n blockId: entry.blockId,\n kernelBlockId,\n tier: entry.tier,\n summarySeq: entry.summarySeq ?? null,\n active: true,\n parentBlockIds: [...entry.parentBlockIds],\n })\n }\n const consumed = new Set<string>()\n for (const entry of raw) {\n for (const parent of entry.parentBlockIds) consumed.add(parent)\n }\n return raw.map((entry) => ({\n ...entry,\n active: !consumed.has(entry.blockId),\n }))\n}\n\n/**\n * The kernel block ref (`bN`) for a surface seq, when that seq is the\n * checkpoint summary node of a block — the edge the model must use to\n * distill (T2/T3). Active blocks distill; a stale (already-distilled) node\n * still maps to its `bN` so the kernel reports \"already compressed\" instead\n * of silently folding the summary as a plain message. Returns null for\n * anything else (plain messages, non-checkpoint nodes).\n */\nexport function blockRefForSummarySeq(session: Session, seq: number): string | null {\n const event = session.events[seq]\n if (event?.type !== 'user/message') return null\n const source = (event.data as { source?: { plugin?: string; compactionId?: string } }).source\n if (source?.plugin !== 'compact' || source.compactionId === undefined) return null\n const entry = blockRegistry(session).find((r) => r.blockId === source.compactionId)\n if (entry === undefined) return null\n return entry.kernelBlockId\n}\n\n/** The durable compaction ids distilled by the given kernel block refs (`bN`). */\nexport function compactionIdsOfKernelBlocks(session: Session, kernelBlockIds: readonly string[]): string[] {\n if (kernelBlockIds.length === 0) return []\n const byKernel = new Map(blockRegistry(session).map((r) => [r.kernelBlockId, r.blockId]))\n return kernelBlockIds\n .map((id) => byKernel.get(id))\n .filter((id): id is string => id !== undefined)\n}\n\n/** The checkpoint summary seq of an ACTIVE kernel block (`bN`), or null. */\nexport function summarySeqOfKernelBlock(session: Session, kernelBlockId: string): number | null {\n const entry = blockRegistry(session).find((r) => r.kernelBlockId === kernelBlockId)\n return entry?.active ? entry.summarySeq : null\n}\n\n/** The durable block whose checkpoint node sits at `seq` (or null). */\nfunction checkpointBlockIdOf(events: readonly SessionEvent[], seq: number): string | null {\n const event = events[seq]\n if (event?.type !== 'user/message') return null\n const source = (event.data as { source?: { plugin?: string; compactionId?: string } }).source\n if (source?.plugin !== 'compact' || source.compactionId === undefined) return null\n return source.compactionId\n}\n\n/**\n * The shadowed seqs of a block, recursing into distilled parent blocks: a\n * tier-2 block shadows its parent's checkpoint node, so recovering its\n * originals requires expanding that node into the parent block's own shadowed\n * seqs. Cycle-safe (a block can never be its own ancestor).\n */\nexport function expandShadowedSeqs(session: Session, blockId: string): number[] {\n const ledger = rebuildBlockLedger(session.events)\n const byId = new Map(ledger.map((entry) => [entry.blockId, entry]))\n const root = byId.get(blockId)\n if (root === undefined) return []\n const out: number[] = []\n const seen = new Set<string>()\n const visit = (entry: AcpBlockLedgerEntry): void => {\n if (seen.has(entry.blockId)) return\n seen.add(entry.blockId)\n for (const seq of entry.shadowedSeqs) {\n const childId = checkpointBlockIdOf(session.events, seq)\n const child = childId === null ? undefined : byId.get(childId)\n if (child !== undefined) visit(child)\n else out.push(seq)\n }\n }\n visit(root)\n return out\n}\n","/**\n * M1 — session-log projection: DSH surface events → acp-kernel CoreMessage.\n *\n * The ACP kernel is message-array based; DSH is event-log based. This module\n * is the bridge in the direction the engine needs (projectEvent /\n * eventsToCoreMessages). The reverse direction (CoreMessage[] → session\n * appends) is the M5 region transaction's job.\n * Mirrors billion-context-pi's `projectMessage`/`entriesToCoreMessages`\n * against DSH event shapes (see V-verification: SurfaceEventType =\n * 'user/message' | 'assistant/message' | 'tool/result').\n * @module billion-context-pi-dsh/messages\n */\n\nimport type { CoreMessage } from 'acp-kernel'\nimport type { SessionEvent } from '@deepseek-ai/dsh-session'\n\n/**\n * Extract plain text from a DSH content block array or string.\n *\n * Recursive: a real DSH `tool-result` block is `{ type: 'tool-result',\n * toolCallId, content: ContentBlock[] }` — the inner `content` array holds\n * the actual `text` blocks, so a top-level-only walk would drop every tool\n * result from the projection (and with it the seq's ref assignment, breaking\n * compress boundary resolution). Nested arrays are flattened depth-first.\n */\nexport function extractText(content: unknown): string {\n if (typeof content === 'string') return content\n if (!Array.isArray(content)) return ''\n const parts: string[] = []\n for (const block of content) {\n if (block === null || typeof block !== 'object') continue\n const b = block as { type?: unknown; text?: unknown; content?: unknown }\n if (b.type === 'text' && typeof b.text === 'string') {\n parts.push(b.text)\n } else if (Array.isArray(b.content)) {\n parts.push(extractText(b.content))\n }\n }\n return parts.join('\\n')\n}\n\ninterface ToolCallBlock {\n type: 'tool-call'\n id?: string\n name?: string\n arguments?: unknown\n}\n\nfunction toolCallsOf(content: unknown): ToolCallBlock[] {\n if (!Array.isArray(content)) return []\n return content.filter((b): b is ToolCallBlock => (b as { type?: string }).type === 'tool-call')\n}\n\nfunction stringifyArgs(args: unknown): string {\n if (!args) return ''\n if (typeof args === 'string') return args\n try {\n return JSON.stringify(args)\n } catch {\n return String(args)\n }\n}\n\n/**\n * Project one surface message event into CoreMessage(s).\n * - user/message → user text (verbatim content)\n * - assistant/message → assistant text, or one CoreMessage per tool-call\n * - tool/result → tool result (toolName/toolCallId, role 'tool')\n * Non-surface events project to nothing.\n */\nexport function projectEvent(event: SessionEvent): CoreMessage[] {\n switch (event.type) {\n case 'user/message': {\n const text = extractText((event.data as { content?: unknown }).content)\n return text.length > 0 ? [{ id: String(event.seq), role: 'user', contentType: 'text', text }] : []\n }\n case 'assistant/message': {\n const content = (event.data as { message?: { content?: unknown } }).message?.content\n const calls = toolCallsOf(content)\n const text = extractText(content)\n if (calls.length === 0) {\n return text.trim().length > 0\n ? [{ id: String(event.seq), role: 'assistant', contentType: 'text', text }]\n : []\n }\n if (calls.length === 1) {\n const call = calls[0]!\n const argStr = stringifyArgs(call.arguments)\n const body = argStr && text ? `${text}\\n${argStr}` : argStr || text\n return [{\n id: String(event.seq),\n role: 'assistant',\n contentType: 'tool-call',\n toolName: call.name ?? '',\n toolCallId: call.id ?? '',\n text: body,\n }]\n }\n return calls.map((call) => ({\n id: `${event.seq}#${call.id ?? ''}`,\n role: 'assistant' as const,\n contentType: 'tool-call' as const,\n toolName: call.name ?? '',\n toolCallId: call.id ?? '',\n text: stringifyArgs(call.arguments) || text,\n }))\n }\n case 'tool/result': {\n const message = (event.data as {\n message?: { content?: unknown; toolName?: string; toolCallId?: string }\n }).message\n const text = extractText(message?.content)\n if (text.length === 0) return []\n return [{\n id: String(event.seq),\n role: 'tool',\n contentType: 'tool-result',\n toolName: message?.toolName ?? '',\n toolCallId: message?.toolCallId ?? '',\n text,\n }]\n }\n default:\n return []\n }\n}\n\n/** Project a session's message events into CoreMessage[] in log order. */\nexport function eventsToCoreMessages(events: readonly SessionEvent[]): CoreMessage[] {\n const out: CoreMessage[] = []\n for (const event of events) out.push(...projectEvent(event))\n return out\n}\n\n/** The surface-visible message events of a session, in model-visible order. */\nexport function surfaceEventsOf(session: import('@deepseek-ai/dsh-session').Session): SessionEvent[] {\n return session.surface.nodes\n .map((seq) => session.events[seq])\n .filter((event): event is SessionEvent => event !== undefined)\n}\n\n/**\n * ALL message-type events in log order — the visible surface PLUS everything\n * shadowed by compression. The ACP kernel deactivates any block whose consumed\n * message ids are absent from the array it is given (syncBlocks), and refuses\n * to anchor a block boundary that cannot find its messages, so T2/T3\n * distillation requires the full log, not just the visible surface.\n */\nexport function allLogMessages(session: import('@deepseek-ai/dsh-session').Session): CoreMessage[] {\n return eventsToCoreMessages(session.events)\n}\n\n/** Extract the model-facing text of any surface message event. */\nexport function extractEventText(event: SessionEvent): string {\n switch (event.type) {\n case 'user/message':\n return extractText((event.data as { content?: unknown }).content)\n case 'assistant/message':\n return extractText((event.data as { message?: { content?: unknown } }).message?.content)\n case 'tool/result':\n return extractText((event.data as { message?: { content?: unknown } }).message?.content)\n default:\n return ''\n }\n}\n","/**\n * M3 — the four model tools: compress / decompress / search_context /\n * acp_status, registered through `ctx.tools` (defineTool).\n *\n * compress is the heart of ACP: the model writes the summary and the tool\n * lands it as a durable surface replacement (no second LLM summarization\n * call). decompress recovers shadowed content read-only from the log (DSH\n * keeps the originals — V5). search_context scores blocks rebuilt from the\n * log. acp_status reports the block ledger and pressure.\n * @module billion-context-dsh/tools\n */\n\nimport { defineTool, type ToolDefinition, type ToolRunContext } from '@deepseek-ai/dsh-tools'\nimport { defaultCountTokens, type CompressionCore } from 'acp-kernel'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport type { AcpStateStore } from './state.ts'\nimport { kernelConfigFor, type KernelConfigInput } from './config.ts'\nimport { windowSourceLabel, type AcpWindow } from './window.ts'\nimport {\n AlreadyCompressedRangeError,\n blockRefForSummarySeq,\n compactionIdsOfKernelBlocks,\n expandShadowedSeqs,\n rebuildBlockLedger,\n resolveSurfaceRange,\n runCompactionTransaction,\n shadowedSeqsOf,\n surfaceSummary,\n type ResolvedSurfaceRange,\n} from './region.ts'\nimport { allLogMessages, eventsToCoreMessages, extractEventText, surfaceEventsOf } from './messages.ts'\n\nexport interface ToolEnvironment extends KernelConfigInput {\n readonly kernel: CompressionCore\n readonly store: AcpStateStore\n /** Resolve the effective context window for an agent (optional: status falls back to modelContextLimit). */\n readonly windowFor?: (agent: Agent) => Promise<AcpWindow>\n}\n\ninterface TextOutput {\n text: string\n}\n\nfunction textOutput(): {\n schema: { type: 'object'; properties: { text: { type: 'string' } }; additionalProperties: boolean }\n render: (args: unknown, value: TextOutput) => import('@deepseek-ai/dsh-llm').ContentBlock[]\n} {\n return {\n schema: {\n type: 'object',\n properties: { text: { type: 'string' } },\n additionalProperties: false,\n },\n render: (_args, value) => [{ type: 'text', text: value.text }],\n }\n}\n\nfunction requireAgent(exec: ToolRunContext): Agent {\n if (exec.agent === undefined) {\n throw new Error('billion-context-dsh: tool requires an agent execution context')\n }\n return exec.agent\n}\n\nconst compressParameters = {\n topic: { type: 'string' as const, description: 'Fallback topic for entries without their own.' },\n content: {\n type: 'array' as const,\n required: true,\n description: 'One or more ranges to compress, each with startSeq/endSeq boundaries (surface seqs) and a dense summary.',\n items: {\n type: 'object' as const,\n properties: {\n startSeq: {\n oneOf: [\n { type: 'integer' as const, description: 'First surface seq of the range.' },\n { type: 'string' as const, description: 'Seq as text; a trailing #callId fragment is ignored.' },\n ],\n },\n endSeq: {\n oneOf: [\n { type: 'integer' as const, description: 'Inclusive last surface seq of the range.' },\n { type: 'string' as const, description: 'Seq as text; a trailing #callId fragment is ignored.' },\n ],\n },\n summary: { type: 'string' as const, description: 'Complete technical summary replacing the range; keep paths, decisions, values verbatim. Minimum 50 characters.' },\n topic: { type: 'string' as const, description: 'Short label (3-5 words) for this range.' },\n },\n additionalProperties: false,\n },\n },\n} as const\n\n/** Normalize a seq arg: number, \"295\", or \"295#call_00_xxx\" → 295. */\nfunction parseSeq(value: number | string): number {\n const text = String(value).split('#')[0]!.trim()\n const seq = Number(text)\n if (!Number.isInteger(seq) || seq < 0) {\n throw new Error(`billion-context-dsh: invalid seq \"${String(value)}\" — use a surface seq like 295`)\n }\n return seq\n}\n\ninterface CompressArgs {\n topic?: string\n content: Array<{ startSeq: number | string; endSeq: number | string; summary: string; topic?: string }>\n}\n\n/** Resolve seq → kernel ref, then applyCompression and land the transaction. */\nasync function handleCompress(env: ToolEnvironment, args: CompressArgs, exec: ToolRunContext): Promise<TextOutput> {\n const agent = requireAgent(exec)\n const session = agent.session\n const state = env.store.stateFor(session)\n // The kernel gets the FULL log (visible + shadowed): syncBlocks deactivates\n // a block whose consumed messages are absent, and resolveBoundaries refuses\n // to anchor a block ref it cannot find, so tier-2/3 distillation needs the\n // originals present. The token count stays a surface measurement.\n const coreMessages = allLogMessages(session)\n const surfaceMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const tokenCount = surfaceMessages.reduce((sum, message) => sum + defaultCountTokens(message.text ?? ''), 0)\n const config = kernelConfigFor(env)\n\n // Assign refs / advance state exactly like a turn would.\n const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount })\n env.store.set(session, turn.state)\n const byRaw = turn.state.messageRefs.byRaw\n\n const ranges: Array<\n ResolvedSurfaceRange & {\n startSeq: number\n endSeq: number\n startRef: string\n endRef: string\n summary: string\n topic?: string\n }\n > = []\n // Ranges whose whole span was already shadowed by earlier compressions.\n // They land as advisory warnings, never as errors or phantom blocks.\n const alreadyCompressedNotes: string[] = []\n for (const range of args.content) {\n const startSeq = parseSeq(range.startSeq)\n const endSeq = parseSeq(range.endSeq)\n let resolved: ResolvedSurfaceRange\n try {\n // Balance edges FIRST: the requested edges may sit on multi-tool-call\n // assistant messages, which project to `${seq}#${callId}` CoreMessage ids\n // and therefore have NO bare-`${seq}` ref. resolveSurfaceRange shifts them\n // to clean tool-pairing-balanced cuts that always carry a bare ref, so the\n // resolved refs exist and the shadowed span matches the returned range.\n // Edges shadowed by an earlier compression (stale nudge table / old\n // compress result) are remapped to the still-live content of the span.\n resolved = resolveSurfaceRange(session, startSeq, endSeq)\n } catch (error) {\n if (error instanceof AlreadyCompressedRangeError) {\n const covering = error.coveringBlockIds\n const blockNote = covering.length === 0\n ? ''\n : ` (block ${covering[0]!.slice(0, 8)}${covering.length > 1 ? ` +${covering.length - 1} more` : ''})`\n alreadyCompressedNotes.push(\n ` seqs ${error.start}..${error.end} already compressed${blockNote} — nothing to reclaim; decompress to recover the originals`,\n )\n continue\n }\n throw error\n }\n // An edge on an ACTIVE block's checkpoint summary node resolves to the\n // kernel block ref (bN) — the boundary that makes applyCompression distill\n // (tier 2/3) instead of folding the summary as a plain message.\n const startBlockRef = blockRefForSummarySeq(session, resolved.start)\n const endBlockRef = blockRefForSummarySeq(session, resolved.end)\n const startRef = startBlockRef ?? byRaw[String(resolved.start)]\n const endRef = endBlockRef ?? byRaw[String(resolved.end)]\n if (startRef === undefined || endRef === undefined) {\n throw new Error(\n `billion-context-dsh: seq ${resolved.start}..${resolved.end} has no assigned ref — `\n + 'the range must be on the current surface (run acp_status for the live seq list)',\n )\n }\n ranges.push({\n ...resolved,\n startSeq,\n endSeq,\n startRef,\n endRef,\n summary: range.summary,\n ...(range.topic ?? args.topic) === undefined ? {} : { topic: range.topic ?? args.topic },\n })\n }\n\n // Nothing to do: every requested range was already compressed.\n if (ranges.length === 0) {\n const text = ['Compressed 0 block(s), ~0 tokens reclaimed.', ...alreadyCompressedNotes]\n if (alreadyCompressedNotes.length > 0) {\n text.push(' (all requested ranges were already compressed — decompress a block to recover its originals)')\n }\n return { text: text.join('\\n') }\n }\n\n const applied = env.kernel.applyCompression({\n ranges: ranges.map(({ startRef, endRef, summary, topic }) => ({ startRef, endRef, summary, topic })),\n messages: coreMessages,\n state: turn.state,\n config,\n // Deliberately NOT overriding protectedMessageIds: with the full log the\n // kernel's recent/last-user protection is computed over the same\n // non-block-covered messages as the visible feed, so default behavior is\n // preserved. Any 'Excluded N protected message(s)' warning is surfaced.\n })\n if (applied.result.errors.length > 0) {\n return { text: `compress failed: ${applied.result.errors.join('; ')}` }\n }\n env.store.set(session, applied.state)\n\n // Match freshly created kernel blocks to the requested ranges by their\n // range key (the kernel stamps startRef/endRef onto each new block).\n const previousIds = new Set(turn.state.blocks.map((block) => block.blockId))\n const newBlocks = applied.state.blocks.filter((block) => !previousIds.has(block.blockId))\n const blockByRangeKey = new Map(newBlocks.map((block) => [`${block.startRef}::${block.endRef}`, block]))\n // Warnings carry two shapes: range-prefixed (\"Skipped range (a..b) — …\")\n // attributable to a specific range, and free-form (\"Excluded N protected\n // message(s) …\") attributable to the call as a whole.\n const warningByRangeKey = new Map<string, string[]>()\n const freeWarnings: string[] = []\n for (const warning of applied.result.warnings) {\n const match = /^Skipped range \\((.+?)\\.\\.(.+?)\\)/.exec(warning)\n if (match !== null) {\n const key = `${match[1]}::${match[2]}`\n const list = warningByRangeKey.get(key) ?? []\n list.push(warning)\n warningByRangeKey.set(key, list)\n } else {\n freeWarnings.push(warning)\n }\n }\n\n const lines: string[] = []\n let skippedRanges = 0\n for (const range of ranges) {\n const key = `${range.startRef}::${range.endRef}`\n const block = blockByRangeKey.get(key)\n if (block === undefined) {\n // The kernel skipped this range (already compressed / overlapped): no\n // kernel block was created, so no durable transaction is landed — the\n // ledger must never record a block the kernel does not know.\n skippedRanges += 1\n const warnings = warningByRangeKey.get(key) ?? []\n for (const warning of warnings) lines.push(` ${warning}`)\n continue\n }\n // The edges were already balanced above; shadow exactly that span.\n const { start, end } = range\n const shadowed = shadowedSeqsOf(session, start, end)\n // Estimate the reclaimed tokens from the actual shadowed messages so the\n // durable ledger (compaction/summary.shadowedTokenCount) reports a real\n // number instead of 0.\n let shadowedTokens = 0\n for (const seq of shadowed) {\n const event = session.events[seq]\n if (event !== undefined) shadowedTokens += defaultCountTokens(extractEventText(event))\n }\n const tier = block.tier === 2 || block.tier === 3 ? block.tier : 1\n const parentBlockIds = compactionIdsOfKernelBlocks(session, block.directBlockIds)\n const { compactionId } = runCompactionTransaction(session, {\n start,\n end,\n shadowedSeqs: shadowed,\n summary: [{ type: 'text', text: range.summary }],\n shadowedTokenCount: shadowedTokens,\n provider: agent.options.provider ?? '',\n model: agent.options.model ?? '',\n tier,\n kernelBlockId: block.blockId,\n ...(parentBlockIds.length === 0 ? {} : { parentBlockIds }),\n // Record the kernel block's raw coverage so a restarted engine\n // rehydrates the SAME effective messages (a tier-2 block's coverage is\n // its parents' originals, not the checkpoint node).\n directMessageIds: block.directMessageIds,\n effectiveMessageIds: block.effectiveMessageIds,\n })\n const adjusted = start !== range.startSeq || end !== range.endSeq\n const tierLabel = tier === 1 ? '' : `, tier ${tier}`\n const note = range.recovered === true\n ? ` (seqs ${range.startSeq}..${range.endSeq} were already shadowed — compressed the live remainder ${start}..${end})`\n : adjusted\n ? ` (adjusted from ${range.startSeq}..${range.endSeq} to balanced edges)`\n : ''\n lines.push(\n ` block ${compactionId.slice(0, 8)}: seqs ${start}..${end}, ${shadowed.length} messages shadowed${tierLabel}${note}`,\n )\n }\n\n const summaryLine = `Compressed ${applied.result.blocksCreated} block(s), ~${applied.result.tokensCompressed} tokens reclaimed.`\n const totalSkipped = skippedRanges + alreadyCompressedNotes.length\n const warningLines = [...freeWarnings.map((warning) => ` ${warning}`), ...alreadyCompressedNotes, ...lines]\n const footer = totalSkipped > 0\n ? ` (${totalSkipped} range(s) skipped — see warnings above)`\n : ''\n return { text: `${summaryLine}\\n${[...warningLines, footer].filter((line) => line !== '').join('\\n')}` }\n}\n\nconst decompressParameters = {\n blockId: { type: 'string' as const, required: true, description: 'Block id from acp_status or search_context (the compaction id).' },\n} as const\n\ninterface DecompressArgs {\n blockId: string\n}\n\nfunction handleDecompress(_env: ToolEnvironment, args: DecompressArgs, exec: ToolRunContext): TextOutput {\n const session = requireAgent(exec).session\n const ledger = rebuildBlockLedger(session.events)\n const block = ledger.find((entry) => entry.blockId.startsWith(args.blockId))\n if (block === undefined) {\n return { text: `decompress: block \"${args.blockId}\" not found (see acp_status for the block list)` }\n }\n const parts: string[] = []\n // Tier-2/3 blocks shadow parent checkpoint nodes: expand to the originals.\n for (const seq of expandShadowedSeqs(session, block.blockId)) {\n const event = session.events[seq]\n const text = event === undefined ? '' : extractEventText(event)\n if (text.length > 0) parts.push(`[seq ${seq}] ${text}`)\n }\n const tierNote = block.tier > 1 ? ` (tier ${block.tier}, distills ${block.parentBlockIds.length} block(s))` : ''\n return {\n text: `Block ${block.blockId} — ${block.summary}${tierNote}\\n\\n${parts.join('\\n\\n') || '(no recoverable content)'}`,\n }\n}\n\nconst searchParameters = {\n query: { type: 'string' as const, required: true, description: 'Search terms to find inside compressed blocks.' },\n limit: { type: 'integer' as const, description: 'Maximum results (default 5).' },\n} as const\n\ninterface SearchArgs {\n query: string\n limit?: number\n}\n\nfunction handleSearch(_env: ToolEnvironment, args: SearchArgs, exec: ToolRunContext): TextOutput {\n const session = requireAgent(exec).session\n const ledger = rebuildBlockLedger(session.events)\n const terms = args.query.toLowerCase().split(/\\s+/).filter(Boolean)\n const scored: Array<{ blockId: string; score: number; summary: string }> = []\n for (const block of ledger) {\n const original = block.shadowedSeqs\n .map((seq) => extractEventText(session.events[seq]!))\n .join('\\n')\n const haystack = `${block.summary}\\n${original}`.toLowerCase()\n let score = 0\n for (const term of terms) score += haystack.split(term).length - 1\n if (score > 0) scored.push({ blockId: block.blockId, score, summary: block.summary })\n }\n scored.sort((a, b) => b.score - a.score)\n const top = scored.slice(0, args.limit ?? 5)\n if (top.length === 0) return { text: `search_context: no matches for \"${args.query}\"` }\n return {\n text: `Matches for \"${args.query}\":\\n`\n + top.map((hit) => ` - ${hit.blockId} (score ${hit.score}): ${hit.summary.slice(0, 160)}`).join('\\n')\n + '\\n\\nDecompress with: decompress({ blockId })',\n }\n}\n\nconst statusParameters = {} as const\n\ninterface StatusArgs {\n [key: string]: never\n}\n\nasync function handleStatus(env: ToolEnvironment, _args: StatusArgs, exec: ToolRunContext): Promise<TextOutput> {\n const agent = requireAgent(exec)\n const session = agent.session\n const ledger = rebuildBlockLedger(session.events)\n const totalTokens = ledger.reduce((sum, block) => sum + block.shadowedTokenCount, 0)\n const coreMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const estimated = coreMessages.reduce((sum, message) => sum + defaultCountTokens(message.text ?? ''), 0)\n const window = env.windowFor === undefined\n ? { limit: env.modelContextLimit, source: 'explicit' as const }\n : await env.windowFor(agent)\n const limit = window.limit\n const lines = [\n `ACP status — session ${session.id}`,\n ` blocks: ${ledger.length}`,\n ` tokens compressed: ${totalTokens}`,\n ` estimated context: ${estimated} / ${limit} (${Math.round((estimated / limit) * 100)}%)`,\n ` context window: ${limit} (${windowSourceLabel(window)})`,\n ` surface: ${surfaceSummary(session)}`,\n ]\n for (const block of ledger.slice(0, 10)) {\n lines.push(` - ${block.blockId.slice(0, 8)}: seqs ${block.start}..${block.end} (${block.shadowedSeqs.length} msgs) — ${block.summary.slice(0, 80)}`)\n }\n return { text: lines.join('\\n') }\n}\n\n/** Build the four ACP model tools bound to one engine. */\nexport function makeTools(env: ToolEnvironment): ToolDefinition[] {\n return [\n defineTool({\n name: 'compress',\n description:\n 'Replace older conversation ranges with dense summaries you write. '\n + 'Each message seq is a surface reference. Single range: compress({ content: [{ startSeq, endSeq, summary }] }). '\n + 'Batch multiple unrelated ranges in one call (each content entry becomes its own block); keep ranges disjoint. '\n + 'Never compress content the current step is actively using. '\n + 'Seq refs must come from the CURRENT surface (acp_status or the latest nudge): a span whose edges were shadowed '\n + 'by an earlier compress is auto-remapped to its still-live content, a fully compressed span is reported as '\n + 'already compressed, and invented/other-session seqs fail with guidance.',\n parameters: compressParameters,\n output: textOutput(),\n async execute(args, exec) {\n return handleCompress(env, args as CompressArgs, exec)\n },\n }),\n defineTool({\n name: 'decompress',\n description: 'Recover the original content of a compressed block by its blockId (read-only; does not unshadow the range).',\n parameters: decompressParameters,\n output: textOutput(),\n execute(args, exec) {\n return Promise.resolve(handleDecompress(env, args as DecompressArgs, exec))\n },\n }),\n defineTool({\n name: 'search_context',\n description: 'Search inside compressed blocks (summaries and original content) for information the model no longer sees in context.',\n parameters: searchParameters,\n output: textOutput(),\n execute(args, exec) {\n return Promise.resolve(handleSearch(env, args as SearchArgs, exec))\n },\n }),\n defineTool({\n name: 'acp_status',\n description: 'Report the ACP block ledger: compressed blocks, reclaimed tokens, and current context pressure.',\n parameters: statusParameters,\n output: textOutput(),\n execute(args, exec) {\n return handleStatus(env, args as StatusArgs, exec)\n },\n }),\n ]\n}\n","/**\n * Kernel configuration assembly — the DSH counterpart of billion-context-pi's\n * `resolveConfig`: build acp-kernel's `Config` from adapter-level knobs.\n *\n * Defaults are deliberately the acp-kernel `defaultConfig` values (the same\n * defaults billion-context-pi ships: nudge window 45%–75%, emergency 95%,\n * growth ratio 5%, protected last messages 5). Every knob is optional — an\n * omitted value keeps the kernel default, so the behavior matches the Pi\n * adapter exactly unless a deployment opts out.\n *\n * NOTE: `AcpCompactionEngine` (src/index.ts) ships its own engine-level\n * defaults 0.70/0.85 for the two nudge thresholds on top of this layer, so an\n * engine with no explicit config lands on 0.70/0.85, not 0.75/0.95.\n * @module billion-context-dsh/config\n */\n\nimport { defaultConfig, type Config } from 'acp-kernel'\n\n/** The kernel-facing knobs shared by the nudge path and the compress tool. */\nexport interface KernelConfigInput {\n readonly modelContextLimit: number\n /** Nudge window lower bound (usage fraction; validation only — the growth-driven trigger has no percentage floor). Kernel default: 0.45. */\n readonly nudgeMinContextLimitPct?: number\n /** Nudge window upper bound — over-limit guarantee line. Kernel default: 0.75. */\n readonly nudgeMaxContextLimitPct?: number\n /** Emergency nudge threshold (bypasses per-turn dedup). Kernel default: 0.95. */\n readonly nudgeEmergencyThresholdPct?: number\n /** Any other acp-kernel Config override (the billion-context-pi escape hatch). */\n readonly coreOverrides?: Partial<Config>\n}\n\n/**\n * Assemble the kernel config: `defaultConfig(limit)` merged with the optional\n * nudge thresholds (merged into the defaults, never replacing them wholesale)\n * and any additional `coreOverrides`.\n */\nexport function kernelConfigFor(input: KernelConfigInput): Config {\n const nudgePatch: Partial<Config['nudge']> = {}\n if (input.nudgeMinContextLimitPct !== undefined) nudgePatch.minContextLimitPct = input.nudgeMinContextLimitPct\n if (input.nudgeMaxContextLimitPct !== undefined) nudgePatch.maxContextLimitPct = input.nudgeMaxContextLimitPct\n if (input.nudgeEmergencyThresholdPct !== undefined) nudgePatch.emergencyThresholdPct = input.nudgeEmergencyThresholdPct\n\n const overrides: Partial<Config> = { ...input.coreOverrides }\n if (Object.keys(nudgePatch).length > 0) {\n overrides.nudge = { ...defaultConfig(input.modelContextLimit).nudge, ...nudgePatch }\n }\n return defaultConfig(input.modelContextLimit, overrides)\n}\n","/**\n * Auto context-window detection — resolve the model's real context window\n * from the host LLM runtime instead of trusting a hardcoded config default.\n *\n * `agent.ctx.llm` (the cordis `LlmRuntime` service) exposes\n * `resolveModelInfo(provider, model)` → `{ context: { contextWindow } }`, the\n * exact-route capacity the adapter learned from the provider API (pi-ai reads\n * `context_window`/`context_length` during discovery). Probing is a standalone\n * capability query — no request is sent.\n * @module billion-context-dsh/window\n */\n\nimport type { Agent } from '@deepseek-ai/dsh-agent'\n\n/** Fallback window when auto-detection is unavailable. Same default as acp-kernel's `defaultConfig`. */\nexport const DEFAULT_CONTEXT_WINDOW = 128000\n\n/** The effective context window plus where it came from. */\nexport interface AcpWindow {\n /** Effective context window in tokens. */\n readonly limit: number\n /** Where the limit came from. */\n readonly source: 'explicit' | 'auto' | 'default'\n /** Route the auto window was resolved for (auto source only). */\n readonly provider?: string\n readonly model?: string\n}\n\n/** Human label for an AcpWindow's source (used by acp_status). */\nexport function windowSourceLabel(window: AcpWindow): string {\n if (window.source === 'explicit') return 'configured'\n if (window.source === 'auto') {\n return `auto-detected from ${window.provider ?? '?'}/${window.model ?? '?'}`\n }\n return 'default (auto-detection unavailable)'\n}\n\n/** The minimal LlmRuntime surface the probe needs (structural — no as any). */\ninterface LlmProbe {\n resolveModelInfo?: (\n provider: string,\n model: string,\n signal?: AbortSignal,\n ) => Promise<{ context?: { contextWindow?: number } }>\n}\n\n/**\n * Probe the model's real context window. Returns null when the host provides\n * no llm service, the adapter discloses no window, or the probe throws —\n * callers fall back to DEFAULT_CONTEXT_WINDOW. Never throws.\n */\nexport async function detectContextWindow(\n agent: Agent,\n provider: string,\n model: string,\n): Promise<number | null> {\n const llm = agent.ctx?.get?.('llm') as LlmProbe | undefined\n if (llm?.resolveModelInfo === undefined) return null\n try {\n const info = await llm.resolveModelInfo(provider, model)\n const window = info?.context?.contextWindow\n if (typeof window === 'number' && Number.isInteger(window) && window > 0) return window\n return null\n } catch {\n return null\n }\n}\n","/**\n * M4 — the `/acp` slash command: a human-friendly window into the same\n * machinery the model tools expose (status, one-shot compress, decompress).\n * @module billion-context-dsh/commands\n */\n\nimport type { CommandDefinition } from '@deepseek-ai/dsh-commands'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport type { ToolEnvironment } from './tools.ts'\nimport {\n blockRefForSummarySeq,\n expandShadowedSeqs,\n rebuildBlockLedger,\n resolveSurfaceRange,\n runCompactionTransaction,\n shadowedSeqsOf,\n} from './region.ts'\nimport { eventsToCoreMessages, extractEventText, surfaceEventsOf } from './messages.ts'\nimport { defaultConfig, defaultCountTokens } from 'acp-kernel'\nimport { windowSourceLabel } from './window.ts'\n\nasync function statusText(env: ToolEnvironment, agent: Agent): Promise<string> {\n const session = agent.session\n const ledger = rebuildBlockLedger(session.events)\n const totalTokens = ledger.reduce((sum, block) => sum + block.shadowedTokenCount, 0)\n const coreMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const estimated = coreMessages.reduce((sum, message) => sum + defaultCountTokens(message.text ?? ''), 0)\n const window = env.windowFor === undefined\n ? { limit: env.modelContextLimit, source: 'explicit' as const }\n : await env.windowFor(agent)\n const limit = window.limit\n const lines = [\n `ACP status — session ${session.id}`,\n ` blocks: ${ledger.length}`,\n ` tokens compressed: ${totalTokens}`,\n ` estimated context: ${estimated} / ${limit} (${Math.round((estimated / limit) * 100)}%)`,\n ` context window: ${limit} (${windowSourceLabel(window)})`,\n ]\n for (const block of ledger.slice(0, 10)) {\n const tier = block.tier > 1 ? ` [T${block.tier}]` : ''\n lines.push(` - ${block.blockId.slice(0, 8)}${tier}: seqs ${block.start}..${block.end} — ${block.summary.slice(0, 80)}`)\n }\n return lines.join('\\n')\n}\n\nfunction compressText(env: ToolEnvironment, agent: Agent, args: string[]): string {\n if (args.length < 3) {\n return '/acp compress <startSeq> <endSeq> <summary...>'\n }\n const startSeq = Number(args[0])\n const endSeq = Number(args[1])\n const summary = args.slice(2).join(' ')\n if (!Number.isInteger(startSeq) || !Number.isInteger(endSeq)) {\n return '/acp compress: startSeq and endSeq must be integers'\n }\n const session = agent.session\n const { start, end } = resolveSurfaceRange(session, startSeq, endSeq)\n // A checkpoint summary node can only be distilled through the kernel (the\n // compress tool); /acp compress is a plain T1 range transaction, so refuse\n // rather than silently folding the summary as a message.\n if (blockRefForSummarySeq(session, start) !== null || blockRefForSummarySeq(session, end) !== null) {\n return '/acp compress: the range touches a compressed block summary node — distill it with the compress tool (seq-based batch), not /acp compress'\n }\n const shadowed = shadowedSeqsOf(session, startSeq, endSeq)\n let shadowedTokens = 0\n for (const seq of shadowed) {\n const event = session.events[seq]\n if (event !== undefined) shadowedTokens += defaultCountTokens(extractEventText(event))\n }\n const { compactionId } = runCompactionTransaction(session, {\n start,\n end,\n shadowedSeqs: shadowed,\n summary: [{ type: 'text', text: summary }],\n shadowedTokenCount: shadowedTokens,\n provider: agent.options.provider ?? '',\n model: agent.options.model ?? '',\n })\n return `Compressed seqs ${start}..${end} (${shadowed.length} messages) as block ${compactionId.slice(0, 8)}`\n}\n\nfunction decompressText(_env: ToolEnvironment, agent: Agent, args: string[]): string {\n if (args.length < 1) return '/acp decompress <blockId>'\n const session = agent.session\n const ledger = rebuildBlockLedger(session.events)\n const block = ledger.find((entry) => entry.blockId.startsWith(args[0]!))\n if (block === undefined) return `block \"${args[0]}\" not found (see /acp status)`\n // Tier-2/3 blocks shadow parent checkpoint nodes: expand to the originals.\n const parts = expandShadowedSeqs(session, block.blockId)\n .map((seq) => extractEventText(session.events[seq]!))\n .filter((text) => text.length > 0)\n return `Block ${block.blockId} — ${block.summary}\\n\\n${parts.join('\\n\\n') || '(no recoverable content)'}`\n}\n\n/** Register the /acp command (idempotent per engine). */\nexport function acpCommand(env: ToolEnvironment): CommandDefinition {\n return {\n name: 'acp',\n description:\n 'Active Context Pruning — model-driven context compression. '\n + 'Usage: /acp status | /acp compress <startSeq> <endSeq> <summary> | /acp decompress <blockId>',\n handler: async (invocation) => {\n const raw = invocation.rawInput.trim()\n if (raw === '' || raw === 'status') {\n return { kind: 'success', text: await statusText(env, invocation.agent) }\n }\n if (raw.startsWith('compress')) {\n return { kind: 'success', text: compressText(env, invocation.agent, raw.slice('compress'.length).trim().split(/\\s+/) ) }\n }\n if (raw.startsWith('decompress')) {\n return { kind: 'success', text: decompressText(env, invocation.agent, raw.slice('decompress'.length).trim().split(/\\s+/)) }\n }\n return { kind: 'error', text: `unknown /acp subcommand \"${raw.split(/\\s+/)[0]}\" — use status | compress | decompress` }\n },\n }\n}\n","/**\n * M4 — ACP nudge: the kernel's compression recommendation, rendered as an\n * injected user message with a seq-based compressible-range table (D1:\n * \"seq is the ref\" — DSH has no in-memory message rewrite hook, so the model\n * targets ranges by surface seq rather than by <acp> tags).\n * @module billion-context-dsh/nudge\n */\n\nimport {\n defaultCountTokens,\n type CompressionCore,\n type CoreMessage,\n type NudgeDecision,\n} from 'acp-kernel'\nimport { createUserMessage, type UserMessage } from '@deepseek-ai/dsh-llm'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport { AcpStateStore } from './state.ts'\nimport { allLogMessages, eventsToCoreMessages, surfaceEventsOf } from './messages.ts'\nimport { buildCompressibleSeqRanges, findOpenTurn, summarySeqOfKernelBlock, surfaceSummary } from './region.ts'\nimport { kernelConfigFor, type KernelConfigInput } from './config.ts'\n\n/** Kernel inputs the nudge path shares with the compress tool. */\nexport interface NudgeEnvironment extends KernelConfigInput {\n readonly kernel: CompressionCore\n readonly store: AcpStateStore\n}\n\nexport interface NudgeOutcome {\n readonly message: UserMessage\n readonly emergency: boolean\n}\n\n/**\n * Render the compressible-range table as seq refs for the model.\n * Computed directly from the surface (not the kernel's ref map, which can\n * drift and hide large tool results) — see buildCompressibleSeqRanges.\n */\nexport function rangeTable(session: import('@deepseek-ai/dsh-session').Session): string {\n const ranges = buildCompressibleSeqRanges(session).slice(0, 6)\n if (ranges.length === 0) return ''\n const lines = ranges.map((range) => ` - seq ${range.start}..${range.end} — ${range.count} messages, ~${range.tokens} tokens`)\n return [\n '',\n `Surface: ${surfaceSummary(session)}`,\n 'Compressible ranges (suggestions only — compress any consumed span; refs are surface seqs):',\n ...lines,\n 'Compress with: compress({ content: [{ startSeq, endSeq, summary }] }) — content is an array: batch multiple unrelated segments in one call, each entry its own block. Keep ranges disjoint.',\n 'Snapshot taken at nudge time: the seqs go stale once the surface moves (a later compress shadows them), so re-run acp_status for fresh refs before compressing.',\n ].join('\\n')\n}\n\n/**\n * The token count driving pressure decisions. Prefer the host token meter's\n * SURFACE tokens (the input-side heuristic total — matches acp_status and the\n * UI's message-token view). `totalTokens` is request-AND-RESPONSE pressure and\n * can far exceed the window in long sessions (observed 230% against a real\n * ~20% occupancy), so it is not used. Falls back to the fast heuristic when\n * the meter is absent (tests, minimal hosts).\n */\nfunction measuredTokenCount(agent: Agent, coreMessages: CoreMessage[]): number {\n const meter = agent.ctx?.get?.('tokenMeter') as\n | { measure?: (session: unknown) => { surfaceTokens?: number } }\n | undefined\n const surface = meter?.measure?.(agent.session)?.surfaceTokens\n if (typeof surface === 'number' && surface > 0) return surface\n return coreMessages.reduce((sum, message) => sum + defaultCountTokens(message.text ?? ''), 0)\n}\n\n/**\n * Decide and build one nudge message for the agent's next pre-step. Returns\n * null when the kernel recommends no nudge or one was already injected for the\n * current turn (emergency nudges always bypass the dedup). Also advances the\n * in-memory kernel state (ref assignment) so the compress tool can resolve\n * seq → mNNNNN refs.\n */\nexport function buildNudge(\n agent: Agent,\n env: NudgeEnvironment,\n lastNudgeTurn: Map<string, number>,\n): NudgeOutcome | null {\n const session = agent.session\n const state = env.store.stateFor(session)\n // Full log for the kernel (so block anchors survive — see handleCompress);\n // the measured token count stays a SURFACE measurement.\n const coreMessages = allLogMessages(session)\n const surfaceMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const tokenCount = measuredTokenCount(agent, surfaceMessages)\n const config = kernelConfigFor(env)\n const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount })\n env.store.set(session, turn.state)\n\n const nudge = turn.nudge\n if (nudge === undefined || !nudge.shouldInject) return null\n const emergency = nudge.breakdown?.emergencyOverride === 1\n\n const turnNumber = findOpenTurn(session.events) ?? 0\n const alreadyShown = !emergency && lastNudgeTurn.get(session.id) === turnNumber\n if (alreadyShown) return null\n lastNudgeTurn.set(session.id, turnNumber)\n\n const text = buildNudgeText(nudge, emergency, session)\n const message = createUserMessage({\n content: [{ type: 'text', text }],\n source: { kind: 'plugin', plugin: 'acp-nudge' },\n })\n return { message, emergency }\n}\n\n/**\n * A short ADVISORY nudge — ACP is model-driven, the model decides whether and\n * when to compress. Full guidance (tools, philosophy, summary rules) lives in\n * the system prompt once, not in every nudge.\n */\nexport function buildNudgeText(\n nudge: NudgeDecision,\n emergency: boolean,\n session: import('@deepseek-ai/dsh-session').Session,\n): string {\n // Cap the reported percentage at 100: a broken measurement (e.g. response\n // pressure folded in) must never surface as an absurd \"230%\" to the model.\n const pct = Math.round(Math.min(nudge.contextUsage, 1) * 100)\n const frame = emergency\n ? `⚠️ Context usage is at ${pct}% of the window — nearly full. Consider compressing consumed ranges soon so working context stays available; the choice and timing are yours.`\n : `Context usage is at ${pct}%. This is a suggestion, not a requirement — you decide whether and when to compress.`\n const guidance = 'Compress by need, not by percentage: replace only ranges you have genuinely consumed, with dense self-contained summaries.'\n const parts = [frame, '', guidance]\n if ((nudge.tier === 2 || nudge.tier === 3) && (nudge.tierTargetBlocks?.length ?? 0) > 0) {\n const targets = nudge.tierTargetBlocks!\n const summarySeqs = targets\n .map((block) => summarySeqOfKernelBlock(session, block.blockId))\n .filter((seq): seq is number => seq !== null)\n const pending = nudge.tier === 2 ? nudge.breakdown?.pendingT2 : nudge.breakdown?.pendingT3\n const tokens = typeof pending === 'number' ? pending : 0\n parts.push(\n `Tier ${nudge.tier}: ${targets.length} tier-${nudge.tier - 1} block(s) distillable (${tokens} tokens) — compress their summary node(s) [seqs ${summarySeqs.join(', ')}] to reclaim the original messages.`,\n )\n }\n parts.push(rangeTable(session))\n return parts.join('\\n')\n}\n","/**\n * M4 — the ACP system-prompt section (DSH counterpart of billion-context-pi's\n * ACP_SYSTEM_PROMPT): the load-bearing compression guidance lives here, ONCE,\n * instead of being re-sent with every nudge. The nudge itself stays a short,\n * advisory notice — ACP is model-driven, the model decides whether and when\n * to compress (never \"compress now\").\n * @module billion-context-dsh/system-prompt\n */\n\nimport { COMPRESS_PHILOSOPHY } from 'acp-kernel'\n\nexport const ACP_SYSTEM_PROMPT = `Active Context Pruning — model-driven context management\n\nYOU decide whether and when to compress context. Nothing forces you: the injected \"nudge\" is a suggestion, not an order, and you may ignore it when compression would not help. Compress only ranges you have genuinely consumed (read tool outputs, finished explorations, superseded steps) that the current work no longer needs verbatim.\n\n${COMPRESS_PHILOSOPHY}\n\nCompression tools (refs are SURFACE SEQS, not ids):\n- 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 — 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. Seq refs must be on the current surface: seqs from older nudges or earlier compresses go stale as the surface moves, so a stale span is auto-remapped to its still-live remainder (the result reports the adjusted span), a fully compressed span is reported as already compressed, and invented/other-session seqs fail with guidance.\n- decompress: recover a compressed block's original content, read-only. decompress({ blockId }).\n- search_context: find information inside compressed blocks BEFORE decompressing. search_context({ query }).\n- acp_status: current context usage and the live compressible-range list. Run it right before compressing — the only seqs that never go stale are the ones you just read.\n\nTiered 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 — decompress on the tier-2 block recovers the full originals.\n\nWhen 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 — the surface moves as messages land and compress; verify with acp_status.`\n\n/** System-prompt section order: tool guidance lives in 100–199. */\nexport const ACP_SYSTEM_PROMPT_ORDER = 150\n"],"mappings":"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","names":["defaultCountTokens","defaultCountTokens","defaultCountTokens","defaultCountTokens","defaultCountTokens","createUserMessage","defaultCountTokens","createUserMessage"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/state.ts","../src/region.ts","../src/messages.ts","../src/tools.ts","../src/config.ts","../src/nudge.ts","../src/window.ts","../src/commands.ts","../src/system-prompt.ts"],"sourcesContent":["/**\n * billion-context-dsh — Active Context Pruning (ACP) for the DeepSeek Harness,\n * delivered as a `CompactionEngine` backend.\n *\n * The model decides when and what to compress (pure ACP semantics):\n * - the `compress` tool durably shadows a surface range with the model-written\n * summary (no second LLM summarization call — the ACP cost win);\n * - the original events stay in the append-only session log, so `decompress`,\n * `search_context`, and replay always work;\n * - refs are surface seqs carried by the injected nudge's range table (DSH\n * has no in-memory message rewrite hook — see docs/dsh-porting-verification.md);\n * - automatic policy never summarizes by itself: it nudges the model.\n *\n * Mount it wherever a compaction backend is expected:\n *\n * ```yaml\n * - id: compaction-billion-context\n * name: 'billion-context-dsh'\n * config:\n * modelContextLimit: 128000\n * ```\n *\n * The package registers `ctx.compaction` plus the four model tools and the\n * `/acp` command when the hosting composition provides `ctx.tools` /\n * `ctx.commands`.\n * @module billion-context-dsh\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport {\n CompactionEngine,\n ManualCompactionError,\n type CompactionAgentContext,\n type CompactionResult,\n type CompactionTrigger,\n type ManualCompactAgentContext,\n} from '@deepseek-ai/dsh-compaction'\nimport { createCore, type CompressionCore } from 'acp-kernel'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport { AcpStateStore } from './state.ts'\nimport { makeTools, type ToolEnvironment } from './tools.ts'\nimport { acpCommand } from './commands.ts'\nimport { buildNudge } from './nudge.ts'\nimport { ACP_SYSTEM_PROMPT, ACP_SYSTEM_PROMPT_ORDER } from './system-prompt.ts'\nimport { DEFAULT_CONTEXT_WINDOW, detectContextWindow, type AcpWindow } from './window.ts'\n\nexport { AcpStateStore } from './state.ts'\nexport { kernelConfigFor, type KernelConfigInput } from './config.ts'\nexport { ACP_SYSTEM_PROMPT, ACP_SYSTEM_PROMPT_ORDER } from './system-prompt.ts'\nexport { makeTools, type ToolEnvironment } from './tools.ts'\nexport { acpCommand } from './commands.ts'\nexport { buildNudge, resolveTokenCount, type NudgeEnvironment, type NudgeOutcome } from './nudge.ts'\nexport {\n DEFAULT_CONTEXT_WINDOW,\n detectContextWindow,\n windowSourceLabel,\n type AcpWindow,\n} from './window.ts'\nexport {\n AlreadyCompressedRangeError,\n rebuildBlockLedger,\n resolveSurfaceRange,\n runCompactionTransaction,\n shadowedSeqsOf,\n findOpenTurn,\n assertNoActiveCompaction,\n blockRegistry,\n blockRefForSummarySeq,\n compactionIdsOfKernelBlocks,\n summarySeqOfKernelBlock,\n expandShadowedSeqs,\n type AcpBlockLedgerEntry,\n type CompactionTransactionInput,\n type ResolvedSurfaceRange,\n} from './region.ts'\nexport { eventsToCoreMessages, projectEvent, surfaceEventsOf, extractEventText } from './messages.ts'\n\nexport interface AcpConfig {\n /**\n * The context window used for pressure decisions, in tokens. When omitted,\n * `autoModelContextLimit` (default true) probes the model's real window via\n * `agent.ctx.llm.resolveModelInfo(provider, model)`; an explicit value\n * always wins and disables the probe.\n */\n readonly modelContextLimit?: number\n /** Probe the model's real context window from the LLM runtime. Default true. */\n readonly autoModelContextLimit: boolean\n /** Nudge window lower bound (usage fraction; validation only — the growth-driven trigger has no percentage floor). Kernel default 0.45 — same as billion-context-pi. */\n readonly nudgeMinContextLimitPct?: number\n /**\n * Nudge window upper bound — over-limit guarantee line: above this the\n * kernel injects a nudge regardless of growth or cadence. Engine default\n * 0.70 (deliberately BELOW the kernel/billion-context-pi default 0.75 and\n * the host compaction-basic auto-compaction line 0.80, so the forced nudge\n * always fires first); an explicit value wins.\n */\n readonly nudgeMaxContextLimitPct?: number\n /**\n * Emergency nudge threshold (bypasses the per-turn dedup). Engine default\n * 0.85 (down from the kernel/billion-context-pi default 0.95: 95% leaves\n * the model no room to act before the API rejects, and the host's 80%\n * compaction-basic line shadows it in standard/code/cordis modes).\n */\n readonly nudgeEmergencyThresholdPct?: number\n /** Any other acp-kernel Config override (billion-context-pi's `coreOverrides` escape hatch). */\n readonly coreOverrides?: Partial<import('acp-kernel').Config>\n /**\n * Custom token-count function for the kernel's internal estimation.\n * Defaults to the kernel's `defaultCountTokens` (CJK: 1 char = 1 token,\n * other: 4 chars = 1 token — aligns with billion-context-pi).\n * Can be overridden for provider-specific tokenization, e.g. DeepSeek's\n * official coefficient: 1 CJK char ≈ 0.6 tokens, 1 other char ≈ 0.3 tokens.\n * Only affects the kernel's internal estimation (compressible range sizing,\n * nudge text, growth branch pending); the `projectedTokens` reading from\n * `sessionProjections` (used for nudge pressure decisions and acp_status)\n * is provider-anchored and unaffected by this function.\n */\n readonly countTokens?: (text: string) => number\n /** Register the four model tools on `ctx.tools`. Default true. */\n readonly autoTools: boolean\n /** Register the `/acp` command on `ctx.commands`. Default true. */\n readonly autoCommand: boolean\n /** Inject the nudge into `agent/pre-step` when the kernel recommends it. Default true. */\n readonly autoNudge: boolean\n}\n\nconst DEFAULT_CONFIG: AcpConfig = {\n autoModelContextLimit: true,\n autoTools: true,\n autoCommand: true,\n autoNudge: true,\n // Nudge thresholds: engine defaults 0.70/0.85 — deliberately below the\n // kernel/billion-context-pi 0.75/0.95. 0.95 leaves no room to act before\n // the API rejects, and the host's compaction-basic line (thresholdRatio\n // 0.80) shadows it in standard/code/cordis modes; 0.70 keeps the forced\n // over-limit nudge ahead of that 80% line. Explicit values always win.\n nudgeMaxContextLimitPct: 0.7,\n nudgeEmergencyThresholdPct: 0.85,\n}\n\nexport function resolveAcpConfig(config: Partial<AcpConfig> = {}): AcpConfig {\n return { ...DEFAULT_CONFIG, ...config }\n}\n\n/**\n * The ACP compaction backend. Subclasses the seam exactly like\n * `dsh-compaction-basic`; swaps summarization-driven compaction for\n * model-driven block compression without touching the agent loop.\n */\nexport class AcpCompactionEngine extends CompactionEngine {\n /** The framework-agnostic ACP compression core, reused verbatim. */\n readonly kernel: CompressionCore\n /** Per-session kernel state. */\n readonly store: AcpStateStore\n /** Resolved engine configuration. */\n readonly config: AcpConfig\n\n private readonly lastNudgeTurn = new Map<string, number>()\n /** Per provider/model route the resolved window (probe failures cached too). */\n private readonly windowCache = new Map<string, AcpWindow>()\n\n constructor(ctx: Context, config: Partial<AcpConfig> = {}) {\n super(ctx)\n this.config = resolveAcpConfig(config)\n const ports = this.config.countTokens !== undefined ? { countTokens: this.config.countTokens } : {}\n this.kernel = createCore(ports)\n this.store = new AcpStateStore()\n\n const env: ToolEnvironment = {\n kernel: this.kernel,\n store: this.store,\n // Initial value before any probe; windowFor() replaces it per pre-step.\n modelContextLimit: this.config.modelContextLimit ?? DEFAULT_CONTEXT_WINDOW,\n nudgeMinContextLimitPct: this.config.nudgeMinContextLimitPct,\n nudgeMaxContextLimitPct: this.config.nudgeMaxContextLimitPct,\n nudgeEmergencyThresholdPct: this.config.nudgeEmergencyThresholdPct,\n coreOverrides: this.config.coreOverrides,\n windowFor: (agent) => this.windowFor(agent),\n }\n\n // Tools and commands may not be registered yet on cold start: cordis\n // starts unrelated composition rows concurrently, so the first\n // `ctx.get('tools')` can legitimately be undefined even though the row\n // ships later in the file. HMR-style reloads always see them (already\n // present), but a fresh process races — the tools silently vanished on\n // restart. Register eagerly, then re-attempt when the service appears\n // (`internal/service`) or the app finishes booting (`ready`); guard so a\n // late callback never double-registers.\n const tools = ctx.get('tools')\n if (tools !== undefined) {\n for (const tool of makeTools(env)) tools.register(tool)\n } else {\n let done = false\n const registerTools = (): void => {\n if (done) return\n const registry = ctx.get('tools')\n if (registry === undefined) return\n done = true\n for (const tool of makeTools(env)) registry.register(tool)\n }\n ctx.on('internal/service', (name: unknown) => {\n if (name === 'tools') registerTools()\n })\n }\n const commands = ctx.get('commands')\n if (commands !== undefined) {\n commands.register(acpCommand(env))\n } else {\n let done = false\n const registerCommand = (): void => {\n if (done) return\n const registry = ctx.get('commands')\n if (registry === undefined) return\n done = true\n registry.register(acpCommand(env))\n }\n ctx.on('internal/service', (name: unknown) => {\n if (name === 'commands') registerCommand()\n })\n }\n if (this.config.autoNudge) {\n ctx.on('agent/pre-step', async (payload, next) => {\n const decision = await next()\n if (decision.kind === 'reject') return decision\n const window = await this.windowFor(payload.agent)\n const outcome = buildNudge(payload.agent, { ...env, modelContextLimit: window.limit }, this.lastNudgeTurn)\n if (outcome === null) return decision\n return { kind: 'enter', messages: [...decision.messages, outcome.message] }\n })\n }\n // The load-bearing ACP guidance lives in the system prompt ONCE; nudges\n // stay short and advisory (model-driven: the model decides). The\n // systemPrompt service may not be registered yet on cold start (cordis\n // starts unrelated composition rows concurrently), so apply the same\n // retry pattern as tools and commands: eager registration, then\n // re-attempt when the service appears via `internal/service`; guard so a\n // late callback never double-registers.\n const systemPrompt = ctx.get('systemPrompt')\n if (systemPrompt !== undefined) {\n systemPrompt.section({\n name: 'billion-context-dsh',\n order: ACP_SYSTEM_PROMPT_ORDER,\n text: ACP_SYSTEM_PROMPT,\n })\n } else {\n let done = false\n const registerSystemPrompt = (): void => {\n if (done) return\n const registry = ctx.get('systemPrompt')\n if (registry === undefined) return\n done = true\n registry.section({\n name: 'billion-context-dsh',\n order: ACP_SYSTEM_PROMPT_ORDER,\n text: ACP_SYSTEM_PROMPT,\n })\n }\n ctx.on('internal/service', (name: unknown) => {\n if (name === 'systemPrompt') registerSystemPrompt()\n })\n }\n }\n\n /**\n * Resolve the effective context window for an agent. An explicitly\n * configured `modelContextLimit` always wins (no probe). Otherwise probe the\n * model's real window via `agent.ctx.llm.resolveModelInfo` (cached per\n * provider/model route, probe failures cached too) and fall back to\n * DEFAULT_CONTEXT_WINDOW when auto-detection is disabled or unavailable.\n */\n async windowFor(agent: Agent): Promise<AcpWindow> {\n if (this.config.modelContextLimit !== undefined) {\n return { limit: this.config.modelContextLimit, source: 'explicit' }\n }\n const provider = agent.options.provider ?? ''\n const model = agent.options.model ?? ''\n const key = `${provider}\\0${model}`\n const cached = this.windowCache.get(key)\n if (cached !== undefined) return cached\n let window: AcpWindow\n if (!this.config.autoModelContextLimit) {\n window = { limit: DEFAULT_CONTEXT_WINDOW, source: 'default', provider, model }\n } else {\n const detected = await detectContextWindow(agent, provider, model)\n window = detected === null\n ? { limit: DEFAULT_CONTEXT_WINDOW, source: 'default', provider, model }\n : { limit: detected, source: 'auto', provider, model }\n }\n this.windowCache.set(key, window)\n return window\n }\n\n /** ACP is model-driven: automatic pressure policy never summarizes by itself. */\n override async compactIfNeeded(\n _agent: CompactionAgentContext,\n _trigger: CompactionTrigger,\n signal: AbortSignal,\n ): Promise<CompactionResult | null> {\n signal.throwIfAborted()\n return null\n }\n\n /** Explicit idle-session compaction: ACP leaves the decision to the model. */\n override async compactNow(\n _agent: ManualCompactAgentContext,\n signal: AbortSignal,\n ): Promise<CompactionResult | null> {\n signal.throwIfAborted()\n return null\n }\n\n /**\n * The model-driven path lands through the `compress` tool, which runs the\n * full durable transaction directly. This seam method rejects with guidance:\n * automatic summarization is exactly what ACP replaces.\n */\n override async compactRegion(\n _start: number,\n _end: number,\n _agent: CompactionAgentContext,\n signal?: AbortSignal,\n ): Promise<CompactionResult> {\n signal?.throwIfAborted()\n throw new ManualCompactionError(\n 'summary',\n 'billion-context-dsh is model-driven: use the compress tool instead of automatic summarization',\n )\n }\n}\n\nexport default AcpCompactionEngine\n","/**\n * M2 — per-session ACP kernel state.\n *\n * The in-memory map holds the exact acp-kernel `CompressionState` while a\n * session is live. Durability does not rely on a sidecar file: every durable\n * compression writes a `compaction/summary` event whose shadowed range and\n * summary re-derive the block ledger (`rebuildBlockLedger` in region.ts), so a\n * restarted engine can answer decompress/search/status from the session log\n * alone — DSH's \"log is the source of truth\" model.\n *\n * Tier-2/3 distillation additionally requires the kernel state to KNOW the\n * blocks: `syncBlocks` deactivates a block whose consumed messages are absent\n * from the message array, and `resolveBoundaries` refuses to anchor a block\n * ref it cannot find — so on first access for a session that already has\n * durable blocks (e.g. after a server restart), the kernel blocks are\n * REHYDRATED from the ledger before use. Live updates continue through `set`.\n * @module billion-context-dsh/state\n */\n\nimport type { Session, SessionEvent } from '@deepseek-ai/dsh-session'\nimport { createInitialState, type CompressionBlock, type CompressionState } from 'acp-kernel'\nimport { rebuildBlockLedger } from './region.ts'\n\n/** Rebuild kernel `CompressionBlock`s from the durable ledger (no kernel run needed). */\nfunction rebuildKernelBlocks(events: readonly SessionEvent[]): CompressionBlock[] {\n const ledger = rebuildBlockLedger(events)\n if (ledger.length === 0) return []\n // Durable compactionId → kernel block ref (bN), recorded or synthesised.\n const kernelIdOf = new Map<string, string>()\n const parentKernelIds = new Map<string, string[]>()\n let next = 1\n for (const entry of ledger) {\n let kernelBlockId: string\n if (entry.kernelBlockId !== undefined && /^b\\d+$/.test(entry.kernelBlockId)) {\n kernelBlockId = entry.kernelBlockId\n const num = Number(kernelBlockId.slice(1))\n if (Number.isInteger(num)) next = Math.max(next, num + 1)\n } else {\n kernelBlockId = `b${next}`\n next += 1\n }\n kernelIdOf.set(entry.blockId, kernelBlockId)\n parentKernelIds.set(\n entry.blockId,\n entry.parentBlockIds\n .map((parent) => kernelIdOf.get(parent))\n .filter((id): id is string => id !== undefined),\n )\n }\n const consumed = new Set<string>()\n for (const entry of ledger) {\n for (const parent of entry.parentBlockIds) consumed.add(parent)\n }\n const blocks: CompressionBlock[] = []\n for (const entry of ledger) {\n const blockId = kernelIdOf.get(entry.blockId)!\n // The kernel anchors a block by its effectiveMessageIds. Since the tier\n // feature, the transaction records the kernel block's raw coverage\n // (direct/effective message ids) verbatim, so rehydration is faithful —\n // a tier-2 block's coverage is its parents' ORIGINALS, not the checkpoint\n // node it shadows. Legacy blocks fall back to the shadowed seqs (tier 1)\n // or the checkpoint node (tier > 1; multi-tool-call assistant messages in\n // legacy blocks lose bare-seq coverage — a documented legacy limitation).\n const direct = entry.directMessageIds ?? [...entry.shadowedSeqs.map(String)]\n const effective = entry.effectiveMessageIds\n ?? (entry.tier > 1\n ? (entry.summarySeq === undefined ? [...entry.shadowedSeqs.map(String)] : [String(entry.summarySeq)])\n : [...entry.shadowedSeqs.map(String)])\n blocks.push({\n blockId,\n runId: `r${blocks.length + 1}`,\n tier: entry.tier,\n summary: entry.summary,\n directMessageIds: [...direct],\n effectiveMessageIds: [...effective],\n directBlockIds: parentKernelIds.get(entry.blockId) ?? [],\n compressedTokens: entry.shadowedTokenCount,\n createdAt: entry.createdAt,\n survivedCount: 0,\n generation: 'young',\n active: !consumed.has(entry.blockId),\n })\n }\n return blocks\n}\n\n/** The next kernel block id after the rehydrated blocks (or the initial 1). */\nfunction nextBlockIdAfter(events: readonly SessionEvent[]): number {\n const blocks = rebuildKernelBlocks(events)\n let max = 0\n for (const block of blocks) {\n const num = Number(block.blockId.slice(1))\n if (Number.isInteger(num)) max = Math.max(max, num)\n }\n return max + 1\n}\n\nexport class AcpStateStore {\n private readonly states = new Map<string, CompressionState>()\n\n /** Kernel state for one session, initialised on first access. */\n stateFor(session: Session): CompressionState {\n const id = session.id\n const existing = this.states.get(id)\n if (existing !== undefined) return existing\n const state = createInitialState()\n if (session.events.some((event) => event.type === 'compaction/summary')) {\n state.blocks = rebuildKernelBlocks(session.events)\n state.nextBlockId = nextBlockIdAfter(session.events)\n }\n this.states.set(id, state)\n return state\n }\n\n set(session: Session, state: CompressionState): void {\n this.states.set(session.id, state)\n }\n\n delete(session: Session): void {\n this.states.delete(session.id)\n }\n}\n","/**\n * M5 — durable region transaction and the log-rebuilt block ledger.\n *\n * Modeled on `dsh-compaction-basic/src/region.ts` (which is package-internal\n * and not exported by the seam): validate the surface range and tool-call/result\n * pairing, take the durable `compaction/start` lock, record `compaction/summary`\n * as the shadow price, land the `user/message` surface replacement carrying the\n * summary under `compactCheckpointSource`, and release the lock with\n * `compaction/end`. The original events stay in the append-only log, so\n * decompress/search/status can rebuild everything from the log.\n * @module billion-context-dsh/region\n */\n\nimport { randomUUID } from 'node:crypto'\nimport type { Session, SessionEvent, SessionEventMap } from '@deepseek-ai/dsh-session'\nimport {\n CompactionId,\n compactCheckpointSource,\n toolPairingBalancedAfter,\n toolPairingBalancedBefore,\n} from '@deepseek-ai/dsh-compaction'\nimport { createUserMessage, type ContentBlock } from '@deepseek-ai/dsh-llm'\nimport { defaultCountTokens } from 'acp-kernel'\nimport { extractEventText, extractText } from './messages.ts'\n\n/** One durable ACP block as rebuilt from the session log. */\nexport interface AcpBlockLedgerEntry {\n /** The compaction transaction id (stable block identity). */\n readonly blockId: string\n readonly summary: string\n readonly shadowedSeqs: readonly number[]\n readonly shadowedTokenCount: number\n readonly start: number\n readonly end: number\n /** Compression tier: 1 (message range), 2 (distills tier-1 blocks), 3 (distills tier-2 blocks). Legacy blocks default to 1. */\n readonly tier: 1 | 2 | 3\n /** Compaction ids of the blocks this block distilled (parents). Empty for tier-1 blocks. */\n readonly parentBlockIds: readonly string[]\n /** The acp-kernel block id (`bN`) created for this transaction — absent for legacy blocks (synthesised by order). */\n readonly kernelBlockId?: string\n /** The surface seq of this block's checkpoint summary node (derived from the log; null when the node is gone). */\n readonly summarySeq?: number\n /** The kernel block's raw direct/effective message ids at creation (recorded since the tier feature; absent for legacy). */\n readonly directMessageIds?: readonly string[]\n readonly effectiveMessageIds?: readonly string[]\n /** Unix epoch ms of the compaction/summary event. */\n readonly createdAt: number\n}\n\n/** The open turn number, or null when the log ends between turns. */\nexport function findOpenTurn(events: readonly SessionEvent[]): number | null {\n let open: number | null = null\n for (const event of events) {\n if (event.type === 'turn/start') open = event.data.turn\n else if (event.type === 'turn/end' && event.data.turn === open) open = null\n }\n return open\n}\n\n/** Reject a second concurrent compaction for the same session. */\nexport function assertNoActiveCompaction(events: readonly SessionEvent[]): void {\n let active = false\n for (const event of events) {\n if (event.type === 'compaction/start') active = true\n else if (event.type === 'compaction/end') active = false\n }\n if (active) {\n throw new Error('billion-context-dsh: another compaction is already active for this session')\n }\n}\n\n/**\n * Whether the surface node at `seq` projects to CoreMessage(s) whose ref key\n * is the bare seq — user messages, tool results, and text-only or SINGLE\n * tool-call assistant messages all do. Multi-tool-call assistant messages\n * project to `${seq}#${callId}` ids (projectEvent) and therefore carry NO\n * bare-`${seq}` ref, so compress's byRaw lookup can never resolve them as\n * range edges. resolveSurfaceRange treats such edges as unbalanced and shifts\n * them to the nearest clean cut.\n */\nfunction hasPlainRef(session: Session, seq: number): boolean {\n const event = session.events[seq]\n if (event === undefined) return false\n switch (event.type) {\n case 'user/message':\n case 'tool/result':\n return extractEventText(event).trim().length > 0\n case 'assistant/message': {\n const content = (event.data as { message?: { content?: unknown } }).message?.content\n const calls = Array.isArray(content)\n ? content.filter(\n (block) => block !== null && typeof block === 'object' && (block as { type?: string }).type === 'tool-call',\n )\n : []\n if (calls.length > 1) return false\n // One tool-call: projectEvent emits a bare-seq CoreMessage unconditionally.\n // Zero: only when the text is non-empty.\n return calls.length === 1 || extractEventText(event).trim().length > 0\n }\n default:\n return false\n }\n}\n\n/**\n * A requested range whose EVERY live message was already shadowed by one or\n * more blocks. The compress tool catches this and reports the range as already\n * compressed (with the covering block ids) instead of folding block summary\n * nodes as plain messages or erroring out. Distillation stays an explicit act:\n * target a LIVE checkpoint seq directly to distill (tier 2/3).\n */\nexport class AlreadyCompressedRangeError extends Error {\n constructor(\n readonly start: number,\n readonly end: number,\n readonly coveringBlockIds: readonly string[],\n ) {\n super(\n `billion-context-dsh: seq ${start}..${end} already compressed — `\n + 'no live content remains in that span',\n )\n this.name = 'AlreadyCompressedRangeError'\n }\n}\n\ntype StaleRangeRecovery =\n | { kind: 'ok'; start: number; end: number }\n | { kind: 'already-compressed'; coveringBlockIds: string[] }\n | { kind: 'unresolvable'; failedEdge: number }\n\n/**\n * Rebuild a requested range whose edges are no longer on the current surface.\n * The dominant cause is staleness: the seqs came from an older nudge table or\n * a previous compress result, and an earlier compression SHADOWED them (they\n * stay in the append-only log, but are gone from the surface). The recovery:\n *\n * 1. An edge that does not exist in the log at all (invented, or from another\n * session) is unresolvable — there is no way to guess what it meant.\n * 2. The still-LIVE surface nodes inside the requested span, in VALUE order\n * (the surface can be locally non-monotonic after replacements, so value\n * order is the only coherent span). If there are none, the whole span was\n * already compressed → 'already-compressed' with the covering block ids.\n * 3. Otherwise the range snaps to the first..last live PLAIN node in the\n * span. Block checkpoint nodes are deliberately excluded: distilling a\n * block on a STALE reference would silently change block structure the\n * model never intended to touch — distillation requires targeting a live\n * checkpoint seq directly.\n */\nfunction recoverStaleRange(session: Session, start: number, end: number): StaleRangeRecovery {\n if (session.events[start] === undefined || session.events[end] === undefined) {\n const failedEdge = session.events[start] === undefined ? start : end\n return { kind: 'unresolvable', failedEdge }\n }\n const liveInside = session.surface.nodes\n .filter((seq) => seq >= start && seq <= end)\n .sort((a, b) => a - b)\n const plain = liveInside.filter((seq) => !isCheckpointNode(session.events[seq]!))\n if (plain.length === 0) {\n const coveringBlockIds = rebuildBlockLedger(session.events)\n .filter((entry) => entry.shadowedSeqs.some((seq) => seq >= start && seq <= end))\n .map((entry) => entry.blockId)\n return { kind: 'already-compressed', coveringBlockIds }\n }\n return { kind: 'ok', start: plain[0]!, end: plain[plain.length - 1]! }\n}\n\nexport interface ResolvedSurfaceRange {\n readonly start: number\n readonly end: number\n /**\n * True when the requested edges were not on the current surface and were\n * remapped to the still-live content of the requested span (an earlier\n * compression shadowed them). Callers surface this so the model sees what\n * was actually compressed instead of silently shadowing a different span.\n */\n readonly recovered?: boolean\n}\n\n/**\n * Validate one inclusive surface span and adjust its edges to a\n * tool-pairing-balanced range whose boundaries carry a bare-seq ref. Reversed\n * ranges throw. An edge that sits inside a tool-call/result pair — or on a\n * multi-tool-call assistant message that has no bare-seq ref — is first nudged\n * inward to the nearest clean cut; if that collapses the range (e.g. the model\n * asked for a SINGLE tool result, which can never be balanced alone), the\n * range EXPANDS outward to the enclosing clean pair instead — a lone tool\n * message is almost always a \"consumed output\" the model genuinely wants to\n * compress. The returned range is what a caller should actually shadow.\n *\n * Missing edges are NOT an immediate error: the seqs were probably shadowed by\n * an earlier compression (stale nudge table / old compress result). The span\n * is rebuilt from its still-live remainder via recoverStaleRange — a fully\n * shadowed span throws AlreadyCompressedRangeError, a genuinely unknown edge\n * throws the not-in-surface guidance error. The returned range is what a\n * caller should actually shadow.\n */\nexport function resolveSurfaceRange(\n session: Session,\n start: number,\n end: number,\n): ResolvedSurfaceRange {\n const nodes = session.surface.nodes\n if (start > end) {\n throw new Error(`billion-context-dsh: reversed range ${start}..${end}`)\n }\n let requestedStartIdx = nodes.indexOf(start)\n let requestedEndIdx = nodes.indexOf(end)\n let recovered = false\n if (requestedStartIdx < 0 || requestedEndIdx < 0) {\n const stale = recoverStaleRange(session, start, end)\n if (stale.kind === 'unresolvable') {\n throw new Error(\n `billion-context-dsh: seq ${start}..${end} not in the current surface — `\n + `edge seq ${stale.failedEdge} is not in this session's log. `\n + 'Surface seqs are sparse message nodes (only user/message, assistant/message, '\n + 'tool/result events); consult acp_status for the current surface range',\n )\n }\n if (stale.kind === 'already-compressed') {\n throw new AlreadyCompressedRangeError(start, end, stale.coveringBlockIds)\n }\n start = stale.start\n end = stale.end\n recovered = true\n requestedStartIdx = nodes.indexOf(start)\n requestedEndIdx = nodes.indexOf(end)\n if (requestedStartIdx < 0 || requestedEndIdx < 0) {\n // Unreachable in practice (recovery returns live nodes), but never let\n // a negative index reach the balancing passes.\n throw new Error(\n `billion-context-dsh: seq ${start}..${end} not in the current surface — `\n + 'consult acp_status for the current surface range',\n )\n }\n }\n if (requestedStartIdx > requestedEndIdx) {\n throw new Error(`billion-context-dsh: reversed range ${start}..${end}`)\n }\n // Belt-and-braces: the surface can be locally out of order after surface\n // replacements, so index order alone does not guarantee value order.\n if (start > end) {\n throw new Error(`billion-context-dsh: reversed range ${start}..${end}`)\n }\n // A boundary must be BOTH tool-pairing-balanced AND carry a bare-seq ref.\n const cleanBefore = (index: number): boolean =>\n toolPairingBalancedBefore(session, nodes[index]!) && hasPlainRef(session, nodes[index]!)\n const cleanAfter = (index: number): boolean =>\n toolPairingBalancedAfter(session, nodes[index]!) && hasPlainRef(session, nodes[index]!)\n let startIdx = requestedStartIdx\n let endIdx = requestedEndIdx\n // First pass: nudge inward to the nearest clean cuts.\n while (startIdx <= endIdx && !cleanBefore(startIdx)) {\n startIdx += 1\n }\n while (endIdx >= startIdx && !cleanAfter(endIdx)) {\n endIdx -= 1\n }\n if (startIdx <= endIdx && nodes[startIdx]! <= nodes[endIdx]!) {\n return recovered\n ? { start: nodes[startIdx]!, end: nodes[endIdx]!, recovered: true }\n : { start: nodes[startIdx]!, end: nodes[endIdx]! }\n }\n // A recovered span NEVER expands across block checkpoints: the model's\n // requested edges were stale, so growing the span into block territory could\n // fold content it never intended to touch. If the live remainder cannot be\n // balanced by shrinking alone, give up with guidance instead.\n if (recovered) {\n throw new Error(\n `billion-context-dsh: no tool-pairing-balanced live remainder around seq ${start}..${end} — `\n + 'narrow the range or consult acp_status for the current surface',\n )\n }\n // Second pass: the inward pass collapsed (a lone tool message) — expand\n // outward from the REQUESTED span to the smallest clean enclosing pair.\n startIdx = requestedStartIdx\n endIdx = requestedEndIdx\n while (startIdx > 0 && !cleanBefore(startIdx)) {\n startIdx -= 1\n }\n while (endIdx < nodes.length - 1 && !cleanAfter(endIdx)) {\n endIdx += 1\n }\n // Value order guard: the surface is locally non-monotonic after replacements\n // (a checkpoint seq inserted ahead of older residual nodes), so index order\n // alone is not enough — never return a span whose end seq is numerically\n // BEFORE its start seq. The caller (nudge / compress) skips such a span.\n if (cleanBefore(startIdx) && cleanAfter(endIdx) && nodes[startIdx]! <= nodes[endIdx]!) {\n return { start: nodes[startIdx]!, end: nodes[endIdx]! }\n }\n throw new Error(\n `billion-context-dsh: no tool-pairing-balanced range around seq ${start}..${end} — `\n + 'narrow the range or consult acp_status for the current surface',\n )\n}\n\n/** The surface seqs shadowed by the inclusive positional span. */\nexport function shadowedSeqsOf(session: Session, start: number, end: number): number[] {\n const nodes = session.surface.nodes\n const startIdx = nodes.indexOf(start)\n const endIdx = nodes.indexOf(end)\n return nodes.slice(startIdx, endIdx + 1)\n}\n\nexport interface CompactionTransactionInput {\n readonly start: number\n readonly end: number\n readonly shadowedSeqs: readonly number[]\n readonly summary: ContentBlock[]\n readonly shadowedTokenCount: number\n readonly provider: string\n readonly model: string\n /** Compression tier of this block (default 1). */\n readonly tier?: 1 | 2 | 3\n /** The acp-kernel block id (`bN`) created by the kernel for this transaction. */\n readonly kernelBlockId?: string\n /** Compaction ids of the blocks distilled into this one. */\n readonly parentBlockIds?: readonly string[]\n /** The kernel block's direct/effective message ids (raw CoreMessage ids) — recorded for faithful rehydration. */\n readonly directMessageIds?: readonly string[]\n readonly effectiveMessageIds?: readonly string[]\n}\n\n/**\n * ACP tier extension fields carried on `compaction/summary` events. The\n * upstream dsh-compaction event type does not know them, so reads and writes\n * go through this precise intersection (never `any`).\n */\nexport interface AcpCompactionSummaryFields {\n /** Compression tier (1/2/3) — 1 = message range, 2 = distills tier-1, 3 = distills tier-2. */\n readonly tier?: 1 | 2 | 3\n /** The acp-kernel block id (`bN`) created for this transaction. */\n readonly kernelBlockId?: string\n /** Durable compaction ids of the blocks distilled into this one. */\n readonly parentBlockIds?: readonly string[]\n /**\n * The kernel block's direct message ids (raw CoreMessage ids) at creation —\n * recorded so a restarted engine rehydrates the SAME coverage (a tier-2\n * block's coverage is its parents' originals, not the checkpoint node).\n */\n readonly directMessageIds?: readonly string[]\n /** The kernel block's effective message ids (raw CoreMessage ids) at creation. */\n readonly effectiveMessageIds?: readonly string[]\n}\n\ntype CompactionSummaryData = SessionEventMap['compaction/summary']\n\n/** Read a `compaction/summary` event's data including the ACP tier extension fields. */\nexport function readCompactionSummary(event: SessionEvent): CompactionSummaryData & AcpCompactionSummaryFields {\n return event.data as CompactionSummaryData & AcpCompactionSummaryFields\n}\n\n/**\n * Run one durable compression transaction. Throws on invalid state; on success\n * the four events are in the log and the surface has one summary node.\n */\nexport function runCompactionTransaction(\n session: Session,\n input: CompactionTransactionInput,\n): { compactionId: string; seqs: number[] } {\n assertNoActiveCompaction(session.events)\n const turn = findOpenTurn(session.events)\n const compactionId = CompactionId(randomUUID())\n const seqs: number[] = []\n\n seqs.push(session.append('compaction/start', { compactionId, turn }).seq)\n seqs.push(session.append('compaction/summary', {\n compactionId,\n summary: input.summary,\n shadowedRange: { start: input.start, end: input.end },\n shadowedSeqs: [...input.shadowedSeqs],\n shadowedTokenCount: input.shadowedTokenCount,\n provider: input.provider,\n model: input.model,\n tier: input.tier ?? 1,\n ...(input.kernelBlockId === undefined ? {} : { kernelBlockId: input.kernelBlockId }),\n ...(input.parentBlockIds === undefined || input.parentBlockIds.length === 0\n ? {}\n : { parentBlockIds: [...input.parentBlockIds] }),\n ...(input.directMessageIds === undefined ? {} : { directMessageIds: [...input.directMessageIds] }),\n ...(input.effectiveMessageIds === undefined ? {} : { effectiveMessageIds: [...input.effectiveMessageIds] }),\n } as CompactionSummaryData & AcpCompactionSummaryFields).seq)\n\n const message = createUserMessage({\n content: input.summary,\n source: compactCheckpointSource(compactionId),\n })\n seqs.push(session.append('user/message', message, {\n surfaceOp: { op: 'replace', start: input.start, end: input.end },\n sourceEventSeqs: [...input.shadowedSeqs],\n }).seq)\n\n seqs.push(session.append('compaction/end', { compactionId, turn }).seq)\n return { compactionId, seqs }\n}\n\n/** The seq of a compaction's checkpoint summary node in the log (visible or shadowed). */\nfunction summarySeqOfCompaction(events: readonly SessionEvent[], compactionId: string): number | null {\n for (const event of events) {\n if (event.type !== 'user/message') continue\n const source = (event.data as { source?: { plugin?: string; compactionId?: string } }).source\n if (source?.plugin === 'compact' && source.compactionId === compactionId) return event.seq\n }\n return null\n}\n\n/** Rebuild the block ledger from the durable log (no kernel state needed). */\nexport function rebuildBlockLedger(events: readonly SessionEvent[]): AcpBlockLedgerEntry[] {\n const ledger: AcpBlockLedgerEntry[] = []\n for (const event of events) {\n if (event.type !== 'compaction/summary') continue\n const data = readCompactionSummary(event)\n // Blocks written before the token-accounting fix carry shadowedTokenCount\n // 0; backfill from the shadowed originals still in the log so acp_status\n // reports real reclaimed tokens.\n let shadowedTokenCount = data.shadowedTokenCount\n if (shadowedTokenCount === 0) {\n shadowedTokenCount = 0\n for (const seq of data.shadowedSeqs) {\n const original = events[seq]\n if (original !== undefined) shadowedTokenCount += defaultCountTokens(extractEventText(original))\n }\n }\n const tier = data.tier === 2 || data.tier === 3 ? data.tier : 1\n const parentBlockIds: string[] = Array.isArray(data.parentBlockIds) ? [...data.parentBlockIds] : []\n const directMessageIds: string[] | undefined = Array.isArray(data.directMessageIds) ? [...data.directMessageIds] : undefined\n const effectiveMessageIds: string[] | undefined = Array.isArray(data.effectiveMessageIds) ? [...data.effectiveMessageIds] : undefined\n const summarySeq = summarySeqOfCompaction(events, data.compactionId)\n ledger.push({\n blockId: data.compactionId,\n summary: extractText(data.summary),\n shadowedSeqs: [...data.shadowedSeqs],\n shadowedTokenCount,\n start: data.shadowedRange.start,\n end: data.shadowedRange.end,\n tier,\n parentBlockIds,\n ...(typeof data.kernelBlockId === 'string' ? { kernelBlockId: data.kernelBlockId } : {}),\n ...(summarySeq === null ? {} : { summarySeq }),\n ...(directMessageIds === undefined ? {} : { directMessageIds }),\n ...(effectiveMessageIds === undefined ? {} : { effectiveMessageIds }),\n createdAt: event.time,\n })\n }\n return ledger\n}\n\n/** One self-computed compressible span of the current surface. */\nexport interface SeqCompressibleRange {\n readonly start: number\n readonly end: number\n readonly count: number\n readonly tokens: number\n}\n\n/** Whether a surface user message is a compaction checkpoint node (already compressed). */\nfunction isCheckpointNode(event: SessionEvent): boolean {\n if (event.type !== 'user/message') return false\n const source = (event.data as { source?: { plugin?: string } }).source\n return source?.plugin === 'compact'\n}\n\n/**\n * Compute compressible spans directly from the surface — independent of the\n * kernel's ref map, which can drift after surface replacements in long\n * sessions and hide large tool results from the nudge range table. Skips the\n * recent protected tail, the last user message, and compaction checkpoints;\n * edges are then balanced through resolveSurfaceRange. Ranges are ordered by\n * size (largest reclaimed first).\n */\nexport function buildCompressibleSeqRanges(\n session: Session,\n opts: { preserveRecent?: number } = {},\n): SeqCompressibleRange[] {\n const nodes = session.surface.nodes\n const preserve = opts.preserveRecent ?? 5\n const protectedSeqs = new Set<number>()\n for (const seq of nodes.slice(-preserve)) protectedSeqs.add(seq)\n for (let index = nodes.length - 1; index >= 0; index -= 1) {\n const event = session.events[nodes[index]!]\n if (event?.type === 'user/message' && !isCheckpointNode(event)) {\n protectedSeqs.add(nodes[index]!)\n break\n }\n }\n const raw: SeqCompressibleRange[] = []\n let cur: SeqCompressibleRange | null = null\n const flush = (): void => {\n if (cur !== null) raw.push(cur)\n cur = null\n }\n for (const seq of nodes) {\n const event = session.events[seq]\n if (event === undefined || protectedSeqs.has(seq) || isCheckpointNode(event)) {\n flush()\n continue\n }\n // Surface nodes can be locally out of order after surface replacements in\n // long sessions; a node with a SMALLER seq than the running segment would\n // produce a reversed range (e.g. 110295..106762). Break the segment so\n // ranges always stay start <= end.\n if (cur !== null && seq < cur.start) {\n flush()\n cur = null\n }\n const tokens = defaultCountTokens(extractEventText(event))\n if (cur === null) {\n cur = { start: seq, end: seq, count: 1, tokens }\n } else {\n cur = { start: cur.start, end: seq, count: cur.count + 1, tokens: cur.tokens + tokens }\n }\n }\n flush()\n const out: SeqCompressibleRange[] = []\n for (const range of raw) {\n try {\n const { start, end } = resolveSurfaceRange(session, range.start, range.end)\n const count = range.count\n out.push({ start, end, count, tokens: range.tokens })\n } catch {\n // Cannot be balanced into a compressible span — skip.\n }\n }\n return out.sort((a, b) => b.tokens - a.tokens)\n}\n\n/**\n * A compact human-readable description of the current surface for the model:\n * node count plus the first/last message seqs. Surface seqs are sparse (the\n * event log interleaves non-message events and expanded delta batches), so a\n * model that never saw the nudge range table — e.g. low-pressure sessions\n * where no nudge fires — cannot guess its own seq space. acp_status and the\n * nudge's range table both surface this so compress edges can be located\n * without blind probing.\n */\nexport function surfaceSummary(session: Session): string {\n const nodes = session.surface.nodes\n if (nodes.length === 0) return 'empty'\n const first = nodes[0]!\n const last = nodes[nodes.length - 1]!\n return `${nodes.length} nodes, seqs ${first}..${last}`\n}\n\n/** One block as seen by the tier machinery: durable id ↔ kernel ref (`bN`). */\nexport interface AcpBlockRegistryEntry {\n /** The durable compaction id. */\n readonly blockId: string\n /** The acp-kernel block ref (`bN`); synthesised by log order for legacy blocks. */\n readonly kernelBlockId: string\n readonly tier: 1 | 2 | 3\n /** The surface seq of this block's checkpoint summary node (null when gone). */\n readonly summarySeq: number | null\n /** True until a LATER block distills this one. Only active blocks are distillable. */\n readonly active: boolean\n readonly parentBlockIds: readonly string[]\n}\n\n/**\n * Rebuild the compactionId ↔ kernel-block-ref registry from the durable log.\n * Legacy blocks (pre-tier, no recorded `kernelBlockId`) are synthesised as\n * `b1`, `b2`, … in log order; recorded ids are kept as-is. A block is active\n * until a later block lists it as a parent.\n */\nexport function blockRegistry(session: Session): AcpBlockRegistryEntry[] {\n const ledger = rebuildBlockLedger(session.events)\n const kernelIdOf = new Map<string, string>()\n const raw: AcpBlockRegistryEntry[] = []\n let next = 1\n for (const entry of ledger) {\n let kernelBlockId: string\n if (entry.kernelBlockId !== undefined && /^b\\d+$/.test(entry.kernelBlockId)) {\n kernelBlockId = entry.kernelBlockId\n const num = Number(kernelBlockId.slice(1))\n if (Number.isInteger(num)) next = Math.max(next, num + 1)\n } else {\n kernelBlockId = `b${next}`\n next += 1\n }\n kernelIdOf.set(entry.blockId, kernelBlockId)\n raw.push({\n blockId: entry.blockId,\n kernelBlockId,\n tier: entry.tier,\n summarySeq: entry.summarySeq ?? null,\n active: true,\n parentBlockIds: [...entry.parentBlockIds],\n })\n }\n const consumed = new Set<string>()\n for (const entry of raw) {\n for (const parent of entry.parentBlockIds) consumed.add(parent)\n }\n return raw.map((entry) => ({\n ...entry,\n active: !consumed.has(entry.blockId),\n }))\n}\n\n/**\n * The kernel block ref (`bN`) for a surface seq, when that seq is the\n * checkpoint summary node of a block — the edge the model must use to\n * distill (T2/T3). Active blocks distill; a stale (already-distilled) node\n * still maps to its `bN` so the kernel reports \"already compressed\" instead\n * of silently folding the summary as a plain message. Returns null for\n * anything else (plain messages, non-checkpoint nodes).\n */\nexport function blockRefForSummarySeq(session: Session, seq: number): string | null {\n const event = session.events[seq]\n if (event?.type !== 'user/message') return null\n const source = (event.data as { source?: { plugin?: string; compactionId?: string } }).source\n if (source?.plugin !== 'compact' || source.compactionId === undefined) return null\n const entry = blockRegistry(session).find((r) => r.blockId === source.compactionId)\n if (entry === undefined) return null\n return entry.kernelBlockId\n}\n\n/** The durable compaction ids distilled by the given kernel block refs (`bN`). */\nexport function compactionIdsOfKernelBlocks(session: Session, kernelBlockIds: readonly string[]): string[] {\n if (kernelBlockIds.length === 0) return []\n const byKernel = new Map(blockRegistry(session).map((r) => [r.kernelBlockId, r.blockId]))\n return kernelBlockIds\n .map((id) => byKernel.get(id))\n .filter((id): id is string => id !== undefined)\n}\n\n/** The checkpoint summary seq of an ACTIVE kernel block (`bN`), or null. */\nexport function summarySeqOfKernelBlock(session: Session, kernelBlockId: string): number | null {\n const entry = blockRegistry(session).find((r) => r.kernelBlockId === kernelBlockId)\n return entry?.active ? entry.summarySeq : null\n}\n\n/** The durable block whose checkpoint node sits at `seq` (or null). */\nfunction checkpointBlockIdOf(events: readonly SessionEvent[], seq: number): string | null {\n const event = events[seq]\n if (event?.type !== 'user/message') return null\n const source = (event.data as { source?: { plugin?: string; compactionId?: string } }).source\n if (source?.plugin !== 'compact' || source.compactionId === undefined) return null\n return source.compactionId\n}\n\n/**\n * The shadowed seqs of a block, recursing into distilled parent blocks: a\n * tier-2 block shadows its parent's checkpoint node, so recovering its\n * originals requires expanding that node into the parent block's own shadowed\n * seqs. Cycle-safe (a block can never be its own ancestor).\n */\nexport function expandShadowedSeqs(session: Session, blockId: string): number[] {\n const ledger = rebuildBlockLedger(session.events)\n const byId = new Map(ledger.map((entry) => [entry.blockId, entry]))\n const root = byId.get(blockId)\n if (root === undefined) return []\n const out: number[] = []\n const seen = new Set<string>()\n const visit = (entry: AcpBlockLedgerEntry): void => {\n if (seen.has(entry.blockId)) return\n seen.add(entry.blockId)\n for (const seq of entry.shadowedSeqs) {\n const childId = checkpointBlockIdOf(session.events, seq)\n const child = childId === null ? undefined : byId.get(childId)\n if (child !== undefined) visit(child)\n else out.push(seq)\n }\n }\n visit(root)\n return out\n}\n","/**\n * M1 — session-log projection: DSH surface events → acp-kernel CoreMessage.\n *\n * The ACP kernel is message-array based; DSH is event-log based. This module\n * is the bridge in the direction the engine needs (projectEvent /\n * eventsToCoreMessages). The reverse direction (CoreMessage[] → session\n * appends) is the M5 region transaction's job.\n * Mirrors billion-context-pi's `projectMessage`/`entriesToCoreMessages`\n * against DSH event shapes (see V-verification: SurfaceEventType =\n * 'user/message' | 'assistant/message' | 'tool/result').\n * @module billion-context-pi-dsh/messages\n */\n\nimport type { CoreMessage } from 'acp-kernel'\nimport type { SessionEvent } from '@deepseek-ai/dsh-session'\n\n/**\n * Extract plain text from a DSH content block array or string.\n *\n * Recursive: a real DSH `tool-result` block is `{ type: 'tool-result',\n * toolCallId, content: ContentBlock[] }` — the inner `content` array holds\n * the actual `text` blocks, so a top-level-only walk would drop every tool\n * result from the projection (and with it the seq's ref assignment, breaking\n * compress boundary resolution). Nested arrays are flattened depth-first.\n */\nexport function extractText(content: unknown): string {\n if (typeof content === 'string') return content\n if (!Array.isArray(content)) return ''\n const parts: string[] = []\n for (const block of content) {\n if (block === null || typeof block !== 'object') continue\n const b = block as { type?: unknown; text?: unknown; content?: unknown }\n if (b.type === 'text' && typeof b.text === 'string') {\n parts.push(b.text)\n } else if (Array.isArray(b.content)) {\n parts.push(extractText(b.content))\n }\n }\n return parts.join('\\n')\n}\n\ninterface ToolCallBlock {\n type: 'tool-call'\n id?: string\n name?: string\n arguments?: unknown\n}\n\nfunction toolCallsOf(content: unknown): ToolCallBlock[] {\n if (!Array.isArray(content)) return []\n return content.filter((b): b is ToolCallBlock => (b as { type?: string }).type === 'tool-call')\n}\n\nfunction stringifyArgs(args: unknown): string {\n if (!args) return ''\n if (typeof args === 'string') return args\n try {\n return JSON.stringify(args)\n } catch {\n return String(args)\n }\n}\n\n/**\n * Project one surface message event into CoreMessage(s).\n * - user/message → user text (verbatim content)\n * - assistant/message → assistant text, or one CoreMessage per tool-call\n * - tool/result → tool result (toolName/toolCallId, role 'tool')\n * Non-surface events project to nothing.\n */\nexport function projectEvent(event: SessionEvent): CoreMessage[] {\n switch (event.type) {\n case 'user/message': {\n const text = extractText((event.data as { content?: unknown }).content)\n return text.length > 0 ? [{ id: String(event.seq), role: 'user', contentType: 'text', text }] : []\n }\n case 'assistant/message': {\n const content = (event.data as { message?: { content?: unknown } }).message?.content\n const calls = toolCallsOf(content)\n const text = extractText(content)\n if (calls.length === 0) {\n return text.trim().length > 0\n ? [{ id: String(event.seq), role: 'assistant', contentType: 'text', text }]\n : []\n }\n if (calls.length === 1) {\n const call = calls[0]!\n const argStr = stringifyArgs(call.arguments)\n const body = argStr && text ? `${text}\\n${argStr}` : argStr || text\n return [{\n id: String(event.seq),\n role: 'assistant',\n contentType: 'tool-call',\n toolName: call.name ?? '',\n toolCallId: call.id ?? '',\n text: body,\n }]\n }\n return calls.map((call) => ({\n id: `${event.seq}#${call.id ?? ''}`,\n role: 'assistant' as const,\n contentType: 'tool-call' as const,\n toolName: call.name ?? '',\n toolCallId: call.id ?? '',\n text: stringifyArgs(call.arguments) || text,\n }))\n }\n case 'tool/result': {\n const message = (event.data as {\n message?: { content?: unknown; toolName?: string; toolCallId?: string }\n }).message\n const text = extractText(message?.content)\n if (text.length === 0) return []\n return [{\n id: String(event.seq),\n role: 'tool',\n contentType: 'tool-result',\n toolName: message?.toolName ?? '',\n toolCallId: message?.toolCallId ?? '',\n text,\n }]\n }\n default:\n return []\n }\n}\n\n/** Project a session's message events into CoreMessage[] in log order. */\nexport function eventsToCoreMessages(events: readonly SessionEvent[]): CoreMessage[] {\n const out: CoreMessage[] = []\n for (const event of events) out.push(...projectEvent(event))\n return out\n}\n\n/** The surface-visible message events of a session, in model-visible order. */\nexport function surfaceEventsOf(session: import('@deepseek-ai/dsh-session').Session): SessionEvent[] {\n return session.surface.nodes\n .map((seq) => session.events[seq])\n .filter((event): event is SessionEvent => event !== undefined)\n}\n\n/**\n * ALL message-type events in log order — the visible surface PLUS everything\n * shadowed by compression. The ACP kernel deactivates any block whose consumed\n * message ids are absent from the array it is given (syncBlocks), and refuses\n * to anchor a block boundary that cannot find its messages, so T2/T3\n * distillation requires the full log, not just the visible surface.\n */\nexport function allLogMessages(session: import('@deepseek-ai/dsh-session').Session): CoreMessage[] {\n return eventsToCoreMessages(session.events)\n}\n\n/** Extract the model-facing text of any surface message event. */\nexport function extractEventText(event: SessionEvent): string {\n switch (event.type) {\n case 'user/message':\n return extractText((event.data as { content?: unknown }).content)\n case 'assistant/message':\n return extractText((event.data as { message?: { content?: unknown } }).message?.content)\n case 'tool/result':\n return extractText((event.data as { message?: { content?: unknown } }).message?.content)\n default:\n return ''\n }\n}\n","/**\n * M3 — the four model tools: compress / decompress / search_context /\n * acp_status, registered through `ctx.tools` (defineTool).\n *\n * compress is the heart of ACP: the model writes the summary and the tool\n * lands it as a durable surface replacement (no second LLM summarization\n * call). decompress recovers shadowed content read-only from the log (DSH\n * keeps the originals — V5). search_context scores blocks rebuilt from the\n * log. acp_status reports the block ledger and pressure.\n * @module billion-context-dsh/tools\n */\n\nimport { defineTool, type ToolDefinition, type ToolRunContext } from '@deepseek-ai/dsh-tools'\nimport { defaultCountTokens, type CompressionCore } from 'acp-kernel'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport type { AcpStateStore } from './state.ts'\nimport { kernelConfigFor, type KernelConfigInput } from './config.ts'\nimport { resolveTokenCount } from './nudge.ts'\nimport { windowSourceLabel, type AcpWindow } from './window.ts'\nimport {\n AlreadyCompressedRangeError,\n blockRefForSummarySeq,\n compactionIdsOfKernelBlocks,\n expandShadowedSeqs,\n rebuildBlockLedger,\n resolveSurfaceRange,\n runCompactionTransaction,\n shadowedSeqsOf,\n surfaceSummary,\n type ResolvedSurfaceRange,\n} from './region.ts'\nimport { allLogMessages, eventsToCoreMessages, extractEventText, surfaceEventsOf } from './messages.ts'\n\nexport interface ToolEnvironment extends KernelConfigInput {\n readonly kernel: CompressionCore\n readonly store: AcpStateStore\n /** Resolve the effective context window for an agent (optional: status falls back to modelContextLimit). */\n readonly windowFor?: (agent: Agent) => Promise<AcpWindow>\n}\n\ninterface TextOutput {\n text: string\n}\n\nfunction textOutput(): {\n schema: { type: 'object'; properties: { text: { type: 'string' } }; additionalProperties: boolean }\n render: (args: unknown, value: TextOutput) => import('@deepseek-ai/dsh-llm').ContentBlock[]\n} {\n return {\n schema: {\n type: 'object',\n properties: { text: { type: 'string' } },\n additionalProperties: false,\n },\n render: (_args, value) => [{ type: 'text', text: value.text }],\n }\n}\n\nfunction requireAgent(exec: ToolRunContext): Agent {\n if (exec.agent === undefined) {\n throw new Error('billion-context-dsh: tool requires an agent execution context')\n }\n return exec.agent\n}\n\nconst compressParameters = {\n topic: { type: 'string' as const, description: 'Fallback topic for entries without their own.' },\n content: {\n type: 'array' as const,\n required: true,\n description: 'One or more ranges to compress, each with startSeq/endSeq boundaries (surface seqs) and a dense summary.',\n items: {\n type: 'object' as const,\n properties: {\n startSeq: {\n oneOf: [\n { type: 'integer' as const, description: 'First surface seq of the range.' },\n { type: 'string' as const, description: 'Seq as text; a trailing #callId fragment is ignored.' },\n ],\n },\n endSeq: {\n oneOf: [\n { type: 'integer' as const, description: 'Inclusive last surface seq of the range.' },\n { type: 'string' as const, description: 'Seq as text; a trailing #callId fragment is ignored.' },\n ],\n },\n summary: { type: 'string' as const, description: 'Complete technical summary replacing the range; keep paths, decisions, values verbatim. Minimum 50 characters.' },\n topic: { type: 'string' as const, description: 'Short label (3-5 words) for this range.' },\n },\n additionalProperties: false,\n },\n },\n} as const\n\n/** Normalize a seq arg: number, \"295\", or \"295#call_00_xxx\" → 295. */\nfunction parseSeq(value: number | string): number {\n const text = String(value).split('#')[0]!.trim()\n const seq = Number(text)\n if (!Number.isInteger(seq) || seq < 0) {\n throw new Error(`billion-context-dsh: invalid seq \"${String(value)}\" — use a surface seq like 295`)\n }\n return seq\n}\n\ninterface CompressArgs {\n topic?: string\n content: Array<{ startSeq: number | string; endSeq: number | string; summary: string; topic?: string }>\n}\n\n/** Resolve seq → kernel ref, then applyCompression and land the transaction. */\nasync function handleCompress(env: ToolEnvironment, args: CompressArgs, exec: ToolRunContext): Promise<TextOutput> {\n const agent = requireAgent(exec)\n const session = agent.session\n const state = env.store.stateFor(session)\n // The kernel gets the FULL log (visible + shadowed): syncBlocks deactivates\n // a block whose consumed messages are absent, and resolveBoundaries refuses\n // to anchor a block ref it cannot find, so tier-2/3 distillation needs the\n // originals present. The token count uses the same priority chain as the\n // nudge (projectedTokens → surfaceTokens → character heuristic).\n const coreMessages = allLogMessages(session)\n const surfaceMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const tokenCount = resolveTokenCount(agent, surfaceMessages)\n const config = kernelConfigFor(env)\n\n // Assign refs / advance state exactly like a turn would.\n const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount })\n env.store.set(session, turn.state)\n const byRaw = turn.state.messageRefs.byRaw\n\n const ranges: Array<\n ResolvedSurfaceRange & {\n startSeq: number\n endSeq: number\n startRef: string\n endRef: string\n summary: string\n topic?: string\n }\n > = []\n // Ranges whose whole span was already shadowed by earlier compressions.\n // They land as advisory warnings, never as errors or phantom blocks.\n const alreadyCompressedNotes: string[] = []\n for (const range of args.content) {\n const startSeq = parseSeq(range.startSeq)\n const endSeq = parseSeq(range.endSeq)\n let resolved: ResolvedSurfaceRange\n try {\n // Balance edges FIRST: the requested edges may sit on multi-tool-call\n // assistant messages, which project to `${seq}#${callId}` CoreMessage ids\n // and therefore have NO bare-`${seq}` ref. resolveSurfaceRange shifts them\n // to clean tool-pairing-balanced cuts that always carry a bare ref, so the\n // resolved refs exist and the shadowed span matches the returned range.\n // Edges shadowed by an earlier compression (stale nudge table / old\n // compress result) are remapped to the still-live content of the span.\n resolved = resolveSurfaceRange(session, startSeq, endSeq)\n } catch (error) {\n if (error instanceof AlreadyCompressedRangeError) {\n const covering = error.coveringBlockIds\n const blockNote = covering.length === 0\n ? ''\n : ` (block ${covering[0]!.slice(0, 8)}${covering.length > 1 ? ` +${covering.length - 1} more` : ''})`\n alreadyCompressedNotes.push(\n ` seqs ${error.start}..${error.end} already compressed${blockNote} — nothing to reclaim; decompress to recover the originals`,\n )\n continue\n }\n throw error\n }\n // An edge on an ACTIVE block's checkpoint summary node resolves to the\n // kernel block ref (bN) — the boundary that makes applyCompression distill\n // (tier 2/3) instead of folding the summary as a plain message.\n const startBlockRef = blockRefForSummarySeq(session, resolved.start)\n const endBlockRef = blockRefForSummarySeq(session, resolved.end)\n const startRef = startBlockRef ?? byRaw[String(resolved.start)]\n const endRef = endBlockRef ?? byRaw[String(resolved.end)]\n if (startRef === undefined || endRef === undefined) {\n throw new Error(\n `billion-context-dsh: seq ${resolved.start}..${resolved.end} has no assigned ref — `\n + 'the range must be on the current surface (run acp_status for the live seq list)',\n )\n }\n ranges.push({\n ...resolved,\n startSeq,\n endSeq,\n startRef,\n endRef,\n summary: range.summary,\n ...(range.topic ?? args.topic) === undefined ? {} : { topic: range.topic ?? args.topic },\n })\n }\n\n // Nothing to do: every requested range was already compressed.\n if (ranges.length === 0) {\n const text = ['Compressed 0 block(s), ~0 tokens reclaimed.', ...alreadyCompressedNotes]\n if (alreadyCompressedNotes.length > 0) {\n text.push(' (all requested ranges were already compressed — decompress a block to recover its originals)')\n }\n return { text: text.join('\\n') }\n }\n\n const applied = env.kernel.applyCompression({\n ranges: ranges.map(({ startRef, endRef, summary, topic }) => ({ startRef, endRef, summary, topic })),\n messages: coreMessages,\n state: turn.state,\n config,\n // Deliberately NOT overriding protectedMessageIds: with the full log the\n // kernel's recent/last-user protection is computed over the same\n // non-block-covered messages as the visible feed, so default behavior is\n // preserved. Any 'Excluded N protected message(s)' warning is surfaced.\n })\n if (applied.result.errors.length > 0) {\n return { text: `compress failed: ${applied.result.errors.join('; ')}` }\n }\n env.store.set(session, applied.state)\n\n // Match freshly created kernel blocks to the requested ranges by their\n // range key (the kernel stamps startRef/endRef onto each new block).\n const previousIds = new Set(turn.state.blocks.map((block) => block.blockId))\n const newBlocks = applied.state.blocks.filter((block) => !previousIds.has(block.blockId))\n const blockByRangeKey = new Map(newBlocks.map((block) => [`${block.startRef}::${block.endRef}`, block]))\n // Warnings carry two shapes: range-prefixed (\"Skipped range (a..b) — …\")\n // attributable to a specific range, and free-form (\"Excluded N protected\n // message(s) …\") attributable to the call as a whole.\n const warningByRangeKey = new Map<string, string[]>()\n const freeWarnings: string[] = []\n for (const warning of applied.result.warnings) {\n const match = /^Skipped range \\((.+?)\\.\\.(.+?)\\)/.exec(warning)\n if (match !== null) {\n const key = `${match[1]}::${match[2]}`\n const list = warningByRangeKey.get(key) ?? []\n list.push(warning)\n warningByRangeKey.set(key, list)\n } else {\n freeWarnings.push(warning)\n }\n }\n\n const lines: string[] = []\n let skippedRanges = 0\n for (const range of ranges) {\n const key = `${range.startRef}::${range.endRef}`\n const block = blockByRangeKey.get(key)\n if (block === undefined) {\n // The kernel skipped this range (already compressed / overlapped): no\n // kernel block was created, so no durable transaction is landed — the\n // ledger must never record a block the kernel does not know.\n skippedRanges += 1\n const warnings = warningByRangeKey.get(key) ?? []\n for (const warning of warnings) lines.push(` ${warning}`)\n continue\n }\n // The edges were already balanced above; shadow exactly that span.\n const { start, end } = range\n const shadowed = shadowedSeqsOf(session, start, end)\n // Estimate the reclaimed tokens from the actual shadowed messages so the\n // durable ledger (compaction/summary.shadowedTokenCount) reports a real\n // number instead of 0.\n let shadowedTokens = 0\n for (const seq of shadowed) {\n const event = session.events[seq]\n if (event !== undefined) shadowedTokens += defaultCountTokens(extractEventText(event))\n }\n const tier = block.tier === 2 || block.tier === 3 ? block.tier : 1\n const parentBlockIds = compactionIdsOfKernelBlocks(session, block.directBlockIds)\n const { compactionId } = runCompactionTransaction(session, {\n start,\n end,\n shadowedSeqs: shadowed,\n summary: [{ type: 'text', text: range.summary }],\n shadowedTokenCount: shadowedTokens,\n provider: agent.options.provider ?? '',\n model: agent.options.model ?? '',\n tier,\n kernelBlockId: block.blockId,\n ...(parentBlockIds.length === 0 ? {} : { parentBlockIds }),\n // Record the kernel block's raw coverage so a restarted engine\n // rehydrates the SAME effective messages (a tier-2 block's coverage is\n // its parents' originals, not the checkpoint node).\n directMessageIds: block.directMessageIds,\n effectiveMessageIds: block.effectiveMessageIds,\n })\n const adjusted = start !== range.startSeq || end !== range.endSeq\n const tierLabel = tier === 1 ? '' : `, tier ${tier}`\n const note = range.recovered === true\n ? ` (seqs ${range.startSeq}..${range.endSeq} were already shadowed — compressed the live remainder ${start}..${end})`\n : adjusted\n ? ` (adjusted from ${range.startSeq}..${range.endSeq} to balanced edges)`\n : ''\n lines.push(\n ` block ${compactionId.slice(0, 8)}: seqs ${start}..${end}, ${shadowed.length} messages shadowed${tierLabel}${note}`,\n )\n }\n\n const summaryLine = `Compressed ${applied.result.blocksCreated} block(s), ~${applied.result.tokensCompressed} tokens reclaimed.`\n const totalSkipped = skippedRanges + alreadyCompressedNotes.length\n const warningLines = [...freeWarnings.map((warning) => ` ${warning}`), ...alreadyCompressedNotes, ...lines]\n const footer = totalSkipped > 0\n ? ` (${totalSkipped} range(s) skipped — see warnings above)`\n : ''\n return { text: `${summaryLine}\\n${[...warningLines, footer].filter((line) => line !== '').join('\\n')}` }\n}\n\nconst decompressParameters = {\n blockId: { type: 'string' as const, required: true, description: 'Block id from acp_status or search_context (the compaction id).' },\n} as const\n\ninterface DecompressArgs {\n blockId: string\n}\n\nfunction handleDecompress(_env: ToolEnvironment, args: DecompressArgs, exec: ToolRunContext): TextOutput {\n const session = requireAgent(exec).session\n const ledger = rebuildBlockLedger(session.events)\n const block = ledger.find((entry) => entry.blockId.startsWith(args.blockId))\n if (block === undefined) {\n return { text: `decompress: block \"${args.blockId}\" not found (see acp_status for the block list)` }\n }\n const parts: string[] = []\n // Tier-2/3 blocks shadow parent checkpoint nodes: expand to the originals.\n for (const seq of expandShadowedSeqs(session, block.blockId)) {\n const event = session.events[seq]\n const text = event === undefined ? '' : extractEventText(event)\n if (text.length > 0) parts.push(`[seq ${seq}] ${text}`)\n }\n const tierNote = block.tier > 1 ? ` (tier ${block.tier}, distills ${block.parentBlockIds.length} block(s))` : ''\n return {\n text: `Block ${block.blockId} — ${block.summary}${tierNote}\\n\\n${parts.join('\\n\\n') || '(no recoverable content)'}`,\n }\n}\n\nconst searchParameters = {\n query: { type: 'string' as const, required: true, description: 'Search terms to find inside compressed blocks.' },\n limit: { type: 'integer' as const, description: 'Maximum results (default 5).' },\n} as const\n\ninterface SearchArgs {\n query: string\n limit?: number\n}\n\nfunction handleSearch(_env: ToolEnvironment, args: SearchArgs, exec: ToolRunContext): TextOutput {\n const session = requireAgent(exec).session\n const ledger = rebuildBlockLedger(session.events)\n const terms = args.query.toLowerCase().split(/\\s+/).filter(Boolean)\n const scored: Array<{ blockId: string; score: number; summary: string }> = []\n for (const block of ledger) {\n const original = block.shadowedSeqs\n .map((seq) => extractEventText(session.events[seq]!))\n .join('\\n')\n const haystack = `${block.summary}\\n${original}`.toLowerCase()\n let score = 0\n for (const term of terms) score += haystack.split(term).length - 1\n if (score > 0) scored.push({ blockId: block.blockId, score, summary: block.summary })\n }\n scored.sort((a, b) => b.score - a.score)\n const top = scored.slice(0, args.limit ?? 5)\n if (top.length === 0) return { text: `search_context: no matches for \"${args.query}\"` }\n return {\n text: `Matches for \"${args.query}\":\\n`\n + top.map((hit) => ` - ${hit.blockId} (score ${hit.score}): ${hit.summary.slice(0, 160)}`).join('\\n')\n + '\\n\\nDecompress with: decompress({ blockId })',\n }\n}\n\nconst statusParameters = {} as const\n\ninterface StatusArgs {\n [key: string]: never\n}\n\nasync function handleStatus(env: ToolEnvironment, _args: StatusArgs, exec: ToolRunContext): Promise<TextOutput> {\n const agent = requireAgent(exec)\n const session = agent.session\n const ledger = rebuildBlockLedger(session.events)\n const totalTokens = ledger.reduce((sum, block) => sum + block.shadowedTokenCount, 0)\n const coreMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const estimated = resolveTokenCount(agent, coreMessages)\n const window = env.windowFor === undefined\n ? { limit: env.modelContextLimit, source: 'explicit' as const }\n : await env.windowFor(agent)\n const limit = window.limit\n const lines = [\n `ACP status — session ${session.id}`,\n ` blocks: ${ledger.length}`,\n ` tokens compressed: ${totalTokens}`,\n ` estimated context: ${estimated} / ${limit} (${Math.round((estimated / limit) * 100)}%)`,\n ` context window: ${limit} (${windowSourceLabel(window)})`,\n ` surface: ${surfaceSummary(session)}`,\n ]\n for (const block of ledger.slice(0, 10)) {\n lines.push(` - ${block.blockId.slice(0, 8)}: seqs ${block.start}..${block.end} (${block.shadowedSeqs.length} msgs) — ${block.summary.slice(0, 80)}`)\n }\n return { text: lines.join('\\n') }\n}\n\n/** Build the four ACP model tools bound to one engine. */\nexport function makeTools(env: ToolEnvironment): ToolDefinition[] {\n return [\n defineTool({\n name: 'compress',\n description:\n 'Replace older conversation ranges with dense summaries you write. '\n + 'Each message seq is a surface reference. Single range: compress({ content: [{ startSeq, endSeq, summary }] }). '\n + 'Batch multiple unrelated ranges in one call (each content entry becomes its own block); keep ranges disjoint. '\n + 'Never compress content the current step is actively using. '\n + 'Seq refs must come from the CURRENT surface (acp_status or the latest nudge): a span whose edges were shadowed '\n + 'by an earlier compress is auto-remapped to its still-live content, a fully compressed span is reported as '\n + 'already compressed, and invented/other-session seqs fail with guidance.',\n parameters: compressParameters,\n output: textOutput(),\n async execute(args, exec) {\n return handleCompress(env, args as CompressArgs, exec)\n },\n }),\n defineTool({\n name: 'decompress',\n description: 'Recover the original content of a compressed block by its blockId (read-only; does not unshadow the range).',\n parameters: decompressParameters,\n output: textOutput(),\n execute(args, exec) {\n return Promise.resolve(handleDecompress(env, args as DecompressArgs, exec))\n },\n }),\n defineTool({\n name: 'search_context',\n description: 'Search inside compressed blocks (summaries and original content) for information the model no longer sees in context.',\n parameters: searchParameters,\n output: textOutput(),\n execute(args, exec) {\n return Promise.resolve(handleSearch(env, args as SearchArgs, exec))\n },\n }),\n defineTool({\n name: 'acp_status',\n description: 'Report the ACP block ledger: compressed blocks, reclaimed tokens, and current context pressure.',\n parameters: statusParameters,\n output: textOutput(),\n execute(args, exec) {\n return handleStatus(env, args as StatusArgs, exec)\n },\n }),\n ]\n}\n","/**\n * Kernel configuration assembly — the DSH counterpart of billion-context-pi's\n * `resolveConfig`: build acp-kernel's `Config` from adapter-level knobs.\n *\n * Defaults are deliberately the acp-kernel `defaultConfig` values (the same\n * defaults billion-context-pi ships: nudge window 45%–75%, emergency 95%,\n * growth ratio 5%, protected last messages 5). Every knob is optional — an\n * omitted value keeps the kernel default, so the behavior matches the Pi\n * adapter exactly unless a deployment opts out.\n *\n * NOTE: `AcpCompactionEngine` (src/index.ts) ships its own engine-level\n * defaults 0.70/0.85 for the two nudge thresholds on top of this layer, so an\n * engine with no explicit config lands on 0.70/0.85, not 0.75/0.95.\n * @module billion-context-dsh/config\n */\n\nimport { defaultConfig, type Config } from 'acp-kernel'\n\n/** The kernel-facing knobs shared by the nudge path and the compress tool. */\nexport interface KernelConfigInput {\n readonly modelContextLimit: number\n /** Nudge window lower bound (usage fraction; validation only — the growth-driven trigger has no percentage floor). Kernel default: 0.45. */\n readonly nudgeMinContextLimitPct?: number\n /** Nudge window upper bound — over-limit guarantee line. Kernel default: 0.75. */\n readonly nudgeMaxContextLimitPct?: number\n /** Emergency nudge threshold (bypasses per-turn dedup). Kernel default: 0.95. */\n readonly nudgeEmergencyThresholdPct?: number\n /** Any other acp-kernel Config override (the billion-context-pi escape hatch). */\n readonly coreOverrides?: Partial<Config>\n}\n\n/**\n * Assemble the kernel config: `defaultConfig(limit)` merged with the optional\n * nudge thresholds (merged into the defaults, never replacing them wholesale)\n * and any additional `coreOverrides`.\n */\nexport function kernelConfigFor(input: KernelConfigInput): Config {\n const nudgePatch: Partial<Config['nudge']> = {}\n if (input.nudgeMinContextLimitPct !== undefined) nudgePatch.minContextLimitPct = input.nudgeMinContextLimitPct\n if (input.nudgeMaxContextLimitPct !== undefined) nudgePatch.maxContextLimitPct = input.nudgeMaxContextLimitPct\n if (input.nudgeEmergencyThresholdPct !== undefined) nudgePatch.emergencyThresholdPct = input.nudgeEmergencyThresholdPct\n\n const overrides: Partial<Config> = { ...input.coreOverrides }\n if (Object.keys(nudgePatch).length > 0) {\n overrides.nudge = { ...defaultConfig(input.modelContextLimit).nudge, ...nudgePatch }\n }\n return defaultConfig(input.modelContextLimit, overrides)\n}\n","/**\n * M4 — ACP nudge: the kernel's compression recommendation, rendered as an\n * injected user message with a seq-based compressible-range table (D1:\n * \"seq is the ref\" — DSH has no in-memory message rewrite hook, so the model\n * targets ranges by surface seq rather than by <acp> tags).\n * @module billion-context-dsh/nudge\n */\n\nimport {\n defaultCountTokens,\n type CompressionCore,\n type CoreMessage,\n type NudgeDecision,\n} from 'acp-kernel'\nimport { createUserMessage, type UserMessage } from '@deepseek-ai/dsh-llm'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport { AcpStateStore } from './state.ts'\nimport { allLogMessages, eventsToCoreMessages, surfaceEventsOf } from './messages.ts'\nimport { buildCompressibleSeqRanges, findOpenTurn, summarySeqOfKernelBlock, surfaceSummary } from './region.ts'\nimport { kernelConfigFor, type KernelConfigInput } from './config.ts'\n\n/** Kernel inputs the nudge path shares with the compress tool. */\nexport interface NudgeEnvironment extends KernelConfigInput {\n readonly kernel: CompressionCore\n readonly store: AcpStateStore\n}\n\nexport interface NudgeOutcome {\n readonly message: UserMessage\n readonly emergency: boolean\n}\n\n/**\n * Resolve the best available token count for ACP pressure decisions.\n *\n * Priority chain:\n * 1. `sessionProjections.contextPressure.projectedTokens` — matches the UI's\n * context-occupancy display (includes fixed overhead: system prompt, tool\n * definitions, AGENTS.md, etc.). Provider-anchored; reacts to compaction.\n * 2. `tokenMeter.measure(session).surfaceTokens` — heuristic surface-only\n * estimate (pure conversation messages, no fixed overhead). Falls back\n * when sessionProjections is unavailable or has no provider anchor yet.\n * 3. `defaultCountTokens` character heuristic — last resort for tests and\n * minimal hosts that lack the token-meter service.\n */\nexport function resolveTokenCount(agent: Agent, coreMessages: CoreMessage[]): number {\n // 1. Prefer sessionProjections.contextPressure.projectedTokens (matches UI).\n const projections = agent.ctx?.get?.('sessionProjections') as\n | { snapshot?: (session: unknown) => { values?: { contextPressure?: { projectedTokens?: number } } } }\n | undefined\n const projected = projections?.snapshot?.(agent.session)?.values?.contextPressure?.projectedTokens\n if (typeof projected === 'number' && projected > 0) return projected\n\n // 2. Fallback to tokenMeter surfaceTokens (heuristic, no fixed overhead).\n const meter = agent.ctx?.get?.('tokenMeter') as\n | { measure?: (session: unknown) => { surfaceTokens?: number } }\n | undefined\n const surface = meter?.measure?.(agent.session)?.surfaceTokens\n if (typeof surface === 'number' && surface > 0) return surface\n\n // 3. Last resort: character heuristic.\n return coreMessages.reduce((sum, message) => sum + defaultCountTokens(message.text ?? ''), 0)\n}\n\n/**\n * Render the compressible-range table as seq refs for the model.\n * Computed directly from the surface (not the kernel's ref map, which can\n * drift and hide large tool results) — see buildCompressibleSeqRanges.\n */\nexport function rangeTable(session: import('@deepseek-ai/dsh-session').Session): string {\n const ranges = buildCompressibleSeqRanges(session).slice(0, 6)\n if (ranges.length === 0) return ''\n const lines = ranges.map((range) => ` - seq ${range.start}..${range.end} — ${range.count} messages, ~${range.tokens} tokens`)\n return [\n '',\n `Surface: ${surfaceSummary(session)}`,\n 'Compressible ranges (suggestions only — compress any consumed span; refs are surface seqs):',\n ...lines,\n 'Compress with: compress({ content: [{ startSeq, endSeq, summary }] }) — content is an array: batch multiple unrelated segments in one call, each entry its own block. Keep ranges disjoint.',\n 'Snapshot taken at nudge time: the seqs go stale once the surface moves (a later compress shadows them), so re-run acp_status for fresh refs before compressing.',\n ].join('\\n')\n}\n\n/**\n * The token count driving pressure decisions. Prefer `resolveTokenCount` which\n * uses `sessionProjections.contextPressure.projectedTokens` (matches the UI's\n * context-occupancy display, including fixed overhead). Falls back to\n * `tokenMeter.measure(session).surfaceTokens`, then `defaultCountTokens`\n * character heuristic for tests and minimal hosts.\n */\nfunction measuredTokenCount(agent: Agent, coreMessages: CoreMessage[]): number {\n return resolveTokenCount(agent, coreMessages)\n}\n\n/**\n * Decide and build one nudge message for the agent's next pre-step. Returns\n * null when the kernel recommends no nudge or one was already injected for the\n * current turn (emergency nudges always bypass the dedup). Also advances the\n * in-memory kernel state (ref assignment) so the compress tool can resolve\n * seq → mNNNNN refs.\n */\nexport function buildNudge(\n agent: Agent,\n env: NudgeEnvironment,\n lastNudgeTurn: Map<string, number>,\n): NudgeOutcome | null {\n const session = agent.session\n const state = env.store.stateFor(session)\n // Full log for the kernel (so block anchors survive — see handleCompress);\n // the measured token count stays a SURFACE measurement.\n const coreMessages = allLogMessages(session)\n const surfaceMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const tokenCount = measuredTokenCount(agent, surfaceMessages)\n const config = kernelConfigFor(env)\n const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount })\n env.store.set(session, turn.state)\n\n const nudge = turn.nudge\n if (nudge === undefined || !nudge.shouldInject) return null\n const emergency = nudge.breakdown?.emergencyOverride === 1\n\n const turnNumber = findOpenTurn(session.events) ?? 0\n const alreadyShown = !emergency && lastNudgeTurn.get(session.id) === turnNumber\n if (alreadyShown) return null\n lastNudgeTurn.set(session.id, turnNumber)\n\n const text = buildNudgeText(nudge, emergency, session)\n const message = createUserMessage({\n content: [{ type: 'text', text }],\n source: { kind: 'plugin', plugin: 'acp-nudge' },\n })\n return { message, emergency }\n}\n\n/**\n * A short ADVISORY nudge — ACP is model-driven, the model decides whether and\n * when to compress. Full guidance (tools, philosophy, summary rules) lives in\n * the system prompt once, not in every nudge.\n */\nexport function buildNudgeText(\n nudge: NudgeDecision,\n emergency: boolean,\n session: import('@deepseek-ai/dsh-session').Session,\n): string {\n // Cap the reported percentage at 100: a broken measurement (e.g. response\n // pressure folded in) must never surface as an absurd \"230%\" to the model.\n const pct = Math.round(Math.min(nudge.contextUsage, 1) * 100)\n const frame = emergency\n ? `⚠️ Context usage is at ${pct}% of the window — nearly full. Consider compressing consumed ranges soon so working context stays available; the choice and timing are yours.`\n : `Context usage is at ${pct}%. This is a suggestion, not a requirement — you decide whether and when to compress.`\n const guidance = 'Compress by need, not by percentage: replace only ranges you have genuinely consumed, with dense self-contained summaries.'\n const parts = [frame, '', guidance]\n if ((nudge.tier === 2 || nudge.tier === 3) && (nudge.tierTargetBlocks?.length ?? 0) > 0) {\n const targets = nudge.tierTargetBlocks!\n const summarySeqs = targets\n .map((block) => summarySeqOfKernelBlock(session, block.blockId))\n .filter((seq): seq is number => seq !== null)\n const pending = nudge.tier === 2 ? nudge.breakdown?.pendingT2 : nudge.breakdown?.pendingT3\n const tokens = typeof pending === 'number' ? pending : 0\n parts.push(\n `Tier ${nudge.tier}: ${targets.length} tier-${nudge.tier - 1} block(s) distillable (${tokens} tokens) — compress their summary node(s) [seqs ${summarySeqs.join(', ')}] to reclaim the original messages.`,\n )\n }\n parts.push(rangeTable(session))\n return parts.join('\\n')\n}\n","/**\n * Auto context-window detection — resolve the model's real context window\n * from the host LLM runtime instead of trusting a hardcoded config default.\n *\n * `agent.ctx.llm` (the cordis `LlmRuntime` service) exposes\n * `resolveModelInfo(provider, model)` → `{ context: { contextWindow } }`, the\n * exact-route capacity the adapter learned from the provider API (pi-ai reads\n * `context_window`/`context_length` during discovery). Probing is a standalone\n * capability query — no request is sent.\n * @module billion-context-dsh/window\n */\n\nimport type { Agent } from '@deepseek-ai/dsh-agent'\n\n/** Fallback window when auto-detection is unavailable. Same default as acp-kernel's `defaultConfig`. */\nexport const DEFAULT_CONTEXT_WINDOW = 128000\n\n/** The effective context window plus where it came from. */\nexport interface AcpWindow {\n /** Effective context window in tokens. */\n readonly limit: number\n /** Where the limit came from. */\n readonly source: 'explicit' | 'auto' | 'default'\n /** Route the auto window was resolved for (auto source only). */\n readonly provider?: string\n readonly model?: string\n}\n\n/** Human label for an AcpWindow's source (used by acp_status). */\nexport function windowSourceLabel(window: AcpWindow): string {\n if (window.source === 'explicit') return 'configured'\n if (window.source === 'auto') {\n return `auto-detected from ${window.provider ?? '?'}/${window.model ?? '?'}`\n }\n return 'default (auto-detection unavailable)'\n}\n\n/** The minimal LlmRuntime surface the probe needs (structural — no as any). */\ninterface LlmProbe {\n resolveModelInfo?: (\n provider: string,\n model: string,\n signal?: AbortSignal,\n ) => Promise<{ context?: { contextWindow?: number } }>\n}\n\n/**\n * Probe the model's real context window. Returns null when the host provides\n * no llm service, the adapter discloses no window, or the probe throws —\n * callers fall back to DEFAULT_CONTEXT_WINDOW. Never throws.\n */\nexport async function detectContextWindow(\n agent: Agent,\n provider: string,\n model: string,\n): Promise<number | null> {\n const llm = agent.ctx?.get?.('llm') as LlmProbe | undefined\n if (llm?.resolveModelInfo === undefined) return null\n try {\n const info = await llm.resolveModelInfo(provider, model)\n const window = info?.context?.contextWindow\n if (typeof window === 'number' && Number.isInteger(window) && window > 0) return window\n return null\n } catch {\n return null\n }\n}\n","/**\n * M4 — the `/acp` slash command: a human-friendly window into the same\n * machinery the model tools expose (status, one-shot compress, decompress).\n * @module billion-context-dsh/commands\n */\n\nimport type { CommandDefinition } from '@deepseek-ai/dsh-commands'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport type { ToolEnvironment } from './tools.ts'\nimport { resolveTokenCount } from './nudge.ts'\nimport {\n blockRefForSummarySeq,\n expandShadowedSeqs,\n rebuildBlockLedger,\n resolveSurfaceRange,\n runCompactionTransaction,\n shadowedSeqsOf,\n} from './region.ts'\nimport { eventsToCoreMessages, extractEventText, surfaceEventsOf } from './messages.ts'\nimport { defaultConfig, defaultCountTokens } from 'acp-kernel'\nimport { windowSourceLabel } from './window.ts'\n\nasync function statusText(env: ToolEnvironment, agent: Agent): Promise<string> {\n const session = agent.session\n const ledger = rebuildBlockLedger(session.events)\n const totalTokens = ledger.reduce((sum, block) => sum + block.shadowedTokenCount, 0)\n const coreMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const estimated = resolveTokenCount(agent, coreMessages)\n const window = env.windowFor === undefined\n ? { limit: env.modelContextLimit, source: 'explicit' as const }\n : await env.windowFor(agent)\n const limit = window.limit\n const lines = [\n `ACP status — session ${session.id}`,\n ` blocks: ${ledger.length}`,\n ` tokens compressed: ${totalTokens}`,\n ` estimated context: ${estimated} / ${limit} (${Math.round((estimated / limit) * 100)}%)`,\n ` context window: ${limit} (${windowSourceLabel(window)})`,\n ]\n for (const block of ledger.slice(0, 10)) {\n const tier = block.tier > 1 ? ` [T${block.tier}]` : ''\n lines.push(` - ${block.blockId.slice(0, 8)}${tier}: seqs ${block.start}..${block.end} — ${block.summary.slice(0, 80)}`)\n }\n return lines.join('\\n')\n}\n\nfunction compressText(env: ToolEnvironment, agent: Agent, args: string[]): string {\n if (args.length < 3) {\n return '/acp compress <startSeq> <endSeq> <summary...>'\n }\n const startSeq = Number(args[0])\n const endSeq = Number(args[1])\n const summary = args.slice(2).join(' ')\n if (!Number.isInteger(startSeq) || !Number.isInteger(endSeq)) {\n return '/acp compress: startSeq and endSeq must be integers'\n }\n const session = agent.session\n const { start, end } = resolveSurfaceRange(session, startSeq, endSeq)\n // A checkpoint summary node can only be distilled through the kernel (the\n // compress tool); /acp compress is a plain T1 range transaction, so refuse\n // rather than silently folding the summary as a message.\n if (blockRefForSummarySeq(session, start) !== null || blockRefForSummarySeq(session, end) !== null) {\n return '/acp compress: the range touches a compressed block summary node — distill it with the compress tool (seq-based batch), not /acp compress'\n }\n const shadowed = shadowedSeqsOf(session, startSeq, endSeq)\n let shadowedTokens = 0\n for (const seq of shadowed) {\n const event = session.events[seq]\n if (event !== undefined) shadowedTokens += defaultCountTokens(extractEventText(event))\n }\n const { compactionId } = runCompactionTransaction(session, {\n start,\n end,\n shadowedSeqs: shadowed,\n summary: [{ type: 'text', text: summary }],\n shadowedTokenCount: shadowedTokens,\n provider: agent.options.provider ?? '',\n model: agent.options.model ?? '',\n })\n return `Compressed seqs ${start}..${end} (${shadowed.length} messages) as block ${compactionId.slice(0, 8)}`\n}\n\nfunction decompressText(_env: ToolEnvironment, agent: Agent, args: string[]): string {\n if (args.length < 1) return '/acp decompress <blockId>'\n const session = agent.session\n const ledger = rebuildBlockLedger(session.events)\n const block = ledger.find((entry) => entry.blockId.startsWith(args[0]!))\n if (block === undefined) return `block \"${args[0]}\" not found (see /acp status)`\n // Tier-2/3 blocks shadow parent checkpoint nodes: expand to the originals.\n const parts = expandShadowedSeqs(session, block.blockId)\n .map((seq) => extractEventText(session.events[seq]!))\n .filter((text) => text.length > 0)\n return `Block ${block.blockId} — ${block.summary}\\n\\n${parts.join('\\n\\n') || '(no recoverable content)'}`\n}\n\n/** Register the /acp command (idempotent per engine). */\nexport function acpCommand(env: ToolEnvironment): CommandDefinition {\n return {\n name: 'acp',\n description:\n 'Active Context Pruning — model-driven context compression. '\n + 'Usage: /acp status | /acp compress <startSeq> <endSeq> <summary> | /acp decompress <blockId>',\n handler: async (invocation) => {\n const raw = invocation.rawInput.trim()\n if (raw === '' || raw === 'status') {\n return { kind: 'success', text: await statusText(env, invocation.agent) }\n }\n if (raw.startsWith('compress')) {\n return { kind: 'success', text: compressText(env, invocation.agent, raw.slice('compress'.length).trim().split(/\\s+/) ) }\n }\n if (raw.startsWith('decompress')) {\n return { kind: 'success', text: decompressText(env, invocation.agent, raw.slice('decompress'.length).trim().split(/\\s+/)) }\n }\n return { kind: 'error', text: `unknown /acp subcommand \"${raw.split(/\\s+/)[0]}\" — use status | compress | decompress` }\n },\n }\n}\n","/**\n * M4 — the ACP system-prompt section (DSH counterpart of billion-context-pi's\n * ACP_SYSTEM_PROMPT): the load-bearing compression guidance lives here, ONCE,\n * instead of being re-sent with every nudge. The nudge itself stays a short,\n * advisory notice — ACP is model-driven, the model decides whether and when\n * to compress (never \"compress now\").\n * @module billion-context-dsh/system-prompt\n */\n\nimport { COMPRESS_PHILOSOPHY } from 'acp-kernel'\n\nexport const ACP_SYSTEM_PROMPT = `Active Context Pruning — model-driven context management\n\nYOU decide whether and when to compress context. Nothing forces you: the injected \"nudge\" is a suggestion, not an order, and you may ignore it when compression would not help. Compress only ranges you have genuinely consumed (read tool outputs, finished explorations, superseded steps) that the current work no longer needs verbatim.\n\n${COMPRESS_PHILOSOPHY}\n\nCompression tools (refs are SURFACE SEQS, not ids):\n- 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 — 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. Seq refs must be on the current surface: seqs from older nudges or earlier compresses go stale as the surface moves, so a stale span is auto-remapped to its still-live remainder (the result reports the adjusted span), a fully compressed span is reported as already compressed, and invented/other-session seqs fail with guidance.\n- decompress: recover a compressed block's original content, read-only. decompress({ blockId }).\n- search_context: find information inside compressed blocks BEFORE decompressing. search_context({ query }).\n- acp_status: current context usage and the live compressible-range list. Run it right before compressing — the only seqs that never go stale are the ones you just read.\n\nTiered 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 — decompress on the tier-2 block recovers the full originals.\n\nWhen 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 — the surface moves as messages land and compress; verify with acp_status.`\n\n/** System-prompt section order: tool guidance lives in 100–199. */\nexport const ACP_SYSTEM_PROMPT_ORDER = 150\n"],"mappings":"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","names":["defaultCountTokens","defaultCountTokens","createUserMessage","defaultCountTokens","createUserMessage","defaultCountTokens","defaultCountTokens","defaultCountTokens"]}
|
package/dist/nudge.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* targets ranges by surface seq rather than by <acp> tags).
|
|
6
6
|
* @module billion-context-dsh/nudge
|
|
7
7
|
*/
|
|
8
|
-
import { type CompressionCore, type NudgeDecision } from 'acp-kernel';
|
|
8
|
+
import { type CompressionCore, type CoreMessage, type NudgeDecision } from 'acp-kernel';
|
|
9
9
|
import { type UserMessage } from '@deepseek-ai/dsh-llm';
|
|
10
10
|
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
11
11
|
import { AcpStateStore } from './state.ts';
|
|
@@ -19,6 +19,20 @@ export interface NudgeOutcome {
|
|
|
19
19
|
readonly message: UserMessage;
|
|
20
20
|
readonly emergency: boolean;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolve the best available token count for ACP pressure decisions.
|
|
24
|
+
*
|
|
25
|
+
* Priority chain:
|
|
26
|
+
* 1. `sessionProjections.contextPressure.projectedTokens` — matches the UI's
|
|
27
|
+
* context-occupancy display (includes fixed overhead: system prompt, tool
|
|
28
|
+
* definitions, AGENTS.md, etc.). Provider-anchored; reacts to compaction.
|
|
29
|
+
* 2. `tokenMeter.measure(session).surfaceTokens` — heuristic surface-only
|
|
30
|
+
* estimate (pure conversation messages, no fixed overhead). Falls back
|
|
31
|
+
* when sessionProjections is unavailable or has no provider anchor yet.
|
|
32
|
+
* 3. `defaultCountTokens` character heuristic — last resort for tests and
|
|
33
|
+
* minimal hosts that lack the token-meter service.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveTokenCount(agent: Agent, coreMessages: CoreMessage[]): number;
|
|
22
36
|
/**
|
|
23
37
|
* Render the compressible-range table as seq refs for the model.
|
|
24
38
|
* Computed directly from the surface (not the kernel's ref map, which can
|
package/package.json
CHANGED