billion-context-dsh 0.2.23 → 0.2.25

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
@@ -3832,21 +3832,19 @@ function assertNoActiveCompaction(events) {
3832
3832
  console.warn("billion-context-dsh: clearing stale compaction flag \u2014 found a compaction/start with no matching compaction/end");
3833
3833
  }
3834
3834
  }
3835
- function hasPlainRef(session, seq) {
3835
+ function anchorsRangeEdge(session, seq) {
3836
3836
  const event = eventAtOf(session, seq);
3837
3837
  if (event === void 0) return false;
3838
+ if (isSystemNode(event)) return false;
3838
3839
  switch (event.type) {
3839
3840
  case "user/message":
3840
- case "tool/result":
3841
3841
  return extractEventText(event).trim().length > 0;
3842
3842
  case "assistant/message": {
3843
3843
  const content = event.data.message?.content;
3844
- const calls = Array.isArray(content) ? content.filter(
3845
- (block) => block !== null && typeof block === "object" && block.type === "tool-call"
3846
- ) : [];
3847
- if (calls.length > 1) return false;
3848
- return calls.length === 1 || extractEventText(event).trim().length > 0;
3844
+ return toolCallsOf(content).length > 0 || extractText(content).trim().length > 0;
3849
3845
  }
3846
+ case "tool/result":
3847
+ return true;
3850
3848
  default:
3851
3849
  return false;
3852
3850
  }
@@ -3917,12 +3915,18 @@ function resolveSurfaceRange(session, start, end) {
3917
3915
  throw new Error(`billion-context-dsh: reversed range ${start}..${end}`);
3918
3916
  }
3919
3917
  const cleanBefore = (index) => {
3920
- const event = eventAtOf(session, nodes[index]);
3921
- return event !== void 0 && !isSystemNode(event) && toolPairingBalancedBefore(session, nodes[index]) && hasPlainRef(session, nodes[index]);
3918
+ const node = nodes[index];
3919
+ const event = eventAtOf(session, node);
3920
+ if (event === void 0 || isSystemNode(event)) return false;
3921
+ if (!toolPairingBalancedBefore(session, node)) return false;
3922
+ return anchorsRangeEdge(session, node);
3922
3923
  };
3923
3924
  const cleanAfter = (index) => {
3924
- const event = eventAtOf(session, nodes[index]);
3925
- return event !== void 0 && !isSystemNode(event) && toolPairingBalancedAfter(session, nodes[index]) && hasPlainRef(session, nodes[index]);
3925
+ const node = nodes[index];
3926
+ const event = eventAtOf(session, node);
3927
+ if (event === void 0 || isSystemNode(event)) return false;
3928
+ if (!toolPairingBalancedAfter(session, node)) return false;
3929
+ return anchorsRangeEdge(session, node);
3926
3930
  };
3927
3931
  let startIdx = requestedStartIdx;
3928
3932
  let endIdx = requestedEndIdx;
@@ -4285,7 +4289,7 @@ function guardedSurfaceSeqsOf(session) {
4285
4289
  function seqOfKernelRef(refs, ref) {
4286
4290
  const id = refs.byRef[ref];
4287
4291
  if (id === void 0) return null;
4288
- const seq = Number(id);
4292
+ const seq = Number(String(id).split("#")[0]);
4289
4293
  return Number.isInteger(seq) ? seq : null;
4290
4294
  }
4291
4295
  function protectedSurfaceSeqs(session, preserve) {
@@ -5018,7 +5022,7 @@ function windowSourceLabel(window) {
5018
5022
  if (window.source === "auto") {
5019
5023
  return `auto-detected from ${window.provider ?? "?"}/${window.model ?? "?"}`;
5020
5024
  }
5021
- if (window.probeFailed === true) return "default (auto-detection failed \u2014 see /acp config)";
5025
+ if (window.probeFailed === true) return "default (auto-detection failed \u2014 see /acp-prune config)";
5022
5026
  return "default (auto-detection unavailable)";
5023
5027
  }
5024
5028
  function projectedContextWindow(agent) {
@@ -5241,6 +5245,39 @@ function protectedRowRejectionNote(start, end, hits, shadowed) {
5241
5245
  const recovery = slices.length === 0 ? "no part of this span is compressible while those rows are current \u2014 pick an OLDER span instead (acp_status lists the live ranges)" : `the compressible part of this span is seq ${slices.join(" and ")} \u2014 submit them as separate content entries (or two compress calls), each with its own summary`;
5242
5246
  return ` seqs ${start}..${end} rejected \u2014 the span covers ${hits.length} CURRENT injected instruction row(s) (seq ${preview}${more}); the host re-injects the newest AGENTS.md copy the moment it leaves the surface, so compressing it reclaims nothing \u2014 ${recovery} (older/stale copies of the same file are fine to compress)`;
5243
5247
  }
5248
+ function edgeRefForSeq(session, byRaw, seq, role, oppositeSeq) {
5249
+ const nodes = session.surface.nodes;
5250
+ let index = -1;
5251
+ let oppositeIndex = -1;
5252
+ for (let i = 0; i < nodes.length; i += 1) {
5253
+ if (nodes[i] === seq) index = i;
5254
+ if (nodes[i] === oppositeSeq) oppositeIndex = i;
5255
+ }
5256
+ if (index < 0 || oppositeIndex < 0) return void 0;
5257
+ const direct = anchorRefForNode(session, byRaw, seq, role);
5258
+ if (direct !== void 0) return direct;
5259
+ const step = role === "start" ? 1 : -1;
5260
+ for (let i = index + step; i !== oppositeIndex + step; i += step) {
5261
+ const ref = anchorRefForNode(session, byRaw, nodes[i], role);
5262
+ if (ref !== void 0) return ref;
5263
+ }
5264
+ return void 0;
5265
+ }
5266
+ function anchorRefForNode(session, byRaw, seq, role) {
5267
+ const direct = byRaw[String(seq)];
5268
+ if (direct !== void 0) return direct;
5269
+ const event = eventAtOf(session, seq);
5270
+ if (event?.type !== "assistant/message") return void 0;
5271
+ const content = event.data.message?.content;
5272
+ const ids = toolCallsOf(content).map((call) => call.id ?? "");
5273
+ if (ids.length < 2) return void 0;
5274
+ const ordered = role === "start" ? ids : [...ids].reverse();
5275
+ for (const id of ordered) {
5276
+ const ref = byRaw[`${seq}#${id}`];
5277
+ if (ref !== void 0) return ref;
5278
+ }
5279
+ return void 0;
5280
+ }
5244
5281
  async function handleCompress(env, args, exec) {
5245
5282
  const agent = requireAgent(exec);
5246
5283
  const session = agent.session;
@@ -5292,8 +5329,8 @@ async function handleCompress(env, args, exec) {
5292
5329
  }
5293
5330
  const startBlockRef = blockRefForSummarySeq(session, resolved.start);
5294
5331
  const endBlockRef = blockRefForSummarySeq(session, resolved.end);
5295
- const startRef = startBlockRef ?? byRaw[String(resolved.start)];
5296
- const endRef = endBlockRef ?? byRaw[String(resolved.end)];
5332
+ const startRef = startBlockRef ?? edgeRefForSeq(session, byRaw, resolved.start, "start", resolved.end);
5333
+ const endRef = endBlockRef ?? edgeRefForSeq(session, byRaw, resolved.end, "end", resolved.start);
5297
5334
  if (startRef === void 0 || endRef === void 0) {
5298
5335
  throw new Error(
5299
5336
  `billion-context-dsh: seq ${resolved.start}..${resolved.end} has no assigned ref \u2014 the range must be on the current surface (run acp_status for the live seq list)`
@@ -5842,7 +5879,7 @@ async function statusText(env, agent) {
5842
5879
  );
5843
5880
  }
5844
5881
  if (window.probeFailed === true) {
5845
- 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)`);
5882
+ 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)`);
5846
5883
  }
5847
5884
  const state = structuredClone(env.store.stateFor(session));
5848
5885
  const config = kernelConfigFor({ ...env, modelContextLimit: limit });
@@ -5865,18 +5902,18 @@ async function statusText(env, agent) {
5865
5902
  }
5866
5903
  function compressText(env, agent, args) {
5867
5904
  if (args.length < 3) {
5868
- return "/acp compress <startSeq> <endSeq> <summary...>";
5905
+ return "/acp-prune compress <startSeq> <endSeq> <summary...>";
5869
5906
  }
5870
5907
  const startSeq = Number(args[0]);
5871
5908
  const endSeq = Number(args[1]);
5872
5909
  const summary = args.slice(2).join(" ");
5873
5910
  if (!Number.isInteger(startSeq) || !Number.isInteger(endSeq)) {
5874
- return "/acp compress: startSeq and endSeq must be integers";
5911
+ return "/acp-prune compress: startSeq and endSeq must be integers";
5875
5912
  }
5876
5913
  const session = agent.session;
5877
5914
  const { start, end } = resolveSurfaceRange(session, startSeq, endSeq);
5878
5915
  if (blockRefForSummarySeq(session, start) !== null || blockRefForSummarySeq(session, end) !== null) {
5879
- return "/acp compress: the range touches a compressed block summary node \u2014 distill it with the compress tool (seq-based batch), not /acp compress";
5916
+ 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";
5880
5917
  }
5881
5918
  const shadowed = shadowedSeqsOf(session, start, end);
5882
5919
  const instructionHits = guardedRowsInSpan(guardedSurfaceSeqsOf(session), shadowed);
@@ -5896,7 +5933,7 @@ function compressText(env, agent, args) {
5896
5933
  });
5897
5934
  return `Compressed seqs ${start}..${end} (${shadowed.length} messages) as block ${compactionId.slice(0, 8)}`;
5898
5935
  }
5899
- var DECOMPRESS_USAGE = "/acp decompress <blockId> [offset] [limit]";
5936
+ var DECOMPRESS_USAGE = "/acp-prune decompress <blockId> [offset] [limit]";
5900
5937
  function decompressText(_env, agent, args) {
5901
5938
  if (args.length < 1) return DECOMPRESS_USAGE;
5902
5939
  const offset = args[1] === void 0 ? 0 : Number(args[1]);
@@ -5907,7 +5944,7 @@ function decompressText(_env, agent, args) {
5907
5944
  const blockId = blockIdOfKernelRef(session, args[0]);
5908
5945
  const ledger = rebuildBlockLedger(sessionEventsOf(session));
5909
5946
  const block = blockId === null ? ledger.find((entry) => entry.blockId.startsWith(args[0])) : ledger.find((entry) => entry.blockId === blockId);
5910
- if (block === void 0) return `block "${args[0]}" not found (see /acp status)`;
5947
+ if (block === void 0) return `block "${args[0]}" not found (see /acp-prune status)`;
5911
5948
  const expanded = expandShadowedSeqs(session, block.blockId);
5912
5949
  const page = sliceDecompressPage(
5913
5950
  expanded,
@@ -5927,14 +5964,14 @@ function decompressText(_env, agent, args) {
5927
5964
  `Block ${block.blockId} \u2014 ${block.summary}`,
5928
5965
  `[messages ${page.offset + 1}..${page.offset + page.seqs.length} of ${page.total}]`
5929
5966
  ];
5930
- if (!page.exhausted) lines.push(`Continue with: /acp decompress ${block.blockId.slice(0, 8)} ${page.offset + page.seqs.length}`);
5967
+ if (!page.exhausted) lines.push(`Continue with: /acp-prune decompress ${block.blockId.slice(0, 8)} ${page.offset + page.seqs.length}`);
5931
5968
  lines.push("", parts.join("\n\n") || "(no recoverable content)");
5932
5969
  return lines.join("\n");
5933
5970
  }
5934
5971
  function acpCommand(env) {
5935
5972
  return {
5936
- name: "acp",
5937
- 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]",
5973
+ name: "acp-prune",
5974
+ 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]",
5938
5975
  handler: async (invocation) => {
5939
5976
  const raw = invocation.rawInput.trim();
5940
5977
  if (raw === "" || raw === "status") {
@@ -5949,7 +5986,7 @@ function acpCommand(env) {
5949
5986
  if (raw.startsWith("decompress")) {
5950
5987
  return { kind: "success", text: decompressText(env, invocation.agent, raw.slice("decompress".length).trim().split(/\s+/)) };
5951
5988
  }
5952
- return { kind: "error", text: `unknown /acp subcommand "${raw.split(/\s+/)[0]}" \u2014 use status | compress | decompress | config` };
5989
+ return { kind: "error", text: `unknown /acp-prune subcommand "${raw.split(/\s+/)[0]}" \u2014 use status | compress | decompress | config` };
5953
5990
  }
5954
5991
  };
5955
5992
  }
@@ -5961,7 +5998,7 @@ function isSettingsKey(key) {
5961
5998
  }
5962
5999
  function settingsWriteFailure(error) {
5963
6000
  if (error instanceof SettingsConflictError) {
5964
- return "conflict: another writer changed this setting at the same time \u2014 run /acp config again";
6001
+ return "conflict: another writer changed this setting at the same time \u2014 run /acp-prune config again";
5965
6002
  }
5966
6003
  return `rejected: ${String(error)}`;
5967
6004
  }
@@ -5989,7 +6026,7 @@ function configListText(surface) {
5989
6026
  }
5990
6027
  lines.push("", " changes apply to running sessions immediately (no restart)");
5991
6028
  lines.push(" coreOverrides (composition layer) merge LAST and beat these values on same-name keys");
5992
- lines.push(" /acp config reset <key> returns the key to the composition row / engine default");
6029
+ lines.push(" /acp-prune config reset <key> returns the key to the composition row / engine default");
5993
6030
  return lines.join("\n");
5994
6031
  }
5995
6032
  async function configSetText(surface, key, rawValue) {
@@ -6051,13 +6088,13 @@ async function configText(env, rest) {
6051
6088
  const verb = args[0] ?? "list";
6052
6089
  if (verb === "list") return configListText(surface);
6053
6090
  if (verb === "set") {
6054
- if (args.length < 3) return "usage: /acp config set <key> <value> (e.g. /acp config set nudgeMaxContextLimitPct 0.72)";
6091
+ if (args.length < 3) return "usage: /acp-prune config set <key> <value> (e.g. /acp-prune config set nudgeMaxContextLimitPct 0.72)";
6055
6092
  return configSetText(surface, args[1], args.slice(2).join(" "));
6056
6093
  }
6057
6094
  if (verb === "reset") {
6058
6095
  return configResetText(surface, args[1] ?? "all");
6059
6096
  }
6060
- return `unknown /acp config verb "${verb}" \u2014 use list | set <key> <value> | reset <key>|all`;
6097
+ return `unknown /acp-prune config verb "${verb}" \u2014 use list | set <key> <value> | reset <key>|all`;
6061
6098
  }
6062
6099
 
6063
6100
  // src/system-prompt.ts
@@ -6134,9 +6171,9 @@ var AcpCompactionEngine = class extends CompactionEngine {
6134
6171
  windowCache = /* @__PURE__ */ new Map();
6135
6172
  /** Live settings snapshot thunk (composition → user settings layer); swapped when the settings provider attaches (SettingsProvider.installSection). */
6136
6173
  readSettingsSource = () => resolveAcpSettings({});
6137
- /** The settings service, captured lazily for /acp config (undefined in provider-less processes). */
6174
+ /** The settings service, captured lazily for /acp-prune config (undefined in provider-less processes). */
6138
6175
  settingsService;
6139
- /** /acp config read/write surface. */
6176
+ /** /acp-prune config read/write surface. */
6140
6177
  settingsCommand;
6141
6178
  /** Per route the adapter's per-request output cap (the output reservation); null = undisclosed. */
6142
6179
  outputReservationCache = /* @__PURE__ */ new Map();
@@ -6186,7 +6223,7 @@ var AcpCompactionEngine = class extends CompactionEngine {
6186
6223
  kernel: this.kernel,
6187
6224
  store: this.store,
6188
6225
  // The settings-exposed knobs read LIVE from the settings source, so a
6189
- // settings.yaml edit (or /acp config set) hot-applies to every
6226
+ // settings.yaml edit (or /acp-prune config set) hot-applies to every
6190
6227
  // subsequent call — consumers never see stale numbers. (ToolEnvironment
6191
6228
  // fields are readonly properties; getters satisfy them.)
6192
6229
  get modelContextLimit() {
@@ -6203,7 +6240,7 @@ var AcpCompactionEngine = class extends CompactionEngine {
6203
6240
  },
6204
6241
  coreOverrides: this.config.coreOverrides,
6205
6242
  // Display-only: which named preset produced the thresholds above (if any),
6206
- // so /acp status can name it. The resolved pct values above are what the
6243
+ // so /acp-prune status can name it. The resolved pct values above are what the
6207
6244
  // kernel actually reads — this field never feeds kernelConfigFor.
6208
6245
  preset: this.config.preset,
6209
6246
  windowFor: (agent) => this.windowFor(agent),
@@ -6344,7 +6381,7 @@ var AcpCompactionEngine = class extends CompactionEngine {
6344
6381
  cap = probe.outputReservation;
6345
6382
  if (probe.contextWindow === null) {
6346
6383
  this.ctx.logger.warn(
6347
- `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)`
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-prune config \u2014 or restart \u2014 to re-probe)`
6348
6385
  );
6349
6386
  window = { limit: DEFAULT_CONTEXT_WINDOW, source: "default", provider, model, probeFailed: true };
6350
6387
  cap = null;