billion-context-dsh 0.2.24 → 0.2.26
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 +28 -26
- package/README.md +28 -26
- package/cordis.patch.yml +1 -1
- package/dist/commands.d.ts +2 -2
- package/dist/index.d.ts +29 -4
- package/dist/index.js +255 -46
- package/dist/index.js.map +1 -1
- package/dist/messages.d.ts +85 -15
- package/dist/nudge.d.ts +9 -1
- package/dist/presets.d.ts +1 -1
- package/dist/region.d.ts +1 -1
- package/dist/settings.d.ts +5 -5
- package/dist/tools.d.ts +3 -3
- package/dist/window.d.ts +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3363,6 +3363,9 @@ function makePreview(text, query, len) {
|
|
|
3363
3363
|
return prefix + text.slice(start, end).trim() + suffix;
|
|
3364
3364
|
}
|
|
3365
3365
|
|
|
3366
|
+
// src/index.ts
|
|
3367
|
+
import { CONTEXT_WINDOW_EXCEEDED_CODE } from "@deepseek-ai/dsh-llm";
|
|
3368
|
+
|
|
3366
3369
|
// src/lru.ts
|
|
3367
3370
|
var DEFAULT_SESSION_CACHE_LIMIT = 512;
|
|
3368
3371
|
var LruMap = class extends Map {
|
|
@@ -3478,9 +3481,18 @@ function buildToolCallIndex(events) {
|
|
|
3478
3481
|
}
|
|
3479
3482
|
var SUMMARY_FRAME_PREFIX = "[Model-written summary \u2014 not user words; re-verify any obligations before relying on them]";
|
|
3480
3483
|
function withSummaryFramePrefix(text) {
|
|
3481
|
-
|
|
3484
|
+
if (text.startsWith(SUMMARY_FRAME_PREFIX)) return text;
|
|
3485
|
+
if (isEngineWrittenSummary(text)) return text;
|
|
3486
|
+
return `${SUMMARY_FRAME_PREFIX}
|
|
3482
3487
|
${text}`;
|
|
3483
3488
|
}
|
|
3489
|
+
var ENGINE_SUMMARY_LEAD = "[engine-written summary \u2014 context-overflow emergency compaction";
|
|
3490
|
+
function isEngineWrittenSummary(text) {
|
|
3491
|
+
return text.startsWith(ENGINE_SUMMARY_LEAD);
|
|
3492
|
+
}
|
|
3493
|
+
function overflowMarkerSummary(hiddenCount) {
|
|
3494
|
+
return `${ENGINE_SUMMARY_LEAD}: ${hiddenCount} surface message(s) hidden because the provider rejected the request as exceeding the context window. The originals are intact in the session log \u2014 use search_context or decompress (see acp_status) to read them, or re-run the compress tool over this range to write a proper summary.]`;
|
|
3495
|
+
}
|
|
3484
3496
|
function projectEvent(event, toolNames) {
|
|
3485
3497
|
switch (event.type) {
|
|
3486
3498
|
case "user/message": {
|
|
@@ -3606,7 +3618,12 @@ function contentBlocksOfEvent(event) {
|
|
|
3606
3618
|
function isCheckpointNode(event) {
|
|
3607
3619
|
if (event.type !== "user/message") return false;
|
|
3608
3620
|
const source = event.data.source;
|
|
3609
|
-
return source?.plugin === "compact";
|
|
3621
|
+
return source?.plugin === "compact" || source?.kind === "compact-checkpoint";
|
|
3622
|
+
}
|
|
3623
|
+
function checkpointCompactionIdOf(event) {
|
|
3624
|
+
if (!isCheckpointNode(event)) return null;
|
|
3625
|
+
const source = event.data.source;
|
|
3626
|
+
return typeof source?.compactionId === "string" ? source.compactionId : null;
|
|
3610
3627
|
}
|
|
3611
3628
|
var METADATA_PLUGINS = /* @__PURE__ */ new Set([
|
|
3612
3629
|
"acp-nudge",
|
|
@@ -3619,17 +3636,44 @@ var REAL_CONTENT_PLUGINS = /* @__PURE__ */ new Set([
|
|
|
3619
3636
|
"user-approval",
|
|
3620
3637
|
"tools-ptc"
|
|
3621
3638
|
]);
|
|
3639
|
+
var REAL_CONTENT_KINDS = /* @__PURE__ */ new Set([
|
|
3640
|
+
"runtime-context",
|
|
3641
|
+
// dynamic-context snapshot (was '@deepseek-ai/dsh-system-prompt')
|
|
3642
|
+
"ptc-mode"
|
|
3643
|
+
// deferred tool context (was 'tools-ptc' / 'tools-code-mode')
|
|
3644
|
+
]);
|
|
3645
|
+
var AUDITED_RELAY_KINDS = /* @__PURE__ */ new Set([
|
|
3646
|
+
"subagent-report",
|
|
3647
|
+
"subagent-settled"
|
|
3648
|
+
]);
|
|
3622
3649
|
var HOST_INSTRUCTION_KINDS = /* @__PURE__ */ new Set([
|
|
3623
3650
|
"agent-instructions",
|
|
3624
3651
|
// AGENTS.md injection (hook shape: {kind:'agent-instructions', form:'instructions'})
|
|
3625
|
-
"skill-catalog"
|
|
3652
|
+
"skill-catalog",
|
|
3626
3653
|
// skill catalog (form:'catalog')
|
|
3654
|
+
// Host compaction summary row (DSH >= 0.1.7; was plugin 'dsh-compaction-basic').
|
|
3655
|
+
// That legacy name was never whitelisted, so its rows were barriers already —
|
|
3656
|
+
// the renamed spelling keeps exactly that treatment instead of silently
|
|
3657
|
+
// becoming foldable content (issue #169).
|
|
3658
|
+
"compact-basic"
|
|
3627
3659
|
]);
|
|
3660
|
+
function sourcePluginOf(source) {
|
|
3661
|
+
if (source === void 0 || typeof source !== "object") return void 0;
|
|
3662
|
+
const kind = source.kind;
|
|
3663
|
+
if (kind === "plugin") {
|
|
3664
|
+
return typeof source.plugin === "string" && source.plugin.length > 0 ? source.plugin : void 0;
|
|
3665
|
+
}
|
|
3666
|
+
if (typeof kind === "string" && kind.startsWith("plugin:")) {
|
|
3667
|
+
const name = kind.slice("plugin:".length);
|
|
3668
|
+
return name.length > 0 ? name : void 0;
|
|
3669
|
+
}
|
|
3670
|
+
return void 0;
|
|
3671
|
+
}
|
|
3628
3672
|
function isAgentInstructionsRow(event) {
|
|
3629
3673
|
if (event.type !== "user/message") return false;
|
|
3630
3674
|
const source = event.data.source;
|
|
3631
3675
|
if (!source) return false;
|
|
3632
|
-
return source.kind === "agent-instructions" || source
|
|
3676
|
+
return source.kind === "agent-instructions" || sourcePluginOf(source) === "agent-instructions";
|
|
3633
3677
|
}
|
|
3634
3678
|
function classifySurfaceEvent(event) {
|
|
3635
3679
|
if (isCheckpointNode(event)) return "checkpoint";
|
|
@@ -3638,20 +3682,26 @@ function classifySurfaceEvent(event) {
|
|
|
3638
3682
|
if (!source) return "real";
|
|
3639
3683
|
const kind = source.kind;
|
|
3640
3684
|
if (kind === "user") return "real";
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
if (
|
|
3685
|
+
const plugin = sourcePluginOf(source);
|
|
3686
|
+
if (kind === "plugin" || plugin !== void 0) {
|
|
3687
|
+
if (plugin !== void 0 && METADATA_PLUGINS.has(plugin)) return "metadata";
|
|
3688
|
+
if (plugin !== void 0 && REAL_CONTENT_PLUGINS.has(plugin)) return "real";
|
|
3644
3689
|
return "instruction";
|
|
3645
3690
|
}
|
|
3646
|
-
if (kind !==
|
|
3647
|
-
return "
|
|
3691
|
+
if (typeof kind !== "string") return "real";
|
|
3692
|
+
if (HOST_INSTRUCTION_KINDS.has(kind)) return "instruction";
|
|
3693
|
+
if (REAL_CONTENT_KINDS.has(kind) || AUDITED_RELAY_KINDS.has(kind)) return "real";
|
|
3694
|
+
return "instruction";
|
|
3648
3695
|
}
|
|
3649
3696
|
function isRealUserTurn(event) {
|
|
3650
3697
|
if (event.type !== "user/message") return false;
|
|
3651
3698
|
if (classifySurfaceEvent(event) !== "real") return false;
|
|
3652
3699
|
const source = event.data.source;
|
|
3653
|
-
|
|
3654
|
-
|
|
3700
|
+
const plugin = sourcePluginOf(source);
|
|
3701
|
+
if (plugin !== void 0 && REAL_CONTENT_PLUGINS.has(plugin)) return false;
|
|
3702
|
+
const kind = source?.kind;
|
|
3703
|
+
if (typeof kind === "string" && REAL_CONTENT_KINDS.has(kind)) return false;
|
|
3704
|
+
return kind !== "subagent-report" && kind !== "subagent-settled";
|
|
3655
3705
|
}
|
|
3656
3706
|
|
|
3657
3707
|
// src/host-tokens.ts
|
|
@@ -4036,9 +4086,8 @@ function summarySeqIndex(events) {
|
|
|
4036
4086
|
const index = /* @__PURE__ */ new Map();
|
|
4037
4087
|
for (const event of events) {
|
|
4038
4088
|
if (event.type !== "user/message") continue;
|
|
4039
|
-
const
|
|
4040
|
-
|
|
4041
|
-
if (compactionId !== void 0 && !index.has(compactionId)) index.set(compactionId, event.seq);
|
|
4089
|
+
const compactionId = checkpointCompactionIdOf(event);
|
|
4090
|
+
if (compactionId !== null && !index.has(compactionId)) index.set(compactionId, event.seq);
|
|
4042
4091
|
}
|
|
4043
4092
|
return index;
|
|
4044
4093
|
}
|
|
@@ -4128,7 +4177,10 @@ function hideSurfaceSeqs(session, seqs, text, priceEvent = hostPriceEvent) {
|
|
|
4128
4177
|
const body = text !== void 0 && text.trim().length > 0 ? text : PRUNE_NOTE;
|
|
4129
4178
|
session.append("user/message", createUserMessage({
|
|
4130
4179
|
content: [{ type: "text", text: body }],
|
|
4131
|
-
|
|
4180
|
+
// V4 producer kind (issue #163): DSH ≥0.1.7's V4 admission rejects the
|
|
4181
|
+
// legacy wrapper `{ kind: 'plugin', plugin: … }`; `plugin:<name>` is what
|
|
4182
|
+
// the host's own V3→V4 migration emits and is accepted by 0.1.5 too.
|
|
4183
|
+
source: { kind: "plugin:billion-context-dsh" }
|
|
4132
4184
|
}), {
|
|
4133
4185
|
surfaceOp: { op: "replace", startSeq: start, endSeq: end },
|
|
4134
4186
|
sourceEventSeqs: [...seqs]
|
|
@@ -4436,10 +4488,10 @@ function blockRegistry(session) {
|
|
|
4436
4488
|
}
|
|
4437
4489
|
function blockRefForSummarySeq(session, seq) {
|
|
4438
4490
|
const event = eventAtOf(session, seq);
|
|
4439
|
-
if (event
|
|
4440
|
-
const
|
|
4441
|
-
if (
|
|
4442
|
-
const entry = blockRegistry(session).find((r) => r.blockId ===
|
|
4491
|
+
if (event === void 0) return null;
|
|
4492
|
+
const compactionId = checkpointCompactionIdOf(event);
|
|
4493
|
+
if (compactionId === null) return null;
|
|
4494
|
+
const entry = blockRegistry(session).find((r) => r.blockId === compactionId);
|
|
4443
4495
|
if (entry === void 0) return null;
|
|
4444
4496
|
return entry.kernelBlockId;
|
|
4445
4497
|
}
|
|
@@ -4459,10 +4511,8 @@ function summarySeqOfKernelBlock(session, kernelBlockId) {
|
|
|
4459
4511
|
}
|
|
4460
4512
|
function checkpointBlockIdOf(events, seq) {
|
|
4461
4513
|
const event = events[seq];
|
|
4462
|
-
if (event
|
|
4463
|
-
|
|
4464
|
-
if (source?.plugin !== "compact" || source.compactionId === void 0) return null;
|
|
4465
|
-
return source.compactionId;
|
|
4514
|
+
if (event === void 0) return null;
|
|
4515
|
+
return checkpointCompactionIdOf(event);
|
|
4466
4516
|
}
|
|
4467
4517
|
function expandShadowedSeqs(session, blockId) {
|
|
4468
4518
|
const ledger = rebuildBlockLedger(sessionEventsOf(session));
|
|
@@ -4902,7 +4952,13 @@ function buildNudge(agent, env, lastNudgeTurn, emergencyNudges, onEmergencyCapHi
|
|
|
4902
4952
|
);
|
|
4903
4953
|
const message = createUserMessage2({
|
|
4904
4954
|
content: [{ type: "text", text }],
|
|
4905
|
-
|
|
4955
|
+
// V4 producer kind (issue #163): DSH ≥0.1.7's V4 admission rejects the
|
|
4956
|
+
// legacy wrapper shape `{ kind: 'plugin', plugin: … }` outright (a wedged
|
|
4957
|
+
// batch fails the NEXT turn with "format v4 message requires a
|
|
4958
|
+
// producer-owned source kind"). `plugin:<name>` is exactly what the host's
|
|
4959
|
+
// own V3→V4 migration emits for unregistered plugins, and DSH 0.1.5
|
|
4960
|
+
// sessions accept it too (probe-verified), so no version gate is needed.
|
|
4961
|
+
source: { kind: "plugin:acp-nudge" }
|
|
4906
4962
|
});
|
|
4907
4963
|
return { message, emergency };
|
|
4908
4964
|
}
|
|
@@ -5022,7 +5078,7 @@ function windowSourceLabel(window) {
|
|
|
5022
5078
|
if (window.source === "auto") {
|
|
5023
5079
|
return `auto-detected from ${window.provider ?? "?"}/${window.model ?? "?"}`;
|
|
5024
5080
|
}
|
|
5025
|
-
if (window.probeFailed === true) return "default (auto-detection failed \u2014 see /acp config)";
|
|
5081
|
+
if (window.probeFailed === true) return "default (auto-detection failed \u2014 see /acp-prune config)";
|
|
5026
5082
|
return "default (auto-detection unavailable)";
|
|
5027
5083
|
}
|
|
5028
5084
|
function projectedContextWindow(agent) {
|
|
@@ -5879,7 +5935,7 @@ async function statusText(env, agent) {
|
|
|
5879
5935
|
);
|
|
5880
5936
|
}
|
|
5881
5937
|
if (window.probeFailed === true) {
|
|
5882
|
-
lines.push(` \u26A0 window auto-detection failed \u2014 using the ${limit} fallback (change modelContextLimit or autoModelContextLimit via /acp config \u2014 or restart \u2014 to re-probe)`);
|
|
5938
|
+
lines.push(` \u26A0 window auto-detection failed \u2014 using the ${limit} fallback (change modelContextLimit or autoModelContextLimit via /acp-prune config \u2014 or restart \u2014 to re-probe)`);
|
|
5883
5939
|
}
|
|
5884
5940
|
const state = structuredClone(env.store.stateFor(session));
|
|
5885
5941
|
const config = kernelConfigFor({ ...env, modelContextLimit: limit });
|
|
@@ -5902,18 +5958,18 @@ async function statusText(env, agent) {
|
|
|
5902
5958
|
}
|
|
5903
5959
|
function compressText(env, agent, args) {
|
|
5904
5960
|
if (args.length < 3) {
|
|
5905
|
-
return "/acp compress <startSeq> <endSeq> <summary...>";
|
|
5961
|
+
return "/acp-prune compress <startSeq> <endSeq> <summary...>";
|
|
5906
5962
|
}
|
|
5907
5963
|
const startSeq = Number(args[0]);
|
|
5908
5964
|
const endSeq = Number(args[1]);
|
|
5909
5965
|
const summary = args.slice(2).join(" ");
|
|
5910
5966
|
if (!Number.isInteger(startSeq) || !Number.isInteger(endSeq)) {
|
|
5911
|
-
return "/acp compress: startSeq and endSeq must be integers";
|
|
5967
|
+
return "/acp-prune compress: startSeq and endSeq must be integers";
|
|
5912
5968
|
}
|
|
5913
5969
|
const session = agent.session;
|
|
5914
5970
|
const { start, end } = resolveSurfaceRange(session, startSeq, endSeq);
|
|
5915
5971
|
if (blockRefForSummarySeq(session, start) !== null || blockRefForSummarySeq(session, end) !== null) {
|
|
5916
|
-
return "/acp compress: the range touches a compressed block summary node \u2014 distill it with the compress tool (seq-based batch), not /acp compress";
|
|
5972
|
+
return "/acp-prune compress: the range touches a compressed block summary node \u2014 distill it with the compress tool (seq-based batch), not /acp-prune compress";
|
|
5917
5973
|
}
|
|
5918
5974
|
const shadowed = shadowedSeqsOf(session, start, end);
|
|
5919
5975
|
const instructionHits = guardedRowsInSpan(guardedSurfaceSeqsOf(session), shadowed);
|
|
@@ -5933,7 +5989,7 @@ function compressText(env, agent, args) {
|
|
|
5933
5989
|
});
|
|
5934
5990
|
return `Compressed seqs ${start}..${end} (${shadowed.length} messages) as block ${compactionId.slice(0, 8)}`;
|
|
5935
5991
|
}
|
|
5936
|
-
var DECOMPRESS_USAGE = "/acp decompress <blockId> [offset] [limit]";
|
|
5992
|
+
var DECOMPRESS_USAGE = "/acp-prune decompress <blockId> [offset] [limit]";
|
|
5937
5993
|
function decompressText(_env, agent, args) {
|
|
5938
5994
|
if (args.length < 1) return DECOMPRESS_USAGE;
|
|
5939
5995
|
const offset = args[1] === void 0 ? 0 : Number(args[1]);
|
|
@@ -5944,7 +6000,7 @@ function decompressText(_env, agent, args) {
|
|
|
5944
6000
|
const blockId = blockIdOfKernelRef(session, args[0]);
|
|
5945
6001
|
const ledger = rebuildBlockLedger(sessionEventsOf(session));
|
|
5946
6002
|
const block = blockId === null ? ledger.find((entry) => entry.blockId.startsWith(args[0])) : ledger.find((entry) => entry.blockId === blockId);
|
|
5947
|
-
if (block === void 0) return `block "${args[0]}" not found (see /acp status)`;
|
|
6003
|
+
if (block === void 0) return `block "${args[0]}" not found (see /acp-prune status)`;
|
|
5948
6004
|
const expanded = expandShadowedSeqs(session, block.blockId);
|
|
5949
6005
|
const page = sliceDecompressPage(
|
|
5950
6006
|
expanded,
|
|
@@ -5964,14 +6020,14 @@ function decompressText(_env, agent, args) {
|
|
|
5964
6020
|
`Block ${block.blockId} \u2014 ${block.summary}`,
|
|
5965
6021
|
`[messages ${page.offset + 1}..${page.offset + page.seqs.length} of ${page.total}]`
|
|
5966
6022
|
];
|
|
5967
|
-
if (!page.exhausted) lines.push(`Continue with: /acp decompress ${block.blockId.slice(0, 8)} ${page.offset + page.seqs.length}`);
|
|
6023
|
+
if (!page.exhausted) lines.push(`Continue with: /acp-prune decompress ${block.blockId.slice(0, 8)} ${page.offset + page.seqs.length}`);
|
|
5968
6024
|
lines.push("", parts.join("\n\n") || "(no recoverable content)");
|
|
5969
6025
|
return lines.join("\n");
|
|
5970
6026
|
}
|
|
5971
6027
|
function acpCommand(env) {
|
|
5972
6028
|
return {
|
|
5973
|
-
name: "acp",
|
|
5974
|
-
description: "Active Context Pruning \u2014 model-driven context compression. Usage: /acp status | /acp compress <startSeq> <endSeq> <summary> | /acp decompress <blockId> [offset] [limit] | /acp config [list|set <key> <value>|reset <key>|all]",
|
|
6029
|
+
name: "acp-prune",
|
|
6030
|
+
description: "Active Context Pruning \u2014 model-driven context compression. Usage: /acp-prune status | /acp-prune compress <startSeq> <endSeq> <summary> | /acp-prune decompress <blockId> [offset] [limit] | /acp-prune config [list|set <key> <value>|reset <key>|all]",
|
|
5975
6031
|
handler: async (invocation) => {
|
|
5976
6032
|
const raw = invocation.rawInput.trim();
|
|
5977
6033
|
if (raw === "" || raw === "status") {
|
|
@@ -5986,7 +6042,7 @@ function acpCommand(env) {
|
|
|
5986
6042
|
if (raw.startsWith("decompress")) {
|
|
5987
6043
|
return { kind: "success", text: decompressText(env, invocation.agent, raw.slice("decompress".length).trim().split(/\s+/)) };
|
|
5988
6044
|
}
|
|
5989
|
-
return { kind: "error", text: `unknown /acp subcommand "${raw.split(/\s+/)[0]}" \u2014 use status | compress | decompress | config` };
|
|
6045
|
+
return { kind: "error", text: `unknown /acp-prune subcommand "${raw.split(/\s+/)[0]}" \u2014 use status | compress | decompress | config` };
|
|
5990
6046
|
}
|
|
5991
6047
|
};
|
|
5992
6048
|
}
|
|
@@ -5998,7 +6054,7 @@ function isSettingsKey(key) {
|
|
|
5998
6054
|
}
|
|
5999
6055
|
function settingsWriteFailure(error) {
|
|
6000
6056
|
if (error instanceof SettingsConflictError) {
|
|
6001
|
-
return "conflict: another writer changed this setting at the same time \u2014 run /acp config again";
|
|
6057
|
+
return "conflict: another writer changed this setting at the same time \u2014 run /acp-prune config again";
|
|
6002
6058
|
}
|
|
6003
6059
|
return `rejected: ${String(error)}`;
|
|
6004
6060
|
}
|
|
@@ -6026,7 +6082,7 @@ function configListText(surface) {
|
|
|
6026
6082
|
}
|
|
6027
6083
|
lines.push("", " changes apply to running sessions immediately (no restart)");
|
|
6028
6084
|
lines.push(" coreOverrides (composition layer) merge LAST and beat these values on same-name keys");
|
|
6029
|
-
lines.push(" /acp config reset <key> returns the key to the composition row / engine default");
|
|
6085
|
+
lines.push(" /acp-prune config reset <key> returns the key to the composition row / engine default");
|
|
6030
6086
|
return lines.join("\n");
|
|
6031
6087
|
}
|
|
6032
6088
|
async function configSetText(surface, key, rawValue) {
|
|
@@ -6088,13 +6144,13 @@ async function configText(env, rest) {
|
|
|
6088
6144
|
const verb = args[0] ?? "list";
|
|
6089
6145
|
if (verb === "list") return configListText(surface);
|
|
6090
6146
|
if (verb === "set") {
|
|
6091
|
-
if (args.length < 3) return "usage: /acp config set <key> <value> (e.g. /acp config set nudgeMaxContextLimitPct 0.72)";
|
|
6147
|
+
if (args.length < 3) return "usage: /acp-prune config set <key> <value> (e.g. /acp-prune config set nudgeMaxContextLimitPct 0.72)";
|
|
6092
6148
|
return configSetText(surface, args[1], args.slice(2).join(" "));
|
|
6093
6149
|
}
|
|
6094
6150
|
if (verb === "reset") {
|
|
6095
6151
|
return configResetText(surface, args[1] ?? "all");
|
|
6096
6152
|
}
|
|
6097
|
-
return `unknown /acp config verb "${verb}" \u2014 use list | set <key> <value> | reset <key>|all`;
|
|
6153
|
+
return `unknown /acp-prune config verb "${verb}" \u2014 use list | set <key> <value> | reset <key>|all`;
|
|
6098
6154
|
}
|
|
6099
6155
|
|
|
6100
6156
|
// src/system-prompt.ts
|
|
@@ -6107,6 +6163,9 @@ var DEFAULT_CONFIG = {
|
|
|
6107
6163
|
autoTools: true,
|
|
6108
6164
|
autoCommand: true,
|
|
6109
6165
|
autoNudge: true,
|
|
6166
|
+
// Same default as the host's compaction-basic policy: one owned retry per
|
|
6167
|
+
// unrelieved overflow, then the original error is preserved.
|
|
6168
|
+
maxOverflowRetries: 1,
|
|
6110
6169
|
// Nudge thresholds: engine defaults 0.70/0.85 — deliberately below the
|
|
6111
6170
|
// kernel/billion-context-pi 0.75/0.95. 0.95 leaves no room to act before
|
|
6112
6171
|
// the API rejects, and the host's compaction-basic line (thresholdRatio
|
|
@@ -6120,7 +6179,11 @@ var DEFAULT_CONFIG = {
|
|
|
6120
6179
|
function resolveAcpConfig(config = {}) {
|
|
6121
6180
|
const resolved = resolvePresetThresholds({ ...DEFAULT_CONFIG, ...config }, config);
|
|
6122
6181
|
assertNudgeThresholdOrder(resolved);
|
|
6123
|
-
|
|
6182
|
+
const maxOverflowRetries = resolved.maxOverflowRetries ?? 1;
|
|
6183
|
+
if (!Number.isInteger(maxOverflowRetries) || maxOverflowRetries < 0) {
|
|
6184
|
+
throw new Error(`maxOverflowRetries must be a non-negative integer (got ${maxOverflowRetries})`);
|
|
6185
|
+
}
|
|
6186
|
+
return { ...resolved, maxOverflowRetries };
|
|
6124
6187
|
}
|
|
6125
6188
|
function resolvePresetThresholds(base, config) {
|
|
6126
6189
|
if (base.preset === void 0) return base;
|
|
@@ -6132,6 +6195,17 @@ function resolvePresetThresholds(base, config) {
|
|
|
6132
6195
|
nudgeEmergencyThresholdPct: config.nudgeEmergencyThresholdPct ?? preset.nudgeEmergencyThresholdPct
|
|
6133
6196
|
};
|
|
6134
6197
|
}
|
|
6198
|
+
function presetFilledSettingsEntry(config) {
|
|
6199
|
+
const entry = filterSettingsEntry(config);
|
|
6200
|
+
if (config.preset === void 0) return entry;
|
|
6201
|
+
const preset = resolvePreset(config.preset);
|
|
6202
|
+
return {
|
|
6203
|
+
...entry,
|
|
6204
|
+
nudgeMinContextLimitPct: config.nudgeMinContextLimitPct ?? preset.nudgeMinContextLimitPct,
|
|
6205
|
+
nudgeMaxContextLimitPct: config.nudgeMaxContextLimitPct ?? preset.nudgeMaxContextLimitPct,
|
|
6206
|
+
nudgeEmergencyThresholdPct: config.nudgeEmergencyThresholdPct ?? preset.nudgeEmergencyThresholdPct
|
|
6207
|
+
};
|
|
6208
|
+
}
|
|
6135
6209
|
function assertNudgeThresholdOrder(config) {
|
|
6136
6210
|
const { nudgeMinContextLimitPct: min, nudgeMaxContextLimitPct: max, nudgeEmergencyThresholdPct: emergency } = config;
|
|
6137
6211
|
const describe = `min ${min ?? "kernel default"} / max ${max ?? "kernel default"} / emergency ${emergency ?? "kernel default"}`;
|
|
@@ -6171,12 +6245,16 @@ var AcpCompactionEngine = class extends CompactionEngine {
|
|
|
6171
6245
|
windowCache = /* @__PURE__ */ new Map();
|
|
6172
6246
|
/** Live settings snapshot thunk (composition → user settings layer); swapped when the settings provider attaches (SettingsProvider.installSection). */
|
|
6173
6247
|
readSettingsSource = () => resolveAcpSettings({});
|
|
6174
|
-
/** The settings service, captured lazily for /acp config (undefined in provider-less processes). */
|
|
6248
|
+
/** The settings service, captured lazily for /acp-prune config (undefined in provider-less processes). */
|
|
6175
6249
|
settingsService;
|
|
6176
|
-
/** /acp config read/write surface. */
|
|
6250
|
+
/** /acp-prune config read/write surface. */
|
|
6177
6251
|
settingsCommand;
|
|
6178
6252
|
/** Per route the adapter's per-request output cap (the output reservation); null = undisclosed. */
|
|
6179
6253
|
outputReservationCache = /* @__PURE__ */ new Map();
|
|
6254
|
+
/** Per-agent context-overflow recovery budget (mirrors the host's `overflowRetries`). */
|
|
6255
|
+
overflowRetries = /* @__PURE__ */ new Map();
|
|
6256
|
+
/** Per-session overflow agents, so session progress can reset the recovery budget. */
|
|
6257
|
+
overflowSessions = /* @__PURE__ */ new Map();
|
|
6180
6258
|
constructor(ctx, config = {}) {
|
|
6181
6259
|
super(ctx);
|
|
6182
6260
|
this.config = resolveAcpConfig(config);
|
|
@@ -6185,7 +6263,7 @@ var AcpCompactionEngine = class extends CompactionEngine {
|
|
|
6185
6263
|
this.kernel = createCore(ports);
|
|
6186
6264
|
setDocCacheCap(128 * 1024 * 1024);
|
|
6187
6265
|
this.store = new AcpStateStore();
|
|
6188
|
-
const compositionEntry =
|
|
6266
|
+
const compositionEntry = presetFilledSettingsEntry(config);
|
|
6189
6267
|
let current = resolveAcpSettings(compositionEntry);
|
|
6190
6268
|
this.readSettingsSource = () => current;
|
|
6191
6269
|
const engine = this;
|
|
@@ -6202,6 +6280,12 @@ var AcpCompactionEngine = class extends CompactionEngine {
|
|
|
6202
6280
|
this.settingsCommand = makeSettingsCommandSurface(() => this.settingsService, () => current);
|
|
6203
6281
|
if (this.config.settingsEnabled !== false) {
|
|
6204
6282
|
ctx.inject(["settings"], (settingsCtx) => {
|
|
6283
|
+
if (typeof settingsCtx.settings?.installSection !== "function") {
|
|
6284
|
+
this.ctx.logger.warn(
|
|
6285
|
+
"billion-context-dsh: host settings service has no installSection (removed in dsh-settings >= 0.1.7) \u2014 the compaction-acp settings section is not registered; the six knobs keep their composition values and /acp-prune config reports the section unavailable"
|
|
6286
|
+
);
|
|
6287
|
+
return void 0;
|
|
6288
|
+
}
|
|
6205
6289
|
settingsCtx.settings.installSection(ctx, ACP_SETTINGS_NAMESPACE, AcpSettingsSchema, compositionEntry, {
|
|
6206
6290
|
// The seam's source type follows the entry it registered, so `source`
|
|
6207
6291
|
// is a partial view of the settings; re-resolve it into a
|
|
@@ -6223,7 +6307,7 @@ var AcpCompactionEngine = class extends CompactionEngine {
|
|
|
6223
6307
|
kernel: this.kernel,
|
|
6224
6308
|
store: this.store,
|
|
6225
6309
|
// The settings-exposed knobs read LIVE from the settings source, so a
|
|
6226
|
-
// settings.yaml edit (or /acp config set) hot-applies to every
|
|
6310
|
+
// settings.yaml edit (or /acp-prune config set) hot-applies to every
|
|
6227
6311
|
// subsequent call — consumers never see stale numbers. (ToolEnvironment
|
|
6228
6312
|
// fields are readonly properties; getters satisfy them.)
|
|
6229
6313
|
get modelContextLimit() {
|
|
@@ -6240,7 +6324,7 @@ var AcpCompactionEngine = class extends CompactionEngine {
|
|
|
6240
6324
|
},
|
|
6241
6325
|
coreOverrides: this.config.coreOverrides,
|
|
6242
6326
|
// Display-only: which named preset produced the thresholds above (if any),
|
|
6243
|
-
// so /acp status can name it. The resolved pct values above are what the
|
|
6327
|
+
// so /acp-prune status can name it. The resolved pct values above are what the
|
|
6244
6328
|
// kernel actually reads — this field never feeds kernelConfigFor.
|
|
6245
6329
|
preset: this.config.preset,
|
|
6246
6330
|
windowFor: (agent) => this.windowFor(agent),
|
|
@@ -6282,6 +6366,13 @@ var AcpCompactionEngine = class extends CompactionEngine {
|
|
|
6282
6366
|
});
|
|
6283
6367
|
}
|
|
6284
6368
|
ctx.on("session/event", (session, event) => {
|
|
6369
|
+
if (event.type === "assistant/message") {
|
|
6370
|
+
const overflowAgent = this.overflowSessions.get(session);
|
|
6371
|
+
if (overflowAgent !== void 0) {
|
|
6372
|
+
this.overflowRetries.delete(overflowAgent);
|
|
6373
|
+
this.overflowSessions.delete(session);
|
|
6374
|
+
}
|
|
6375
|
+
}
|
|
6285
6376
|
if (event.type !== "tool/result") return;
|
|
6286
6377
|
const message = event.data.message;
|
|
6287
6378
|
const block = message.content[0];
|
|
@@ -6312,6 +6403,49 @@ var AcpCompactionEngine = class extends CompactionEngine {
|
|
|
6312
6403
|
if (outcome === null) return decision;
|
|
6313
6404
|
return { kind: "enter", messages: [...decision.messages, outcome.message] };
|
|
6314
6405
|
});
|
|
6406
|
+
ctx.on("agent/status", ({ agent, status }) => {
|
|
6407
|
+
if (status !== "idle") return;
|
|
6408
|
+
this.overflowRetries.delete(agent);
|
|
6409
|
+
this.overflowSessions.delete(agent.session);
|
|
6410
|
+
});
|
|
6411
|
+
ctx.on("agent/request-error", async ({ agent, failure, signal }, next) => {
|
|
6412
|
+
if (failure.code !== CONTEXT_WINDOW_EXCEEDED_CODE || signal.aborted) return next();
|
|
6413
|
+
this.overflowSessions.set(agent.session, agent);
|
|
6414
|
+
const retries = this.overflowRetries.get(agent) ?? 0;
|
|
6415
|
+
const max = this.config.maxOverflowRetries ?? 1;
|
|
6416
|
+
if (retries >= max) {
|
|
6417
|
+
this.ctx.logger.warn(
|
|
6418
|
+
`billion-context-dsh: context-overflow recovery budget spent (${max} retries) for session ${agent.session.id}; preserving the original request error`
|
|
6419
|
+
);
|
|
6420
|
+
return next();
|
|
6421
|
+
}
|
|
6422
|
+
const generation = agent.session.surface.replaceGeneration;
|
|
6423
|
+
let result;
|
|
6424
|
+
try {
|
|
6425
|
+
result = await this.compactForOverflow(agent, signal);
|
|
6426
|
+
} catch (error) {
|
|
6427
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6428
|
+
if (!signal.aborted && agent.session.surface.replaceGeneration > generation) {
|
|
6429
|
+
this.ctx.logger.warn(
|
|
6430
|
+
`billion-context-dsh: context-overflow compaction failed after durable surface progress: ${message}; retrying from the replacement surface`
|
|
6431
|
+
);
|
|
6432
|
+
this.overflowRetries.set(agent, retries + 1);
|
|
6433
|
+
return { kind: "retry" };
|
|
6434
|
+
}
|
|
6435
|
+
this.ctx.logger.warn(
|
|
6436
|
+
`billion-context-dsh: context-overflow compaction failed: ${message}; ${signal.aborted ? "cancellation prevents retry" : "preserving the original request error"}`
|
|
6437
|
+
);
|
|
6438
|
+
return next();
|
|
6439
|
+
}
|
|
6440
|
+
if (signal.aborted || agent.session.surface.replaceGeneration <= generation) return next();
|
|
6441
|
+
if (result !== null) {
|
|
6442
|
+
this.ctx.logger.info(
|
|
6443
|
+
`compaction (context overflow recovery): shadowed ${result.shadowedSeqs.length} surface nodes (seqs ${result.start}-${result.end}, ~${result.shadowedTokenCount} tokens)`
|
|
6444
|
+
);
|
|
6445
|
+
}
|
|
6446
|
+
this.overflowRetries.set(agent, retries + 1);
|
|
6447
|
+
return { kind: "retry" };
|
|
6448
|
+
});
|
|
6315
6449
|
const systemPrompt = ctx.get("systemPrompt");
|
|
6316
6450
|
if (systemPrompt !== void 0) {
|
|
6317
6451
|
systemPrompt.section({
|
|
@@ -6381,7 +6515,7 @@ var AcpCompactionEngine = class extends CompactionEngine {
|
|
|
6381
6515
|
cap = probe.outputReservation;
|
|
6382
6516
|
if (probe.contextWindow === null) {
|
|
6383
6517
|
this.ctx.logger.warn(
|
|
6384
|
-
`billion-context-dsh: context-window auto-detection failed for ${provider}/${model} \u2014 using the ${DEFAULT_CONTEXT_WINDOW} fallback (change modelContextLimit or autoModelContextLimit via /acp config \u2014 or restart \u2014 to re-probe)`
|
|
6518
|
+
`billion-context-dsh: context-window auto-detection failed for ${provider}/${model} \u2014 using the ${DEFAULT_CONTEXT_WINDOW} fallback (change modelContextLimit or autoModelContextLimit via /acp-prune config \u2014 or restart \u2014 to re-probe)`
|
|
6385
6519
|
);
|
|
6386
6520
|
window = { limit: DEFAULT_CONTEXT_WINDOW, source: "default", provider, model, probeFailed: true };
|
|
6387
6521
|
cap = null;
|
|
@@ -6435,6 +6569,81 @@ var AcpCompactionEngine = class extends CompactionEngine {
|
|
|
6435
6569
|
if (cap === null || cap >= window.limit) return window;
|
|
6436
6570
|
return { ...window, rawLimit: window.limit, outputReserved: cap, limit: window.limit - cap };
|
|
6437
6571
|
}
|
|
6572
|
+
/**
|
|
6573
|
+
* Best-effort emergency compaction for one provider-confirmed context
|
|
6574
|
+
* overflow: pick the largest eligible (guarded, tool-pairing-balanced)
|
|
6575
|
+
* surface range and land the normal durable transaction with a fixed
|
|
6576
|
+
* engine-written marker summary. No LLM call — the provider just rejected
|
|
6577
|
+
* the request for being too large, so there is no model turn available to
|
|
6578
|
+
* write a summary; the originals stay in the append-only log, so
|
|
6579
|
+
* search_context still indexes them, decompress restores them, and the
|
|
6580
|
+
* model can re-run the compress tool over the marker later to write a real
|
|
6581
|
+
* summary. Returns null when nothing eligible exists (nothing to reclaim).
|
|
6582
|
+
*/
|
|
6583
|
+
async compactForOverflow(agent, signal) {
|
|
6584
|
+
signal.throwIfAborted();
|
|
6585
|
+
const session = agent.session;
|
|
6586
|
+
const state = this.store.stateFor(session);
|
|
6587
|
+
const coreMessages = allLogMessages(session);
|
|
6588
|
+
const surfaceMessages = eventsToCoreMessages(surfaceEventsOf(session));
|
|
6589
|
+
const tokenCount = resolveTokenCount(agent, surfaceMessages);
|
|
6590
|
+
const window = await this.windowFor(agent);
|
|
6591
|
+
const config = kernelConfigFor({ ...this.env, modelContextLimit: window.limit });
|
|
6592
|
+
const turn = this.kernel.processTurn({ messages: coreMessages, state, config, tokenCount, renderTags: "none" });
|
|
6593
|
+
this.store.set(session, turn.state);
|
|
6594
|
+
const byRaw = turn.state.messageRefs.byRaw;
|
|
6595
|
+
let firstSeq;
|
|
6596
|
+
let lastSeq;
|
|
6597
|
+
for (const seq of session.surface.nodes) {
|
|
6598
|
+
if (byRaw[String(seq)] === void 0) continue;
|
|
6599
|
+
firstSeq ??= seq;
|
|
6600
|
+
lastSeq = seq;
|
|
6601
|
+
}
|
|
6602
|
+
if (firstSeq === void 0 || lastSeq === void 0) return null;
|
|
6603
|
+
const view = {
|
|
6604
|
+
ranges: [{ startRef: byRaw[String(firstSeq)], endRef: byRaw[String(lastSeq)] }],
|
|
6605
|
+
refs: { byRef: turn.state.messageRefs.byRef }
|
|
6606
|
+
};
|
|
6607
|
+
const ranges = buildCompressibleSeqRanges(session, view, {
|
|
6608
|
+
preserveRecent: 5,
|
|
6609
|
+
mediaPriceOf: meterMediaPriceResolver(agent, session)
|
|
6610
|
+
});
|
|
6611
|
+
if (ranges.length === 0) return null;
|
|
6612
|
+
const best = ranges.reduce((largest, range) => range.tokens > largest.tokens ? range : largest);
|
|
6613
|
+
const startRef = byRaw[String(best.start)];
|
|
6614
|
+
const endRef = byRaw[String(best.end)];
|
|
6615
|
+
if (startRef === void 0 || endRef === void 0) return null;
|
|
6616
|
+
const summary = overflowMarkerSummary(best.count);
|
|
6617
|
+
const applied = this.kernel.applyCompression({
|
|
6618
|
+
ranges: [{ startRef, endRef, summary, topic: "context-overflow recovery" }],
|
|
6619
|
+
messages: coreMessages,
|
|
6620
|
+
state: turn.state,
|
|
6621
|
+
config
|
|
6622
|
+
});
|
|
6623
|
+
if (applied.result.blocksCreated === 0) return null;
|
|
6624
|
+
this.store.set(session, applied.state);
|
|
6625
|
+
const previousIds = new Set(turn.state.blocks.map((block) => block.blockId));
|
|
6626
|
+
const created = applied.state.blocks.find((block) => !previousIds.has(block.blockId));
|
|
6627
|
+
if (created === void 0) return null;
|
|
6628
|
+
const shadowed = shadowedSeqsOf(session, best.start, best.end);
|
|
6629
|
+
if (shadowed.length === 0) return null;
|
|
6630
|
+
const shadowedTokenCount = shadowedTokensViaMeter(session, shadowed, agent.ctx);
|
|
6631
|
+
const { provider, model } = routeFor(agent);
|
|
6632
|
+
runCompactionTransaction(session, {
|
|
6633
|
+
start: best.start,
|
|
6634
|
+
end: best.end,
|
|
6635
|
+
shadowedSeqs: shadowed,
|
|
6636
|
+
summary: [{ type: "text", text: summary }],
|
|
6637
|
+
shadowedTokenCount,
|
|
6638
|
+
provider,
|
|
6639
|
+
model,
|
|
6640
|
+
topic: "context-overflow recovery",
|
|
6641
|
+
kernelBlockId: created.blockId,
|
|
6642
|
+
directMessageIds: created.directMessageIds,
|
|
6643
|
+
effectiveMessageIds: created.effectiveMessageIds
|
|
6644
|
+
});
|
|
6645
|
+
return { start: best.start, end: best.end, shadowedSeqs: shadowed, shadowedTokenCount };
|
|
6646
|
+
}
|
|
6438
6647
|
/** ACP is model-driven: automatic pressure policy never summarizes by itself. */
|
|
6439
6648
|
async compactIfNeeded(_agent, _trigger, signal) {
|
|
6440
6649
|
signal.throwIfAborted();
|