acp-kernel 0.0.23 → 0.0.24

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.
@@ -1 +1 @@
1
- {"version":3,"file":"compress.d.ts","sourceRoot":"","sources":["../src/compress.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAapB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAQvD,OAAO,EAGL,KAAK,YAAY,EAElB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EACV,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,EAEhB,MAAM,EAEN,WAAW,EAGX,iBAAiB,EAEjB,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,KAAK;IACpB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,iBAAiB,CAAC;IACxD,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,sBAAsB,CAAC;IACvE,YAAY,IAAI,YAAY,EAAE,CAAC;IAC/B,UAAU,CACR,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,gBAAgB,GACtB,gBAAgB,GAAG,SAAS,CAAC;IAChC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,EAAE,CAAC;IACnE,MAAM,CACJ,KAAK,EAAE,gBAAgB,EACvB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GACb,YAAY,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;CAC7B;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,EAAE,CAAC;IACJ,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnC;AAsBD,wBAAgB,UAAU,CAAC,KAAK,GAAE,KAAU,GAAG,eAAe,CAwQ7D;AAiuBD,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
1
+ {"version":3,"file":"compress.d.ts","sourceRoot":"","sources":["../src/compress.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAapB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AASvD,OAAO,EAGL,KAAK,YAAY,EAElB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EACV,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,EAEhB,MAAM,EAEN,WAAW,EAGX,iBAAiB,EAEjB,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,KAAK;IACpB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,iBAAiB,CAAC;IACxD,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,sBAAsB,CAAC;IACvE,YAAY,IAAI,YAAY,EAAE,CAAC;IAC/B,UAAU,CACR,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,gBAAgB,GACtB,gBAAgB,GAAG,SAAS,CAAC;IAChC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,EAAE,CAAC;IACnE,MAAM,CACJ,KAAK,EAAE,gBAAgB,EACvB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GACb,YAAY,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;CAC7B;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,EAAE,CAAC;IACJ,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnC;AAsBD,wBAAgB,UAAU,CAAC,KAAK,GAAE,KAAU,GAAG,eAAe,CAwQ7D;AAixBD,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC,wBAAgB,aAAa,CAC3B,iBAAiB,EAAE,MAAM,EACzB,SAAS,GAAE,OAAO,CAAC,MAAM,CAAM,GAC9B,MAAM,CAoCR;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CA+BvD"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC,wBAAgB,aAAa,CAC3B,iBAAiB,EAAE,MAAM,EACzB,SAAS,GAAE,OAAO,CAAC,MAAM,CAAM,GAC9B,MAAM,CAqCR;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CA+BvD"}
package/dist/index.js CHANGED
@@ -330,7 +330,8 @@ function defaultConfig(modelContextLimit, overrides = {}) {
330
330
  growthCap: 5e4,
331
331
  minGrowthFloor: 2e4,
332
332
  minGrowthRatio: 0.45,
333
- emergencyThresholdPct: 0.95
333
+ emergencyThresholdPct: 0.95,
334
+ tier2GrowthMultiplier: 1.5
334
335
  },
335
336
  promotionThreshold: 5,
336
337
  truncate: { threshold: 0.95 },
@@ -1090,6 +1091,44 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs, cou
1090
1091
  protected: protectedRanges
1091
1092
  };
1092
1093
  }
1094
+ function mergeBatch(batch) {
1095
+ const first = batch[0];
1096
+ const last = batch[batch.length - 1];
1097
+ const count = batch.reduce((s, r) => s + r.count, 0);
1098
+ const tokens = batch.reduce((s, r) => s + r.tokens, 0);
1099
+ const toolPct = Math.round(
1100
+ batch.reduce((s, r) => s + r.toolPct * r.count, 0) / count
1101
+ );
1102
+ const merged = {
1103
+ startRef: first.startRef,
1104
+ endRef: last.endRef,
1105
+ count,
1106
+ tokens,
1107
+ toolPct,
1108
+ textPct: 100 - toolPct
1109
+ };
1110
+ if (batch.some((r) => r.dangerous === true)) {
1111
+ merged.dangerous = true;
1112
+ }
1113
+ return merged;
1114
+ }
1115
+ function mergeRangesToThreshold(ranges, minChars) {
1116
+ if (minChars <= 0 || ranges.length === 0) return ranges;
1117
+ const result = [];
1118
+ let batch = [];
1119
+ for (const r of ranges) {
1120
+ batch.push(r);
1121
+ const batchTokens = batch.reduce((s, x) => s + x.tokens, 0);
1122
+ if (batchTokens * 4 >= minChars) {
1123
+ result.push(mergeBatch(batch));
1124
+ batch = [];
1125
+ }
1126
+ }
1127
+ if (batch.length > 0) {
1128
+ result.push(mergeBatch(batch));
1129
+ }
1130
+ return result;
1131
+ }
1093
1132
 
1094
1133
  // src/pipeline.ts
1095
1134
  function makeIO(messages, state, effects = {}) {
@@ -1376,7 +1415,10 @@ var recommendNode = {
1376
1415
  const nothingToCompress = contextRanges.compressible.length === 0;
1377
1416
  const recommendation = {
1378
1417
  contextRanges,
1379
- recommendedRanges: contextRanges.compressible,
1418
+ recommendedRanges: mergeRangesToThreshold(
1419
+ contextRanges.compressible,
1420
+ ctx.config.compress.minCompressRange
1421
+ ),
1380
1422
  nothingToCompress
1381
1423
  };
1382
1424
  return { ...io, effects: { ...io.effects, recommendation } };
@@ -1402,6 +1444,7 @@ var nudgeNode = {
1402
1444
  if (baseline > 0 && ctx.tokenCount < baseline - nudgeGrowthTokens) {
1403
1445
  stamped.lastPerMessageNudgeTokens = ctx.tokenCount;
1404
1446
  stamped.lastNudgeShownTokens = 0;
1447
+ stamped.lastShownByTier = {};
1405
1448
  }
1406
1449
  if (stamped.lastPerMessageNudgeTokens === 0) {
1407
1450
  stamped.lastPerMessageNudgeTokens = ctx.tokenCount;
@@ -1670,10 +1713,11 @@ function resolveAdaptiveGrowth(modelContextLimit, nudge) {
1670
1713
  )
1671
1714
  );
1672
1715
  }
1673
- function pendingByTier(state, recommendation, countTokens) {
1716
+ function pendingByTier(state, recommendation, countTokens, minCompressRange) {
1674
1717
  const out = {};
1675
- const compressible = recommendation?.contextRanges.compressible ?? [];
1676
- out[1] = { pending: compressible.reduce((s, r) => s + r.tokens, 0), targetBlocks: [] };
1718
+ const merged = recommendation?.recommendedRanges ?? [];
1719
+ const effective = minCompressRange > 0 ? merged.filter((r) => r.tokens * 4 >= minCompressRange) : merged;
1720
+ out[1] = { pending: effective.reduce((s, r) => s + r.tokens, 0), targetBlocks: [] };
1677
1721
  const active = activeBlocks(state);
1678
1722
  const t1 = active.filter((b) => b.tier === 1);
1679
1723
  const t2 = active.filter((b) => b.tier === 2);
@@ -1688,6 +1732,7 @@ function decideNudge(input) {
1688
1732
  const nudgeGrowthTokens = resolveAdaptiveGrowth(limit, config.nudge);
1689
1733
  const overLimit = usage >= config.nudge.maxContextLimitPct;
1690
1734
  const emergencyOverride = usage >= config.nudge.emergencyThresholdPct;
1735
+ const pressure = overLimit || emergencyOverride;
1691
1736
  const baseline = state.nudge.lastPerMessageNudgeTokens;
1692
1737
  const hadPendingNudge = state.nudge.lastNudgeShownTokens > 0;
1693
1738
  const hasPendingNudge = hadPendingNudge;
@@ -1699,44 +1744,67 @@ function decideNudge(input) {
1699
1744
  );
1700
1745
  const growthSinceReference = tokenCount - growthReference;
1701
1746
  const rec = recommendation;
1702
- const tiers = pendingByTier(state, rec, countTokens);
1747
+ const tiers = pendingByTier(
1748
+ state,
1749
+ rec,
1750
+ countTokens,
1751
+ config.compress.minCompressRange
1752
+ );
1753
+ const tier2Threshold = Math.round(
1754
+ nudgeGrowthTokens * (config.nudge.tier2GrowthMultiplier ?? 1.5)
1755
+ );
1703
1756
  let injectedTier = null;
1704
1757
  let injectedReason = "";
1705
1758
  const growthReady = growthSinceReference >= growthFloor;
1706
- if (!overLimit && growthReady) {
1707
- for (const tier of [1, 2, 3]) {
1708
- if (!config.tiers.enabled && tier > 1) break;
1709
- const info = tiers[tier];
1710
- if (!info || info.pending < nudgeGrowthTokens) continue;
1711
- const lastShown = state.nudge.lastShownByTier[tier] ?? 0;
1712
- const cadenceMet = lastShown === 0 || tokenCount - lastShown >= growthFloor;
1713
- if (!cadenceMet) continue;
1714
- injectedTier = tier;
1715
- injectedReason = tier === 1 ? `T1 compressible ${info.pending} >= ${nudgeGrowthTokens}, growth ${growthSinceReference}, usage ${Math.round(usage * 100)}%` : `T${tier} distill ready: ${info.targetBlocks.length} tier-${tier - 1} blocks (${info.pending} tokens) >= ${nudgeGrowthTokens}, usage ${Math.round(usage * 100)}%`;
1716
- break;
1759
+ const t1Eff = tiers[1]?.pending ?? 0;
1760
+ const t2Pen = tiers[2]?.pending ?? 0;
1761
+ const t3Pen = tiers[3]?.pending ?? 0;
1762
+ if (pressure) {
1763
+ const candidates = [1];
1764
+ if (config.tiers.enabled) {
1765
+ candidates.push(2, 3);
1766
+ }
1767
+ let best = null;
1768
+ let bestPending = 0;
1769
+ for (const t of candidates) {
1770
+ const p = tiers[t]?.pending ?? 0;
1771
+ if (p > bestPending) {
1772
+ bestPending = p;
1773
+ best = t;
1774
+ }
1717
1775
  }
1718
- } else if (overLimit) {
1719
- for (const tier of [1, 2, 3]) {
1720
- if (!config.tiers.enabled && tier > 1) break;
1721
- const info = tiers[tier];
1722
- if (!info || info.pending < config.compress.minCompressRange) continue;
1723
- injectedTier = tier;
1724
- injectedReason = emergencyOverride ? `EMERGENCY: usage ${Math.round(usage * 100)}% >= ${Math.round(config.nudge.emergencyThresholdPct * 100)}%, T${tier} pending ${info.pending}` : `OVER-LIMIT: usage ${Math.round(usage * 100)}% >= ${Math.round(config.nudge.maxContextLimitPct * 100)}%, T${tier} pending ${info.pending}`;
1725
- break;
1776
+ if (best !== null && bestPending > 0) {
1777
+ injectedTier = best;
1778
+ const label = emergencyOverride ? "EMERGENCY" : "OVER-LIMIT";
1779
+ injectedReason = best === 1 ? `${label} T1: max effective pending ${bestPending}, usage ${Math.round(usage * 100)}%` : `${label} T${best} distill: max pending ${bestPending} (T1 effective ${t1Eff}, T2 ${t2Pen}, T3 ${t3Pen}), usage ${Math.round(usage * 100)}%`;
1780
+ }
1781
+ } else if (growthReady) {
1782
+ if (t1Eff >= nudgeGrowthTokens) {
1783
+ injectedTier = 1;
1784
+ injectedReason = `T1 effective ${t1Eff} >= ${nudgeGrowthTokens}, growth ${growthSinceReference}, usage ${Math.round(usage * 100)}%`;
1785
+ } else if (config.tiers.enabled && t2Pen >= tier2Threshold && t2Pen > t1Eff) {
1786
+ const lastShown = state.nudge.lastShownByTier[2] ?? 0;
1787
+ const cadenceMet = lastShown === 0 || tokenCount - lastShown >= growthFloor;
1788
+ if (cadenceMet) {
1789
+ injectedTier = 2;
1790
+ injectedReason = `T2 distill ready: ${tiers[2].targetBlocks.length} tier-1 blocks (${t2Pen} tokens) >= ${tier2Threshold} (1.5x) and > T1 effective ${t1Eff}, usage ${Math.round(usage * 100)}%`;
1791
+ }
1792
+ } else if (config.tiers.enabled && t3Pen >= tier2Threshold && t3Pen > t2Pen && t3Pen > t1Eff) {
1793
+ const lastShown = state.nudge.lastShownByTier[3] ?? 0;
1794
+ const cadenceMet = lastShown === 0 || tokenCount - lastShown >= growthFloor;
1795
+ if (cadenceMet) {
1796
+ injectedTier = 3;
1797
+ injectedReason = `T3 condense ready: ${tiers[3].targetBlocks.length} tier-2 blocks (${t3Pen} tokens) >= ${tier2Threshold} (1.5x) and > T2 ${t2Pen} and > T1 effective ${t1Eff}, usage ${Math.round(usage * 100)}%`;
1798
+ }
1726
1799
  }
1727
1800
  }
1728
- const shouldInject = injectedTier !== null || overLimit && (rec?.recommendedRanges?.length ?? 0) > 0;
1801
+ const shouldInject = injectedTier !== null;
1729
1802
  let reason;
1730
- if (emergencyOverride && injectedTier !== null) {
1731
- reason = injectedReason;
1732
- } else if (emergencyOverride) {
1733
- reason = `EMERGENCY: usage ${Math.round(usage * 100)}% >= ${Math.round(config.nudge.emergencyThresholdPct * 100)}% (no compressible content)`;
1734
- } else if (overLimit && injectedTier !== null) {
1735
- reason = injectedReason;
1736
- } else if (overLimit) {
1737
- reason = `OVER-LIMIT: usage ${Math.round(usage * 100)}% >= ${Math.round(config.nudge.maxContextLimitPct * 100)}% (no compressible content)`;
1738
- } else if (injectedTier !== null) {
1803
+ if (injectedTier !== null) {
1739
1804
  reason = injectedReason;
1805
+ } else if (pressure) {
1806
+ const label = emergencyOverride ? "EMERGENCY" : "OVER-LIMIT";
1807
+ reason = `${label}: usage ${Math.round(usage * 100)}% but no tier has effective compressible content (T1 effective ${t1Eff}, T2 ${t2Pen}, T3 ${t3Pen}) \u2014 nudge suppressed to avoid offering ranges below minCompressRange`;
1740
1808
  } else {
1741
1809
  const tiersList = [1, 2, 3];
1742
1810
  const eligible = tiersList.filter((t) => config.tiers.enabled || t === 1);
@@ -2091,20 +2159,23 @@ ${lines.join("\n")}`;
2091
2159
  function renderNudgeText(decision, prompts = defaultPrompts) {
2092
2160
  const breakdownStr = formatBreakdown(decision.contextBreakdown);
2093
2161
  const rangesStr = formatRanges(decision.compressibleRanges, decision.protectedRanges ?? []);
2162
+ const isEmergency = !!decision.breakdown?.emergencyOverride || !!decision.breakdown?.overLimit;
2094
2163
  if (decision.tier !== null && decision.tier >= 2) {
2095
2164
  const isT2 = decision.tier === 2;
2096
2165
  const targets = decision.tierTargetBlocks ?? [];
2097
2166
  const blockList = formatTierTargetBlocks(targets);
2098
2167
  const startId = targets[0]?.blockId ?? "b1";
2099
2168
  const endId = targets[targets.length - 1]?.blockId ?? "b5";
2169
+ const voice = isEmergency ? "emergency" : "gentle";
2170
+ const triggerLine = isEmergency ? `[EMERGENCY \u2014 TIER ${decision.tier} ${isT2 ? "DISTILLATION" : "CONDENSATION"}] Context limit reached \u2014 distill NOW into a denser summary to reclaim tokens.` : `[TIER ${decision.tier} ${isT2 ? "DISTILLATION" : "CONDENSATION"} TRIGGER]`;
2100
2171
  return {
2101
- voice: "gentle",
2172
+ voice,
2102
2173
  text: [
2103
2174
  efficiencyNote(prompts),
2104
2175
  "",
2105
2176
  breakdownStr,
2106
2177
  "",
2107
- `[TIER ${decision.tier} ${isT2 ? "DISTILLATION" : "CONDENSATION"} TRIGGER]`,
2178
+ triggerLine,
2108
2179
  isT2 ? `Your tier-1 compression summaries have accumulated. Distill them into a single denser tier-2 summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-2 block as well \u2014 apply HOW TO COMPRESS to those raw messages and the TIER 2 distillation rules to the existing summaries, so the whole span is covered and nothing is lost.` : `Your tier-2 compression summaries have accumulated. Condense them further into a tier-3 ultra-condensed summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-3 block as well \u2014 apply HOW TO COMPRESS to those raw messages and the TIER 3 condensation rules to the existing summaries, so the whole span is covered and nothing is lost.`,
2109
2180
  blockList,
2110
2181
  `Example: compress({ content: [{ startId: "${startId}", endId: "${endId}", summary: "..." }] })`,
@@ -2115,7 +2186,6 @@ function renderNudgeText(decision, prompts = defaultPrompts) {
2115
2186
  ].join("\n")
2116
2187
  };
2117
2188
  }
2118
- const isEmergency = !!decision.breakdown?.emergencyOverride || !!decision.breakdown?.overLimit;
2119
2189
  if (isEmergency) {
2120
2190
  return {
2121
2191
  voice: "emergency",