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/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
- return text.startsWith(SUMMARY_FRAME_PREFIX) ? text : `${SUMMARY_FRAME_PREFIX}
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.kind === "plugin" && source.plugin === "agent-instructions";
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
- if (kind === "plugin") {
3642
- if (source.plugin !== void 0 && METADATA_PLUGINS.has(source.plugin)) return "metadata";
3643
- if (source.plugin !== void 0 && REAL_CONTENT_PLUGINS.has(source.plugin)) return "real";
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 !== void 0 && HOST_INSTRUCTION_KINDS.has(kind)) return "instruction";
3647
- return "real";
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
- if (source?.plugin !== void 0 && REAL_CONTENT_PLUGINS.has(source.plugin)) return false;
3654
- return source?.kind !== "subagent-report" && source?.kind !== "subagent-settled";
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 source = event.data.source;
4040
- const compactionId = source?.plugin === "compact" ? source.compactionId : void 0;
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
- source: { kind: "plugin", plugin: "billion-context-dsh" }
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?.type !== "user/message") return null;
4440
- const source = event.data.source;
4441
- if (source?.plugin !== "compact" || source.compactionId === void 0) return null;
4442
- const entry = blockRegistry(session).find((r) => r.blockId === source.compactionId);
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?.type !== "user/message") return null;
4463
- const source = event.data.source;
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
- source: { kind: "plugin", plugin: "acp-nudge" }
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
- return resolved;
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 = filterSettingsEntry(config);
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();