billion-context-pi 0.1.46 → 0.1.47-pr.216.12

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
@@ -324,7 +324,7 @@ function viableRanges(ranges) {
324
324
  return ranges.filter((r) => r.tokens >= VIABLE_RANGE_MIN_TOKENS);
325
325
  }
326
326
 
327
- // node_modules/acp-kernel/dist/chunk-DPH62BGM.js
327
+ // node_modules/acp-kernel/dist/chunk-UX4LINT7.js
328
328
  function createInitialState() {
329
329
  return {
330
330
  blocks: [],
@@ -337,7 +337,8 @@ function createInitialState() {
337
337
  anchors: {},
338
338
  lastShownByTier: {}
339
339
  },
340
- stats: { tokensCompressed: 0, compressionCount: 0 },
340
+ stats: { tokensCompressed: 0, compressionCount: 0, absorbedTokens: 0 },
341
+ absorbed: [],
341
342
  nextBlockId: 1,
342
343
  nextRunId: 1
343
344
  };
@@ -601,6 +602,7 @@ function syncBlocks(messages, state) {
601
602
  tokenSnapshot: { ...state.tokenSnapshot ?? {} },
602
603
  nudge: { ...state.nudge, anchors: { ...state.nudge.anchors } },
603
604
  stats: { ...state.stats },
605
+ absorbed: (state.absorbed ?? []).map((record) => ({ ...record })),
604
606
  nextBlockId: state.nextBlockId,
605
607
  nextRunId: state.nextRunId
606
608
  };
@@ -661,7 +663,14 @@ function defaultConfig(modelContextLimit, overrides = {}) {
661
663
  protectedTools: [],
662
664
  preserveRecentMessages: 5,
663
665
  preserveRecentTokens: 5e3,
664
- modelContextLimit
666
+ modelContextLimit,
667
+ absorb: {
668
+ enabled: false,
669
+ toolName: "absorb",
670
+ minToolTokens: 1e3,
671
+ contextThresholdPct: 0,
672
+ excludeTools: []
673
+ }
665
674
  };
666
675
  return {
667
676
  ...base,
@@ -669,7 +678,8 @@ function defaultConfig(modelContextLimit, overrides = {}) {
669
678
  tiers: { ...base.tiers, ...overrides.tiers },
670
679
  nudge: { ...base.nudge, ...overrides.nudge },
671
680
  truncate: { ...base.truncate, ...overrides.truncate },
672
- compress: { ...base.compress, ...overrides.compress }
681
+ compress: { ...base.compress, ...overrides.compress },
682
+ absorb: overrides.absorb ? { ...base.absorb, ...overrides.absorb } : base.absorb
673
683
  };
674
684
  }
675
685
  function validateConfig(config) {
@@ -699,6 +709,17 @@ function validateConfig(config) {
699
709
  if (config.tiers.tier3Trigger <= config.tiers.tier2Trigger) {
700
710
  errors.push("tiers.tier3Trigger must be greater than tiers.tier2Trigger");
701
711
  }
712
+ if (config.absorb) {
713
+ if (config.absorb.enabled && !config.absorb.toolName) {
714
+ errors.push("absorb.toolName must be a non-empty string when enabled");
715
+ }
716
+ if (!Number.isFinite(config.absorb.minToolTokens) || config.absorb.minToolTokens < 0) {
717
+ errors.push("absorb.minToolTokens must be >= 0");
718
+ }
719
+ if (config.absorb.contextThresholdPct < 0 || config.absorb.contextThresholdPct > 1) {
720
+ errors.push("absorb.contextThresholdPct must be in [0, 1]");
721
+ }
722
+ }
702
723
  return errors;
703
724
  }
704
725
  var MESSAGE_REF_PATTERN = /^m0*(\d{1,5})$/;
@@ -1055,6 +1076,306 @@ function hideConsumedCompressCalls(state, messages) {
1055
1076
  }
1056
1077
  return { messages: result, hidden };
1057
1078
  }
1079
+ var COMPRESS_TOOL_NAME = "compress";
1080
+ var DECOMPRESS_TOOL_NAME = "decompress";
1081
+ var SEARCH_CONTEXT_TOOL_NAME = "search_context";
1082
+ var ACP_STATUS_TOOL_NAME = "acp_status";
1083
+ var ABSORB_TOOL_NAME = "absorb";
1084
+ var COMPRESS_TOOL = {
1085
+ name: COMPRESS_TOOL_NAME,
1086
+ description: "Replace a contiguous range of older conversation with a detailed summary you write. Use when content is genuinely consumed. Batch form: content=[{startId,endId,summary,topic?}]. REQUIRED \u2014 compress without content is invalid.",
1087
+ input_schema: {
1088
+ type: "object",
1089
+ properties: {
1090
+ topic: {
1091
+ type: "string",
1092
+ description: "Optional short title for the compressed range"
1093
+ },
1094
+ content: {
1095
+ type: "array",
1096
+ description: "One or more ranges to compress into separate summary blocks",
1097
+ items: {
1098
+ type: "object",
1099
+ properties: {
1100
+ topic: { type: "string" },
1101
+ startId: {
1102
+ type: "string",
1103
+ description: "mNNNNN ref at the start of the range"
1104
+ },
1105
+ endId: {
1106
+ type: "string",
1107
+ description: "mNNNNN ref at the end of the range"
1108
+ },
1109
+ summary: {
1110
+ type: "string",
1111
+ description: "Self-contained summary replacing the range"
1112
+ }
1113
+ },
1114
+ required: ["startId", "endId", "summary"]
1115
+ }
1116
+ }
1117
+ },
1118
+ required: ["content"]
1119
+ }
1120
+ };
1121
+ var COMPRESS_TOOL_OPENAI = {
1122
+ type: "function",
1123
+ function: {
1124
+ name: COMPRESS_TOOL_NAME,
1125
+ description: COMPRESS_TOOL.description,
1126
+ parameters: {
1127
+ type: "object",
1128
+ properties: {
1129
+ topic: {
1130
+ type: "string",
1131
+ description: "Optional short title for the compressed range"
1132
+ },
1133
+ content: {
1134
+ type: "array",
1135
+ description: "One or more ranges to compress into separate summary blocks. REQUIRED \u2014 compress without content is invalid.",
1136
+ items: {
1137
+ type: "object",
1138
+ properties: {
1139
+ topic: { type: "string" },
1140
+ startId: {
1141
+ type: "string",
1142
+ description: "mNNNNN ref at the start of the range"
1143
+ },
1144
+ endId: {
1145
+ type: "string",
1146
+ description: "mNNNNN ref at the end of the range"
1147
+ },
1148
+ summary: {
1149
+ type: "string",
1150
+ description: "Self-contained summary replacing the range"
1151
+ }
1152
+ },
1153
+ required: ["startId", "endId", "summary"]
1154
+ }
1155
+ }
1156
+ },
1157
+ required: ["content"]
1158
+ }
1159
+ }
1160
+ };
1161
+ var DECOMPRESS_TOOL_OPENAI = {
1162
+ type: "function",
1163
+ function: {
1164
+ name: DECOMPRESS_TOOL_NAME,
1165
+ description: "Restores previously compressed content. Use when you need exact details lost in compression. By default restores one tier up. Use full:true for all the way to original messages. Use toFile to write to file instead of inflating context.",
1166
+ parameters: {
1167
+ type: "object",
1168
+ properties: {
1169
+ blockId: {
1170
+ type: "string",
1171
+ description: "Block ID to decompress (e.g. b5)"
1172
+ },
1173
+ toFile: {
1174
+ type: "string",
1175
+ description: "Optional: write content to file instead of context"
1176
+ },
1177
+ full: {
1178
+ type: "boolean",
1179
+ description: "Restore all the way to original messages"
1180
+ }
1181
+ },
1182
+ required: ["blockId"]
1183
+ }
1184
+ }
1185
+ };
1186
+ var SEARCH_CONTEXT_TOOL_OPENAI = {
1187
+ type: "function",
1188
+ function: {
1189
+ name: SEARCH_CONTEXT_TOOL_NAME,
1190
+ description: "Search through compressed block summaries by keyword. Use BEFORE decompressing to find the right block.",
1191
+ parameters: {
1192
+ type: "object",
1193
+ properties: {
1194
+ query: { type: "string", description: "Search query" },
1195
+ limit: { type: "number", description: "Max results (default 5)" }
1196
+ },
1197
+ required: ["query"]
1198
+ }
1199
+ }
1200
+ };
1201
+ var ACP_STATUS_TOOL_OPENAI = {
1202
+ type: "function",
1203
+ function: {
1204
+ name: ACP_STATUS_TOOL_NAME,
1205
+ description: "Show context usage and compressible ranges. No args = overview. Use to find what to compress next.",
1206
+ parameters: {
1207
+ type: "object",
1208
+ properties: {}
1209
+ }
1210
+ }
1211
+ };
1212
+ var DECOMPRESS_TOOL = {
1213
+ name: DECOMPRESS_TOOL_NAME,
1214
+ description: DECOMPRESS_TOOL_OPENAI.function.description,
1215
+ input_schema: DECOMPRESS_TOOL_OPENAI.function.parameters
1216
+ };
1217
+ var SEARCH_CONTEXT_TOOL = {
1218
+ name: SEARCH_CONTEXT_TOOL_NAME,
1219
+ description: SEARCH_CONTEXT_TOOL_OPENAI.function.description,
1220
+ input_schema: SEARCH_CONTEXT_TOOL_OPENAI.function.parameters
1221
+ };
1222
+ var ACP_STATUS_TOOL = {
1223
+ name: ACP_STATUS_TOOL_NAME,
1224
+ description: ACP_STATUS_TOOL_OPENAI.function.description,
1225
+ input_schema: ACP_STATUS_TOOL_OPENAI.function.parameters
1226
+ };
1227
+ var COMPRESS_TOOL_RESPONSES = {
1228
+ type: "function",
1229
+ name: COMPRESS_TOOL_NAME,
1230
+ description: COMPRESS_TOOL.description,
1231
+ parameters: COMPRESS_TOOL_OPENAI.function.parameters
1232
+ };
1233
+ var DECOMPRESS_TOOL_RESPONSES = {
1234
+ type: "function",
1235
+ name: DECOMPRESS_TOOL_OPENAI.function.name,
1236
+ description: DECOMPRESS_TOOL_OPENAI.function.description,
1237
+ parameters: DECOMPRESS_TOOL_OPENAI.function.parameters
1238
+ };
1239
+ var SEARCH_CONTEXT_TOOL_RESPONSES = {
1240
+ type: "function",
1241
+ name: SEARCH_CONTEXT_TOOL_OPENAI.function.name,
1242
+ description: SEARCH_CONTEXT_TOOL_OPENAI.function.description,
1243
+ parameters: SEARCH_CONTEXT_TOOL_OPENAI.function.parameters
1244
+ };
1245
+ var ACP_STATUS_TOOL_RESPONSES = {
1246
+ type: "function",
1247
+ name: ACP_STATUS_TOOL_OPENAI.function.name,
1248
+ description: ACP_STATUS_TOOL_OPENAI.function.description,
1249
+ parameters: ACP_STATUS_TOOL_OPENAI.function.parameters
1250
+ };
1251
+ var ACP_TOOL_NAMES = /* @__PURE__ */ new Set([
1252
+ COMPRESS_TOOL_NAME,
1253
+ DECOMPRESS_TOOL_NAME,
1254
+ SEARCH_CONTEXT_TOOL_NAME,
1255
+ ACP_STATUS_TOOL_NAME
1256
+ ]);
1257
+ var ALWAYS_PROTECTED_TOOLS = ["compress"];
1258
+ var NEVER_PRESERVE_RECENT_TOOLS = [
1259
+ "decompress",
1260
+ "search_context",
1261
+ "read",
1262
+ "bash"
1263
+ ];
1264
+ function isNeverPreserveRecent(msg) {
1265
+ if (msg.contentType !== "tool-call" && msg.contentType !== "tool-result") {
1266
+ return false;
1267
+ }
1268
+ if (!msg.toolName) return false;
1269
+ return NEVER_PRESERVE_RECENT_TOOLS.includes(msg.toolName);
1270
+ }
1271
+ function matchToolPattern(toolName, pattern) {
1272
+ if (pattern.endsWith("*")) {
1273
+ return toolName.startsWith(pattern.slice(0, -1));
1274
+ }
1275
+ return toolName === pattern;
1276
+ }
1277
+ function isMessageProtected(msg, config) {
1278
+ if (msg.contentType !== "tool-call" && msg.contentType !== "tool-result" || !msg.toolName) {
1279
+ return false;
1280
+ }
1281
+ if (ALWAYS_PROTECTED_TOOLS.includes(msg.toolName)) {
1282
+ return true;
1283
+ }
1284
+ for (const pattern of config.protectedTools) {
1285
+ if (matchToolPattern(msg.toolName, pattern)) return true;
1286
+ }
1287
+ if (config.isToolProtected?.(msg.toolName, msg.text)) return true;
1288
+ return false;
1289
+ }
1290
+ function collectProtectedToolCallIds(messages, config) {
1291
+ const ids = /* @__PURE__ */ new Set();
1292
+ for (const m of messages) {
1293
+ if (m.contentType === "tool-call" && m.toolCallId && isMessageProtected(m, config)) {
1294
+ ids.add(m.toolCallId);
1295
+ }
1296
+ }
1297
+ return ids;
1298
+ }
1299
+ function isMessageProtectedWithPairing(msg, config, protectedCallIds) {
1300
+ if (isMessageProtected(msg, config)) return true;
1301
+ if (msg.contentType === "tool-result" && msg.toolCallId && protectedCallIds.has(msg.toolCallId)) {
1302
+ return true;
1303
+ }
1304
+ return false;
1305
+ }
1306
+ var ABSORB_PROMPT_MARKER = "[ACP absorb]";
1307
+ var DEFAULT_ABSORB_CONFIG = {
1308
+ enabled: false,
1309
+ toolName: ABSORB_TOOL_NAME,
1310
+ minToolTokens: 1e3,
1311
+ contextThresholdPct: 0,
1312
+ excludeTools: []
1313
+ };
1314
+ function resolveAbsorbConfig(config) {
1315
+ return { ...DEFAULT_ABSORB_CONFIG, ...config.absorb ?? {} };
1316
+ }
1317
+ function formatTokenCount(tokens) {
1318
+ if (tokens < 1e3) return String(tokens);
1319
+ if (tokens < 1e4) return (tokens / 1e3).toFixed(1) + "K";
1320
+ return Math.round(tokens / 1e3) + "K";
1321
+ }
1322
+ function buildAbsorbPrompt(ref, tokens, toolName = ABSORB_TOOL_NAME) {
1323
+ return `${ABSORB_PROMPT_MARKER} This tool result (~${formatTokenCount(tokens)} tokens) will be REMOVED from context. Your IMMEDIATE next action: call ${toolName}({ ref: "${ref}", summary: "..." }) \u2014 summary = distilled essentials only (outcome, key values, exact paths:lines, error text verbatim, decisions). Afterwards work from your summary; do NOT re-run this tool. If the result contains nothing you need, call ${toolName} with summary "(nothing needed)".`;
1324
+ }
1325
+ function isAcpOrConfiguredTool(toolName, cfg) {
1326
+ if (!toolName) return false;
1327
+ if (toolName === cfg.toolName) return true;
1328
+ return ACP_TOOL_NAMES.has(toolName);
1329
+ }
1330
+ function isAbsorbCandidate(msg, config) {
1331
+ if (msg.contentType !== "tool-result" || !msg.toolCallId) return false;
1332
+ const cfg = resolveAbsorbConfig(config);
1333
+ if (isAcpOrConfiguredTool(msg.toolName, cfg)) return false;
1334
+ if (isMessageProtected(msg, config)) return false;
1335
+ for (const pattern of cfg.excludeTools) {
1336
+ if (msg.toolName && matchToolPattern(msg.toolName, pattern)) return false;
1337
+ }
1338
+ return true;
1339
+ }
1340
+ function hideAbsorbedMessages(messages, state) {
1341
+ const records = state.absorbed ?? [];
1342
+ if (records.length === 0) return messages;
1343
+ const hidden = /* @__PURE__ */ new Set();
1344
+ for (const record of records) {
1345
+ if (record.callMessageId) hidden.add(record.callMessageId);
1346
+ if (record.resultMessageId) hidden.add(record.resultMessageId);
1347
+ }
1348
+ return messages.filter((msg) => !hidden.has(msg.id));
1349
+ }
1350
+ function appendAbsorbPrompts(messages, state, config, tokenCount, countTokens) {
1351
+ const cfg = resolveAbsorbConfig(config);
1352
+ if (!cfg.enabled) return { messages, promptedCount: 0 };
1353
+ const limit = config.modelContextLimit;
1354
+ if (cfg.contextThresholdPct > 0 && limit > 0 && tokenCount < cfg.contextThresholdPct * limit) {
1355
+ return { messages, promptedCount: 0 };
1356
+ }
1357
+ const absorbedIds = /* @__PURE__ */ new Set();
1358
+ for (const record of state.absorbed ?? []) {
1359
+ if (record.resultMessageId) absorbedIds.add(record.resultMessageId);
1360
+ }
1361
+ let promptedCount = 0;
1362
+ const out = messages.map((msg) => {
1363
+ if (!isAbsorbCandidate(msg, config)) return msg;
1364
+ if (absorbedIds.has(msg.id)) return msg;
1365
+ const text = msg.text ?? "";
1366
+ if (text.includes(ABSORB_PROMPT_MARKER)) return msg;
1367
+ const tokens = countTokens(text);
1368
+ if (tokens < cfg.minToolTokens) return msg;
1369
+ const ref = refForRaw(state.messageRefs, msg.id);
1370
+ if (!ref || ref === BLOCKED_REF) return msg;
1371
+ promptedCount++;
1372
+ return {
1373
+ ...msg,
1374
+ text: text + "\n\n" + buildAbsorbPrompt(ref, tokens, cfg.toolName)
1375
+ };
1376
+ });
1377
+ return { messages: out, promptedCount };
1378
+ }
1058
1379
  var registry = /* @__PURE__ */ new Map();
1059
1380
  function listMessageFilters() {
1060
1381
  return [...registry.values()];
@@ -1205,55 +1526,6 @@ function createRenderRefsNode(strategy) {
1205
1526
  };
1206
1527
  }
1207
1528
  var renderRefsNode = createRenderRefsNode("all");
1208
- var ALWAYS_PROTECTED_TOOLS = ["compress"];
1209
- var NEVER_PRESERVE_RECENT_TOOLS = [
1210
- "decompress",
1211
- "search_context",
1212
- "read",
1213
- "bash"
1214
- ];
1215
- function isNeverPreserveRecent(msg) {
1216
- if (msg.contentType !== "tool-call" && msg.contentType !== "tool-result") {
1217
- return false;
1218
- }
1219
- if (!msg.toolName) return false;
1220
- return NEVER_PRESERVE_RECENT_TOOLS.includes(msg.toolName);
1221
- }
1222
- function matchToolPattern(toolName, pattern) {
1223
- if (pattern.endsWith("*")) {
1224
- return toolName.startsWith(pattern.slice(0, -1));
1225
- }
1226
- return toolName === pattern;
1227
- }
1228
- function isMessageProtected(msg, config) {
1229
- if (msg.contentType !== "tool-call" && msg.contentType !== "tool-result" || !msg.toolName) {
1230
- return false;
1231
- }
1232
- if (ALWAYS_PROTECTED_TOOLS.includes(msg.toolName)) {
1233
- return true;
1234
- }
1235
- for (const pattern of config.protectedTools) {
1236
- if (matchToolPattern(msg.toolName, pattern)) return true;
1237
- }
1238
- if (config.isToolProtected?.(msg.toolName, msg.text)) return true;
1239
- return false;
1240
- }
1241
- function collectProtectedToolCallIds(messages, config) {
1242
- const ids = /* @__PURE__ */ new Set();
1243
- for (const m of messages) {
1244
- if (m.contentType === "tool-call" && m.toolCallId && isMessageProtected(m, config)) {
1245
- ids.add(m.toolCallId);
1246
- }
1247
- }
1248
- return ids;
1249
- }
1250
- function isMessageProtectedWithPairing(msg, config, protectedCallIds) {
1251
- if (isMessageProtected(msg, config)) return true;
1252
- if (msg.contentType === "tool-result" && msg.toolCallId && protectedCallIds.has(msg.toolCallId)) {
1253
- return true;
1254
- }
1255
- return false;
1256
- }
1257
1529
  function adjustBoundariesForToolPairs(startIndex, endIndex, messages, maxScan = 20) {
1258
1530
  const callIdsInRange = /* @__PURE__ */ new Set();
1259
1531
  for (let i = startIndex; i <= endIndex; i++) {
@@ -1628,7 +1900,7 @@ function createCore(ports = {}) {
1628
1900
  if (!hasBlockBoundaryRange && totalRangeChars < input.config.compress.minCompressRange) {
1629
1901
  const live = activeBlocks(state).map((b) => b.blockId).sort((x, y) => numericBlockId(x) - numericBlockId(y));
1630
1902
  const liveHint = live.length > 0 ? ` Current active blocks span ${live[0]}..${live[live.length - 1]} \u2014 retry with startId/endId set to active block IDs in that span.` : "";
1631
- const gateMessage = resolvableCount === 0 && consumedRanges.length === 0 && unknownCount > 0 ? `None of the ${input.ranges.length} requested range(s) resolved \u2014 every ref failed with "does not exist in this session". Refs recorded before an earlier compress are stale: each successful compress renumbers the remaining refs. Run acp_status, then re-issue the compress in the same turn using only the refs it reports.` : consumedRanges.length > 0 ? `Requested range(s) already compressed (e.g. ${consumedRanges[0].startRef}..${consumedRanges[0].endRef}); remaining compressible content ${totalRangeChars} chars < min ${input.config.compress.minCompressRange}. Nothing to do.${liveHint}` : `Total compressible content too small (${totalRangeChars} chars across ${countedRanges} range(s), min ${input.config.compress.minCompressRange}). Combine more messages into your range(s) to meet the threshold.`;
1903
+ const gateMessage = resolvableCount === 0 && consumedRanges.length === 0 && unknownCount > 0 ? `None of the ${input.ranges.length} requested range(s) resolved \u2014 every ref failed with "does not exist in this session". Refs recorded before an earlier compress are stale: each successful compress renumbers the remaining refs. Run acp_status, then call the compress tool again using only the refs it reports.` : consumedRanges.length > 0 ? `Requested range(s) already compressed (e.g. ${consumedRanges[0].startRef}..${consumedRanges[0].endRef}) \u2014 your refs are stale: a prior compress renumbered the remaining refs, so this range now falls inside an active block. Run acp_status, then call the compress tool again using only the CURRENT compressible ranges it reports.${liveHint}` : `Total compressible content too small (${totalRangeChars} chars across ${countedRanges} range(s), min ${input.config.compress.minCompressRange}). Combine more messages into your range(s) to meet the threshold.`;
1632
1904
  return {
1633
1905
  state: input.state,
1634
1906
  result: {
@@ -1746,6 +2018,8 @@ function createCore(ports = {}) {
1746
2018
  assignRefsNode,
1747
2019
  syncBlocksNode,
1748
2020
  pruneNode,
2021
+ absorbHideNode,
2022
+ absorbPromptNode,
1749
2023
  filterNode,
1750
2024
  hideCompressCallsNode,
1751
2025
  recommendNode,
@@ -1791,6 +2065,31 @@ var pruneNode = {
1791
2065
  return { ...io, messages: prune(io.messages, io.state) };
1792
2066
  }
1793
2067
  };
2068
+ var absorbHideNode = {
2069
+ name: "absorb-hide",
2070
+ enabled: (io) => (io.state.absorbed?.length ?? 0) > 0,
2071
+ run(io) {
2072
+ return { ...io, messages: hideAbsorbedMessages(io.messages, io.state) };
2073
+ }
2074
+ };
2075
+ var absorbPromptNode = {
2076
+ name: "absorb-prompt",
2077
+ enabled: (_io, ctx) => ctx.config.absorb?.enabled === true,
2078
+ run(io, ctx) {
2079
+ const applied = appendAbsorbPrompts(
2080
+ io.messages,
2081
+ io.state,
2082
+ ctx.config,
2083
+ ctx.tokenCount,
2084
+ ctx.countTokens
2085
+ );
2086
+ return {
2087
+ ...io,
2088
+ messages: applied.messages,
2089
+ effects: { ...io.effects, absorbPromptedCount: applied.promptedCount }
2090
+ };
2091
+ }
2092
+ };
1794
2093
  var filterNode = {
1795
2094
  name: "filter",
1796
2095
  enabled: (_io, ctx) => !!ctx.config.messageFilters?.enabled && listMessageFilters().length > 0,
@@ -2328,6 +2627,7 @@ function cloneState(state) {
2328
2627
  tokenSnapshot: { ...state.tokenSnapshot ?? {} },
2329
2628
  nudge: { ...state.nudge, anchors: { ...state.nudge.anchors } },
2330
2629
  stats: { ...state.stats },
2630
+ absorbed: (state.absorbed ?? []).map((record) => ({ ...record })),
2331
2631
  nextBlockId: state.nextBlockId,
2332
2632
  nextRunId: state.nextRunId
2333
2633
  };
@@ -2544,14 +2844,14 @@ function renderUncompressedRanges(visible) {
2544
2844
  };
2545
2845
  const merged = [];
2546
2846
  for (const m of visible) {
2547
- const num = refNum2(m.ref);
2847
+ const num2 = refNum2(m.ref);
2548
2848
  const last = merged[merged.length - 1];
2549
- if (last && num === last.startNum + last.count) {
2849
+ if (last && num2 === last.startNum + last.count) {
2550
2850
  last.endRef = m.ref;
2551
2851
  last.count += 1;
2552
2852
  last.tokens += m.tokens;
2553
2853
  } else {
2554
- merged.push({ startRef: m.ref, endRef: m.ref, startNum: num, count: 1, tokens: m.tokens, tool: m.tool });
2854
+ merged.push({ startRef: m.ref, endRef: m.ref, startNum: num2, count: 1, tokens: m.tokens, tool: m.tool });
2555
2855
  }
2556
2856
  }
2557
2857
  for (const r of merged.slice(0, 30)) {
@@ -2618,6 +2918,296 @@ function renderCompressedDrilldown(blocks, state, sort, limit, countTokens) {
2618
2918
  }
2619
2919
  return lines.join("\n");
2620
2920
  }
2921
+ function parseCompressArgs(input, opts) {
2922
+ const callId = opts?.callId;
2923
+ const diag = { ok: false, kind: "ok", invalidItems: 0 };
2924
+ if (input === null || input === void 0) {
2925
+ diag.kind = "empty-input";
2926
+ return finish([], diag);
2927
+ }
2928
+ if (typeof input === "string") {
2929
+ return parseStringInput(input, callId, diag);
2930
+ }
2931
+ if (typeof input !== "object" || Array.isArray(input)) {
2932
+ diag.kind = "not-object";
2933
+ return finish([], diag);
2934
+ }
2935
+ return parseObjectValue(input, callId, diag);
2936
+ }
2937
+ function parseStringInput(raw, callId, diag) {
2938
+ diag.rawPrefix = raw.slice(0, 800);
2939
+ diag.length = raw.length;
2940
+ const cleaned = stripFence(raw.trim());
2941
+ if (cleaned === "") {
2942
+ diag.kind = "empty-input";
2943
+ return finish([], diag);
2944
+ }
2945
+ let value = tryParseLenient(cleaned);
2946
+ if (typeof value === "string") {
2947
+ const inner = tryParseLenient(stripFence(value));
2948
+ if (inner !== void 0) value = inner;
2949
+ }
2950
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
2951
+ return parseObjectValue(value, callId, diag);
2952
+ }
2953
+ if (value !== void 0) {
2954
+ diag.kind = "not-object";
2955
+ return finish([], diag);
2956
+ }
2957
+ const entries = salvageContentEntries(cleaned);
2958
+ return finishSalvage(entries, callId, diag, looksTruncated(cleaned));
2959
+ }
2960
+ function parseObjectValue(value, callId, diag) {
2961
+ diag.keys = Object.keys(value);
2962
+ const content = value["content"];
2963
+ if (content === void 0) {
2964
+ const single = validateEntry(value, callId);
2965
+ if (single) {
2966
+ diag.kind = "ok";
2967
+ return finish([single], diag);
2968
+ }
2969
+ diag.kind = "missing-content";
2970
+ return finish([], diag);
2971
+ }
2972
+ let entries;
2973
+ let salvaged = false;
2974
+ if (Array.isArray(content)) {
2975
+ entries = content;
2976
+ } else if (typeof content === "string") {
2977
+ const parsed = parseContentArray(content);
2978
+ if (parsed === null) {
2979
+ diag.kind = "content-not-array";
2980
+ return finish([], diag);
2981
+ }
2982
+ entries = parsed.entries;
2983
+ salvaged = parsed.salvaged;
2984
+ } else {
2985
+ diag.kind = "content-not-array";
2986
+ return finish([], diag);
2987
+ }
2988
+ const { ranges, invalid } = validateEntries(entries, callId);
2989
+ const topTopic = stringOr(value["topic"]);
2990
+ const topMaxChars = value["summaryMaxChars"];
2991
+ const hasTopMaxChars = typeof topMaxChars === "number" && Number.isFinite(topMaxChars);
2992
+ if (topTopic !== void 0 || hasTopMaxChars) {
2993
+ for (const r of ranges) {
2994
+ if (r.topic === void 0 && topTopic !== void 0) r.topic = topTopic;
2995
+ if (r.summaryMaxChars === void 0 && hasTopMaxChars) r.summaryMaxChars = topMaxChars;
2996
+ }
2997
+ }
2998
+ diag.invalidItems = invalid;
2999
+ diag.kind = salvaged ? "truncated" : ranges.length > 0 ? "ok" : "no-valid-ranges";
3000
+ return finish(ranges, diag);
3001
+ }
3002
+ function parseContentArray(s) {
3003
+ const cleaned = stripFence(s.trim());
3004
+ let value = cleaned === "" ? void 0 : tryParseLenient(cleaned);
3005
+ if (typeof value === "string") {
3006
+ value = tryParseLenient(stripFence(value));
3007
+ }
3008
+ if (Array.isArray(value)) {
3009
+ return { entries: value, salvaged: false };
3010
+ }
3011
+ if (value === void 0) {
3012
+ const entries = salvageContentEntries('{"content": ' + cleaned);
3013
+ return { entries, salvaged: entries.length > 0 };
3014
+ }
3015
+ return null;
3016
+ }
3017
+ function finish(ranges, diag) {
3018
+ diag.ok = ranges.length > 0;
3019
+ return { ranges, diagnostics: diag };
3020
+ }
3021
+ function finishSalvage(entries, callId, diag, truncatedShape) {
3022
+ const { ranges, invalid } = validateEntries(entries, callId);
3023
+ diag.invalidItems = invalid;
3024
+ diag.kind = entries.length > 0 || truncatedShape ? "truncated" : "malformed-json";
3025
+ return finish(ranges, diag);
3026
+ }
3027
+ function validateEntries(entries, callId) {
3028
+ const ranges = [];
3029
+ let invalid = 0;
3030
+ for (const entry of entries) {
3031
+ const range = validateEntry(entry, callId);
3032
+ if (range !== null) ranges.push(range);
3033
+ else invalid++;
3034
+ }
3035
+ return { ranges, invalid };
3036
+ }
3037
+ function validateEntry(entry, callId) {
3038
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return null;
3039
+ const e = entry;
3040
+ const start = stringOr(e["startRef"]) ?? stringOr(e["startId"]) ?? stringOr(e["messageId"]);
3041
+ const end = stringOr(e["endRef"]) ?? stringOr(e["endId"]) ?? stringOr(e["messageId"]);
3042
+ if (start === void 0 || end === void 0) return null;
3043
+ const summary = stringOr(e["summary"]);
3044
+ if (summary === void 0) return null;
3045
+ const range = { startRef: start, endRef: end, summary };
3046
+ const topic = stringOr(e["topic"]);
3047
+ if (topic !== void 0) range.topic = topic;
3048
+ const maxChars = e["summaryMaxChars"];
3049
+ if (typeof maxChars === "number" && Number.isFinite(maxChars)) range.summaryMaxChars = maxChars;
3050
+ if (callId !== void 0) range.compressCallId = callId;
3051
+ return range;
3052
+ }
3053
+ function stringOr(value) {
3054
+ return typeof value === "string" && value.length > 0 ? value : void 0;
3055
+ }
3056
+ function tryParseLenient(s) {
3057
+ if (s === "") return void 0;
3058
+ try {
3059
+ return JSON.parse(s);
3060
+ } catch {
3061
+ }
3062
+ const noTrailingCommas = stripTrailingCommas(s);
3063
+ if (noTrailingCommas !== s) {
3064
+ try {
3065
+ return JSON.parse(noTrailingCommas);
3066
+ } catch {
3067
+ }
3068
+ }
3069
+ const fixed = escapeRawNewlinesInStrings(noTrailingCommas);
3070
+ if (fixed !== noTrailingCommas) {
3071
+ try {
3072
+ return JSON.parse(fixed);
3073
+ } catch {
3074
+ }
3075
+ }
3076
+ return void 0;
3077
+ }
3078
+ function stripFence(s) {
3079
+ if (!s.startsWith("```")) return s;
3080
+ const firstNewline = s.indexOf("\n");
3081
+ if (firstNewline === -1) return s;
3082
+ const bodyStart = firstNewline + 1;
3083
+ const end = s.lastIndexOf("```");
3084
+ return end > bodyStart ? s.slice(bodyStart, end).trim() : s.slice(bodyStart).trim();
3085
+ }
3086
+ function stripTrailingCommas(s) {
3087
+ let out = "";
3088
+ let inString = false;
3089
+ let escaped = false;
3090
+ for (let i = 0; i < s.length; i++) {
3091
+ const ch = s.charAt(i);
3092
+ if (inString) {
3093
+ out += ch;
3094
+ if (escaped) escaped = false;
3095
+ else if (ch === "\\") escaped = true;
3096
+ else if (ch === '"') inString = false;
3097
+ continue;
3098
+ }
3099
+ if (ch === '"') {
3100
+ inString = true;
3101
+ out += ch;
3102
+ continue;
3103
+ }
3104
+ if (ch === ",") {
3105
+ let j = i + 1;
3106
+ while (j < s.length && (s.charAt(j) === " " || s.charAt(j) === " " || s.charAt(j) === "\n" || s.charAt(j) === "\r")) j++;
3107
+ if (j < s.length && (s.charAt(j) === "}" || s.charAt(j) === "]")) continue;
3108
+ }
3109
+ out += ch;
3110
+ }
3111
+ return out;
3112
+ }
3113
+ function escapeRawNewlinesInStrings(s) {
3114
+ let out = "";
3115
+ let inString = false;
3116
+ let escaped = false;
3117
+ for (let i = 0; i < s.length; i++) {
3118
+ const ch = s.charAt(i);
3119
+ if (!inString) {
3120
+ if (ch === '"') inString = true;
3121
+ out += ch;
3122
+ continue;
3123
+ }
3124
+ if (escaped) {
3125
+ out += ch;
3126
+ escaped = false;
3127
+ continue;
3128
+ }
3129
+ if (ch === "\\") {
3130
+ out += ch;
3131
+ escaped = true;
3132
+ continue;
3133
+ }
3134
+ if (ch === "\n") {
3135
+ out += "\\n";
3136
+ continue;
3137
+ }
3138
+ if (ch === "\r") {
3139
+ out += "\\r";
3140
+ continue;
3141
+ }
3142
+ if (ch === " ") {
3143
+ out += "\\t";
3144
+ continue;
3145
+ }
3146
+ if (ch === '"') inString = false;
3147
+ out += ch;
3148
+ }
3149
+ return out;
3150
+ }
3151
+ function looksTruncated(s) {
3152
+ let depth = 0;
3153
+ let inString = false;
3154
+ let escaped = false;
3155
+ for (let i = 0; i < s.length; i++) {
3156
+ const ch = s.charAt(i);
3157
+ if (inString) {
3158
+ if (escaped) escaped = false;
3159
+ else if (ch === "\\") escaped = true;
3160
+ else if (ch === '"') inString = false;
3161
+ continue;
3162
+ }
3163
+ if (ch === '"') {
3164
+ inString = true;
3165
+ continue;
3166
+ }
3167
+ if (ch === "{" || ch === "[") depth++;
3168
+ else if (ch === "}" || ch === "]") depth--;
3169
+ }
3170
+ return depth > 0 || inString;
3171
+ }
3172
+ function salvageContentEntries(raw) {
3173
+ const match = /"content"\s*:\s*\[/.exec(raw);
3174
+ if (match === null) return [];
3175
+ const arrayStart = match.index + match[0].length - 1;
3176
+ const entries = [];
3177
+ let depth = 0;
3178
+ let inString = false;
3179
+ let escaped = false;
3180
+ let entryStart = -1;
3181
+ for (let i = arrayStart + 1; i < raw.length; i++) {
3182
+ const ch = raw.charAt(i);
3183
+ if (inString) {
3184
+ if (escaped) escaped = false;
3185
+ else if (ch === "\\") escaped = true;
3186
+ else if (ch === '"') inString = false;
3187
+ continue;
3188
+ }
3189
+ if (ch === '"') {
3190
+ inString = true;
3191
+ continue;
3192
+ }
3193
+ if (ch === "{" || ch === "[") {
3194
+ if (depth === 0 && ch === "{" && entryStart === -1) entryStart = i;
3195
+ depth++;
3196
+ continue;
3197
+ }
3198
+ if (ch === "}" || ch === "]") {
3199
+ depth--;
3200
+ if (depth < 0) break;
3201
+ if (depth === 0 && entryStart !== -1) {
3202
+ const entrySlice = raw.slice(entryStart, i + 1);
3203
+ entryStart = -1;
3204
+ const parsed = tryParseLenient(entrySlice);
3205
+ if (parsed !== void 0) entries.push(parsed);
3206
+ }
3207
+ }
3208
+ }
3209
+ return entries;
3210
+ }
2621
3211
  function stem(word) {
2622
3212
  let w = word;
2623
3213
  if (w.length <= 3) return w;
@@ -8668,25 +9258,24 @@ function makeCompressTool(runtime) {
8668
9258
  }
8669
9259
  };
8670
9260
  }
8671
- function normalizeRanges(content) {
8672
- let ranges = content ?? [];
8673
- if (typeof ranges === "string") {
8674
- try {
8675
- ranges = JSON.parse(ranges);
8676
- } catch (e) {
8677
- return `Invalid content: not valid JSON (${e instanceof Error ? e.message : String(e)}). content must be an ARRAY of {startId, endId, summary} objects \u2014 pass the array directly, not a string.`;
8678
- }
9261
+ function normalizeRanges(args) {
9262
+ const { ranges, diagnostics } = parseCompressArgs(args);
9263
+ if (ranges.length === 0) {
9264
+ if (Array.isArray(args.content) && args.content.length === 0) return [];
9265
+ return describeDiagnostics(diagnostics, args.content);
8679
9266
  }
8680
- if (!Array.isArray(ranges)) {
8681
- return `Invalid content: expected an array of ranges, got ${ranges === null ? "null" : typeof ranges}.`;
9267
+ return ranges.map((r) => ({ startId: r.startRef, endId: r.endRef, summary: r.summary, topic: r.topic }));
9268
+ }
9269
+ function describeDiagnostics(diagnostics, content) {
9270
+ const shape = typeof content === "string" ? "a JSON-encoded string (non-strict-tool providers stringify array arguments)" : content === null ? "null" : `a ${typeof content}`;
9271
+ const base = `Invalid compress content (${diagnostics.kind}): got ${shape}`;
9272
+ if (diagnostics.kind === "truncated") {
9273
+ return `${base}; the input was truncated and no complete ranges could be recovered. Shorten the summary or split into smaller ranges.`;
8682
9274
  }
8683
- for (const [i, r] of ranges.entries()) {
8684
- const o = r;
8685
- if (!o || typeof o !== "object" || typeof o.startId !== "string" || typeof o.endId !== "string" || typeof o.summary !== "string") {
8686
- return `Invalid content[${i}]: each range must be an object with string fields startId, endId, summary.`;
8687
- }
9275
+ if (diagnostics.invalidItems > 0) {
9276
+ return `${base}; ${diagnostics.invalidItems} entr${diagnostics.invalidItems === 1 ? "y was" : "ies were"} dropped as invalid. Each range must be an object with string fields startId, endId, summary.`;
8688
9277
  }
8689
- return ranges;
9278
+ return `${base}. content must be an ARRAY of {startId, endId, summary} objects.`;
8690
9279
  }
8691
9280
  function compressPanelBlocks(text) {
8692
9281
  if (!text.trimStart().startsWith("\u25A3 ACP |")) return -1;
@@ -8713,7 +9302,7 @@ function tier3OnlyRewrite(newBlocks, allBlocks) {
8713
9302
  return spans;
8714
9303
  }
8715
9304
  async function handleCompress(args, runtime, ctx, toolCallId) {
8716
- const maybeRanges = normalizeRanges(args.content);
9305
+ const maybeRanges = normalizeRanges(args);
8717
9306
  if (typeof maybeRanges === "string") throw new Error(maybeRanges);
8718
9307
  const ranges = maybeRanges;
8719
9308
  if (ranges.length === 0) return "No ranges provided.";
@@ -9923,7 +10512,7 @@ function makeDelegateWaitTool(_pi) {
9923
10512
  return new Promise((resolve3) => {
9924
10513
  let settled = false;
9925
10514
  let timer2;
9926
- const finish = (result) => {
10515
+ const finish2 = (result) => {
9927
10516
  if (settled) return;
9928
10517
  settled = true;
9929
10518
  run.waiter = void 0;
@@ -9932,19 +10521,19 @@ function makeDelegateWaitTool(_pi) {
9932
10521
  resolve3(result);
9933
10522
  };
9934
10523
  const onAbort = () => {
9935
- finish({ details: void 0, content: [{ type: "text", text: `Aborted; delegate \`${args.runId}\` is still running in the background. A notification will be injected when it finishes.` }] });
10524
+ finish2({ details: void 0, content: [{ type: "text", text: `Aborted; delegate \`${args.runId}\` is still running in the background. A notification will be injected when it finishes.` }] });
9936
10525
  };
9937
10526
  run.waiter = () => {
9938
10527
  run.consumed = true;
9939
10528
  if (run.status === "cancelled") {
9940
- finish(buildWaitResult(run, `Delegate \`${run.runId}\` was cancelled (no result).${remainingLineForWait(run.runId)}`, displayMode));
10529
+ finish2(buildWaitResult(run, `Delegate \`${run.runId}\` was cancelled (no result).${remainingLineForWait(run.runId)}`, displayMode));
9941
10530
  return;
9942
10531
  }
9943
- finish(buildWaitResult(run, formatRunResult(run), displayMode));
10532
+ finish2(buildWaitResult(run, formatRunResult(run), displayMode));
9944
10533
  };
9945
10534
  signal?.addEventListener("abort", onAbort);
9946
10535
  timer2 = setTimeout(
9947
- () => finish({ details: void 0, content: [{ type: "text", text: `Failed: delegate \`${args.runId}\` result not ready after ${Math.round(timeoutMs / 1e3)}s. Do NOT keep waiting or retry \u2014 go do other work now. The run continues in the background and a completion notification (with the result file path) will be injected into the chat when it finishes.` }] }),
10536
+ () => finish2({ details: void 0, content: [{ type: "text", text: `Failed: delegate \`${args.runId}\` result not ready after ${Math.round(timeoutMs / 1e3)}s. Do NOT keep waiting or retry \u2014 go do other work now. The run continues in the background and a completion notification (with the result file path) will be injected into the chat when it finishes.` }] }),
9948
10537
  timeoutMs
9949
10538
  );
9950
10539
  });
@@ -10222,20 +10811,20 @@ function waitForChild(child, signal) {
10222
10811
  });
10223
10812
  const timer2 = setTimeout(() => {
10224
10813
  child.kill("SIGTERM");
10225
- finish({ code: null, stdout: "", stderr: stderrText, timedOut: true });
10814
+ finish2({ code: null, stdout: "", stderr: stderrText, timedOut: true });
10226
10815
  }, SYNC_TIMEOUT_MS);
10227
10816
  const onAbort = () => {
10228
10817
  clearTimeout(timer2);
10229
10818
  child.kill("SIGTERM");
10230
10819
  };
10231
10820
  signal?.addEventListener("abort", onAbort, { once: true });
10232
- function finish(r) {
10821
+ function finish2(r) {
10233
10822
  clearTimeout(timer2);
10234
10823
  signal?.removeEventListener("abort", onAbort);
10235
10824
  resolve3(r);
10236
10825
  }
10237
10826
  child.on("close", (code) => {
10238
- finish({
10827
+ finish2({
10239
10828
  code,
10240
10829
  stdout: Buffer.concat(stdoutChunks).toString("utf8").trim(),
10241
10830
  stderr: stderrText,
@@ -10243,7 +10832,7 @@ function waitForChild(child, signal) {
10243
10832
  });
10244
10833
  });
10245
10834
  child.on("error", (err) => {
10246
- finish({ code: null, stdout: "", stderr: err.message, timedOut: false });
10835
+ finish2({ code: null, stdout: "", stderr: err.message, timedOut: false });
10247
10836
  });
10248
10837
  });
10249
10838
  }
@@ -10448,6 +11037,37 @@ function formatCompactTokens2(count) {
10448
11037
  if (count < 1e7) return `${(count / 1e6).toFixed(1)}M`;
10449
11038
  return `${Math.round(count / 1e6)}M`;
10450
11039
  }
11040
+ function num(v) {
11041
+ return typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
11042
+ }
11043
+ function cacheHitStats(usages) {
11044
+ let cacheRead = 0;
11045
+ let billedPrompt = 0;
11046
+ let requests = 0;
11047
+ let last;
11048
+ for (const u of usages) {
11049
+ const read = num(u?.cacheRead);
11050
+ const write = num(u?.cacheWrite);
11051
+ const input = num(u?.input);
11052
+ if (read + write <= 0) continue;
11053
+ const total = read + write + input;
11054
+ if (total <= 0) continue;
11055
+ cacheRead += read;
11056
+ billedPrompt += total;
11057
+ requests += 1;
11058
+ last = read / total;
11059
+ }
11060
+ return {
11061
+ session: requests > 0 ? cacheRead / billedPrompt : void 0,
11062
+ last,
11063
+ requests,
11064
+ cacheRead,
11065
+ billedPrompt
11066
+ };
11067
+ }
11068
+ function formatHitRate(rate) {
11069
+ return `${(Math.max(0, Math.min(1, rate)) * 100).toFixed(1)}%`;
11070
+ }
10451
11071
  function bar(value, total, width = 20) {
10452
11072
  if (total === 0) return "";
10453
11073
  const filled = Math.max(0, Math.min(width, Math.round(value / total * width)));
@@ -10500,6 +11120,15 @@ function buildStatusPanel(input) {
10500
11120
  lines.push(` ${cat.label.padEnd(10)} ${b} ${String(pct2).padStart(3)}% ${fmt2(cat.value)}`);
10501
11121
  }
10502
11122
  }
11123
+ if (input.cacheUsages) {
11124
+ const cache2 = cacheHitStats(input.cacheUsages);
11125
+ if (cache2.requests > 0 && cache2.session !== void 0 && cache2.last !== void 0) {
11126
+ lines.push("");
11127
+ lines.push(
11128
+ `Prompt cache (provider-reported): ${formatHitRate(cache2.last)} last \xB7 ${formatHitRate(cache2.session)} session avg \u2014 ${fmt2(cache2.cacheRead)} of ${fmt2(cache2.billedPrompt)} billed prompt tokens served from cache (${cache2.requests} req)`
11129
+ );
11130
+ }
11131
+ }
10503
11132
  lines.push("");
10504
11133
  if (nudge) {
10505
11134
  if (nudge.shouldInject) {
@@ -10730,6 +11359,16 @@ function ensureSubagentAcpTools(settingsPath, options) {
10730
11359
  }
10731
11360
 
10732
11361
  // src/commands.ts
11362
+ function cacheUsageSamples(entries) {
11363
+ const out = [];
11364
+ for (const e of entries) {
11365
+ if (e.type !== "message") continue;
11366
+ const m = e.message;
11367
+ if (m?.role !== "assistant" || !m.usage) continue;
11368
+ out.push({ input: m.usage.input ?? 0, cacheRead: m.usage.cacheRead ?? 0, cacheWrite: m.usage.cacheWrite ?? 0 });
11369
+ }
11370
+ return out;
11371
+ }
10733
11372
  function makeCommands(runtime) {
10734
11373
  return [
10735
11374
  {
@@ -10816,7 +11455,7 @@ ${text}`);
10816
11455
  ];
10817
11456
  }
10818
11457
  async function statusReport(runtime, ctx) {
10819
- const { state, coreMessages } = await runtime.stateFor(ctx);
11458
+ const { state, coreMessages, entries } = await runtime.stateFor(ctx);
10820
11459
  const config = runtime.configFor(ctx);
10821
11460
  const realUsage = ctx.getContextUsage?.();
10822
11461
  const systemPromptText = getSystemPromptText(ctx);
@@ -10826,7 +11465,7 @@ async function statusReport(runtime, ctx) {
10826
11465
  const modelId = ctx.model?.id ?? "default";
10827
11466
  const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
10828
11467
  const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount: calibrateTokens(sentTokens, runtime.density.densityFor(modelId)) });
10829
- const versionStr = "0.1.46" ? `billion-context-pi@${"0.1.46"}` : void 0;
11468
+ const versionStr = "0.1.47" ? `billion-context-pi@${"0.1.47"}` : void 0;
10830
11469
  let text = buildStatusPanel({
10831
11470
  version: versionStr,
10832
11471
  tokenCount: sessionTokens,
@@ -10834,7 +11473,8 @@ async function statusReport(runtime, ctx) {
10834
11473
  state: turn.state,
10835
11474
  nudge: turn.nudge,
10836
11475
  modelContextLimit: config.modelContextLimit,
10837
- unprunedTokens: coreMessages.reduce((sum, m) => sum + defaultCountTokens(m.text ?? ""), 0)
11476
+ unprunedTokens: coreMessages.reduce((sum, m) => sum + defaultCountTokens(m.text ?? ""), 0),
11477
+ cacheUsages: cacheUsageSamples(entries ?? [])
10838
11478
  });
10839
11479
  const delegateUsage = getDelegateUsage();
10840
11480
  if (delegateUsage && delegateUsage.totalTokens > 0) {
@@ -11025,14 +11665,12 @@ function wireToolGuardrails(pi, runtime) {
11025
11665
  const fullPath = isBash ? event.details?.fullOutputPath : void 0;
11026
11666
  const timeoutSecs = isBash && event.isError ? detectBashTimeout(event.content) : void 0;
11027
11667
  let modified;
11028
- const max = runtime.adapter.toolOutputMaxBytes;
11029
- if (max !== void 0 && max > 0) {
11030
- const next = capToolOutput(event.content, max, fullPath);
11031
- if (next) {
11032
- modified = next;
11033
- debug.event("guardrail-output-cap", { max, hadPath: !!fullPath });
11034
- logWarn("guardrail", { event: "output-cap", max, hadPath: !!fullPath });
11035
- }
11668
+ const max = runtime.adapter.toolOutputMaxBytes ?? DEFAULT_TOOL_OUTPUT_MAX_BYTES;
11669
+ const next = capToolOutput(event.content, max, fullPath);
11670
+ if (next) {
11671
+ modified = next;
11672
+ debug.event("guardrail-output-cap", { max, hadPath: !!fullPath });
11673
+ logWarn("guardrail", { event: "output-cap", max, hadPath: !!fullPath });
11036
11674
  }
11037
11675
  if (timeoutSecs !== void 0) {
11038
11676
  modified = appendTimeoutNotice(modified ?? event.content, timeoutSecs);
@@ -11188,7 +11826,7 @@ async function autoInstallLatest(latest, extDirOverride) {
11188
11826
  "--no-fund",
11189
11827
  "--no-save"
11190
11828
  ];
11191
- const prevVersion = (await readPackageJson(join8(extDir, "package.json")))?.version ?? "0.1.46";
11829
+ const prevVersion = (await readPackageJson(join8(extDir, "package.json")))?.version ?? "0.1.47";
11192
11830
  const { code, stderr } = await runNpmImpl(installArgs(latest), { cwd: npmDir, timeout: 6e4 });
11193
11831
  if (code !== 0) {
11194
11832
  logWarn("update", {
@@ -11201,7 +11839,7 @@ async function autoInstallLatest(latest, extDirOverride) {
11201
11839
  }
11202
11840
  const verify = await verifyInstall(npmDir, latest);
11203
11841
  if (!verify.ok) {
11204
- const rollbackTo = SEMVER_RE.test(prevVersion) ? prevVersion : "0.1.46";
11842
+ const rollbackTo = SEMVER_RE.test(prevVersion) ? prevVersion : "0.1.47";
11205
11843
  logWarn("update", { event: "auto-install-verify-failed", latest, reason: verify.reason, rollbackTo });
11206
11844
  const rb = await runNpmImpl(installArgs(rollbackTo), { cwd: npmDir, timeout: 6e4 });
11207
11845
  logInfo("update", { event: "rollback", from: latest, to: rollbackTo, ok: rb.code === 0 });
@@ -11262,7 +11900,7 @@ async function checkForUpdate(autoUpdate, notify) {
11262
11900
  const runtimeVersion = await getRuntimeVersion();
11263
11901
  const latest = await fetchLatestVersion();
11264
11902
  if (!latest) return;
11265
- const current = runtimeVersion ?? "0.1.46";
11903
+ const current = runtimeVersion ?? "0.1.47";
11266
11904
  const hasUpdate = isNewer(latest, current);
11267
11905
  debug.event("update-check", {
11268
11906
  current,
@@ -11303,6 +11941,10 @@ async function getRuntimeVersion() {
11303
11941
  // src/index.ts
11304
11942
  function createAcpExtension(adapter = {}) {
11305
11943
  return (pi) => {
11944
+ if (process.env.BILLION_CONTEXT_PROXY) {
11945
+ console.log("[bcp] disabled: BILLION_CONTEXT_PROXY detected \u2014 proxy handles compression");
11946
+ return;
11947
+ }
11306
11948
  const runtime = createRuntime(adapter);
11307
11949
  wireCompactionDisable(pi);
11308
11950
  wireSessionLifecycle(pi, runtime);
@@ -11337,7 +11979,7 @@ function wireSessionLifecycle(pi, runtime) {
11337
11979
  const sid = ctx.sessionManager.getSessionId();
11338
11980
  runtime.clearSessionTracking(sid);
11339
11981
  const modelInfo = ctx.model;
11340
- logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.46" : null, model: modelInfo?.id ?? null, modelApi: modelInfo?.api ?? null, contextWindow: modelInfo?.contextWindow ?? null });
11982
+ logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.47" : null, model: modelInfo?.id ?? null, modelApi: modelInfo?.api ?? null, contextWindow: modelInfo?.contextWindow ?? null });
11341
11983
  try {
11342
11984
  await runtime.reloadConfig(ctx.cwd);
11343
11985
  setDelegateDisplayUsage(resolveDelegate(runtime.adapter).displayUsage);