acp-kernel 0.0.72 → 0.0.73-pr.302.168

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,EAAkC,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAgBhF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAUvD,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;AA2DD,wBAAgB,UAAU,CAAC,KAAK,GAAE,KAAU,GAAG,eAAe,CAyU7D;AA4iCD,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
1
+ {"version":3,"file":"compress.d.ts","sourceRoot":"","sources":["../src/compress.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkC,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAgBhF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAUvD,OAAO,EAGL,KAAK,YAAY,EAGlB,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;AA2DD,wBAAgB,UAAU,CAAC,KAAK,GAAE,KAAU,GAAG,eAAe,CA+U7D;AA0lCD,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,CA+CR;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAuDvD"}
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,CA+CR;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CA8DvD"}
package/dist/index.js CHANGED
@@ -296,6 +296,7 @@ function syncBlocks(messages, state) {
296
296
  nudge: { ...state.nudge, anchors: { ...state.nudge.anchors } },
297
297
  stats: { ...state.stats },
298
298
  absorbed: (state.absorbed ?? []).map((record) => ({ ...record })),
299
+ terminalStreak: state.terminalStreak,
299
300
  nextBlockId: state.nextBlockId,
300
301
  nextRunId: state.nextRunId
301
302
  };
@@ -353,7 +354,7 @@ function defaultConfig(modelContextLimit, overrides = {}) {
353
354
  tier2GrowthMultiplier: 1.5
354
355
  },
355
356
  promotionThreshold: 5,
356
- truncate: { threshold: 0.95 },
357
+ truncate: { threshold: 0.95, terminalEscapeAfter: 3 },
357
358
  compress: {
358
359
  minCompressRange: 5e3,
359
360
  maxSummaryLength: 2e4,
@@ -405,6 +406,9 @@ function validateConfig(config) {
405
406
  if (config.truncate.threshold <= 0 || config.truncate.threshold > 1) {
406
407
  errors.push("truncate.threshold must be in (0, 1]");
407
408
  }
409
+ if (config.truncate.terminalEscapeAfter !== void 0 && (!Number.isInteger(config.truncate.terminalEscapeAfter) || config.truncate.terminalEscapeAfter < 0)) {
410
+ errors.push("truncate.terminalEscapeAfter must be an integer >= 0");
411
+ }
408
412
  for (const tier of [config.tiers.tier2Trigger, config.tiers.tier3Trigger]) {
409
413
  if (tier < 1) errors.push("tier triggers must be >= 1");
410
414
  }
@@ -654,47 +658,69 @@ var DEFAULTS = {
654
658
  };
655
659
  function truncateLargeToolOutputs(messages, tokenCount, config, countTokens, options = {}) {
656
660
  const opts = { ...DEFAULTS, ...options };
657
- if (config.modelContextLimit <= 0) return { messages, truncatedCount: 0, savedTokens: 0 };
658
- const threshold = config.truncate.threshold * config.modelContextLimit;
659
- if (tokenCount < threshold) return { messages, truncatedCount: 0, savedTokens: 0 };
660
- const protectedIndex = messages.length - opts.protectRecentMessages;
661
- const candidates = [];
662
- for (let index = 0; index < messages.length; index++) {
663
- if (index >= protectedIndex) break;
664
- const message = messages[index];
665
- if (message.contentType !== "tool-result") continue;
666
- const text = message.text ?? "";
667
- if (text.length === 0 || text.includes(TRUNCATION_MARKER)) continue;
668
- const tokens = countTokens(text);
669
- if (tokens < opts.minOutputTokens) continue;
670
- candidates.push({ index, tokens });
661
+ const limit = config.modelContextLimit;
662
+ if (limit <= 0 || tokenCount < config.truncate.threshold * limit) {
663
+ return { messages, truncatedCount: 0, savedTokens: 0, candidatesFound: 0 };
671
664
  }
672
- if (candidates.length === 0) return { messages, truncatedCount: 0, savedTokens: 0 };
673
- candidates.sort((left, right) => right.tokens - left.tokens);
674
- const targetTokens = threshold * 0.9;
675
- let savedTokens = 0;
676
- const edits = /* @__PURE__ */ new Map();
665
+ const protectedIndex = messages.length - opts.protectRecentMessages;
666
+ const findCandidates = (predicate) => {
667
+ const found = [];
668
+ for (let i = 0; i < protectedIndex; i++) {
669
+ const message = messages[i];
670
+ if (!predicate(message)) continue;
671
+ const text = message.text ?? "";
672
+ if (text.length === 0 || text.includes(TRUNCATION_MARKER)) continue;
673
+ if (countTokens(text) < opts.minOutputTokens) continue;
674
+ found.push(message);
675
+ }
676
+ return found.sort(
677
+ (a, b) => countTokens(b.text ?? "") - countTokens(a.text ?? "")
678
+ );
679
+ };
680
+ const toolResults = findCandidates((m) => m.contentType === "tool-result");
681
+ const textMessages = options.includeTextMessages ? findCandidates(
682
+ (m) => m.contentType === "text" && (m.role === "user" || m.role === "assistant") && !isRenderedSummaryMessage(m) && !m.text?.startsWith(SUMMARY_HEADER)
683
+ ) : [];
684
+ const candidatesFound = toolResults.length + textMessages.length;
677
685
  let truncatedCount = 0;
678
- for (const candidate of candidates) {
679
- if (tokenCount - savedTokens <= targetTokens) break;
680
- const original = messages[candidate.index].text ?? "";
681
- if (original.length <= opts.keepPrefixChars + opts.keepSuffixChars) continue;
682
- const prefix = original.slice(0, opts.keepPrefixChars);
683
- const suffix = original.slice(-opts.keepSuffixChars);
684
- const replacement = prefix + `
686
+ let savedTokens = 0;
687
+ let remaining = tokenCount;
688
+ const targetTokens = config.truncate.threshold * limit * 0.9;
689
+ const replacements = /* @__PURE__ */ new Map();
690
+ const applyTo = (candidates) => {
691
+ for (const candidate of candidates) {
692
+ if (remaining <= targetTokens) break;
693
+ const original = candidate.text ?? "";
694
+ const tokens = countTokens(original);
695
+ if (original.length <= opts.keepPrefixChars + opts.keepSuffixChars) {
696
+ continue;
697
+ }
698
+ const prefix = original.slice(0, opts.keepPrefixChars);
699
+ const suffix = original.slice(-opts.keepSuffixChars);
700
+ const replacement = `${prefix}
685
701
 
686
- ...${TRUNCATION_MARKER} \u2014 original ~${candidate.tokens} tokens]...
702
+ ...${TRUNCATION_MARKER} \u2014 original ~${tokens} tokens]...
687
703
 
688
- ` + suffix;
689
- edits.set(candidate.index, replacement);
690
- savedTokens += candidate.tokens - countTokens(replacement);
691
- truncatedCount++;
692
- }
693
- if (truncatedCount === 0) return { messages, truncatedCount: 0, savedTokens: 0 };
694
- const updated = messages.map(
695
- (message, index) => edits.has(index) ? { ...message, text: edits.get(index) } : message
696
- );
697
- return { messages: updated, truncatedCount, savedTokens };
704
+ ${suffix}`;
705
+ replacements.set(candidate.id, replacement);
706
+ truncatedCount++;
707
+ remaining -= tokens - countTokens(replacement);
708
+ savedTokens += tokens - countTokens(replacement);
709
+ }
710
+ };
711
+ applyTo(toolResults);
712
+ applyTo(textMessages);
713
+ if (replacements.size === 0) {
714
+ return { messages, truncatedCount: 0, savedTokens: 0, candidatesFound };
715
+ }
716
+ return {
717
+ messages: messages.map(
718
+ (m) => replacements.has(m.id) ? { ...m, text: replacements.get(m.id) } : m
719
+ ),
720
+ truncatedCount,
721
+ savedTokens,
722
+ candidatesFound
723
+ };
698
724
  }
699
725
 
700
726
  // src/hide-consumed.ts
@@ -2323,6 +2349,7 @@ function createCore(ports = {}) {
2323
2349
  state.nudge.lastPerMessageNudgeTokens = 0;
2324
2350
  state.nudge.lastNudgeShownTokens = 0;
2325
2351
  state.nudge.lastShownByTier = {};
2352
+ state.terminalStreak = 0;
2326
2353
  }
2327
2354
  return {
2328
2355
  state,
@@ -2352,7 +2379,9 @@ function createCore(ports = {}) {
2352
2379
  return {
2353
2380
  messages: result.messages,
2354
2381
  state: result.state,
2355
- nudge: result.effects.nudge
2382
+ nudge: result.effects.nudge,
2383
+ terminalEscape: result.effects.terminalEscape,
2384
+ truncationSkipped: result.effects.truncationSkipped
2356
2385
  };
2357
2386
  }
2358
2387
  function decompress(blockId, state) {
@@ -2545,18 +2574,49 @@ var emergencyTruncateNode = {
2545
2574
  name: "emergency-truncate",
2546
2575
  run(io, ctx) {
2547
2576
  const usage = ctx.config.modelContextLimit > 0 ? ctx.tokenCount / ctx.config.modelContextLimit : 0;
2548
- if (usage < ctx.config.truncate.threshold) return io;
2577
+ const prevStreak = io.state.terminalStreak ?? 0;
2578
+ if (usage < ctx.config.truncate.threshold) {
2579
+ return prevStreak > 0 ? { ...io, state: { ...io.state, terminalStreak: 0 } } : io;
2580
+ }
2549
2581
  const trunc = truncateLargeToolOutputs(
2550
2582
  io.messages,
2551
2583
  ctx.tokenCount,
2552
2584
  ctx.config,
2553
2585
  ctx.countTokens,
2554
- { protectRecentMessages: ctx.config.preserveRecentMessages }
2586
+ {
2587
+ protectRecentMessages: ctx.config.preserveRecentMessages,
2588
+ includeTextMessages: true
2589
+ }
2555
2590
  );
2591
+ const nudge = io.effects.nudge;
2592
+ const minBenefit = nudge?.breakdown.minPressureBenefit ?? 0;
2593
+ const maxPending = nudge?.breakdown.maxPending ?? 0;
2594
+ const noViableCompression = nudge !== void 0 && (minBenefit > 0 ? maxPending < minBenefit : maxPending <= 0);
2595
+ const stuck = noViableCompression && trunc.savedTokens <= 0;
2596
+ const streak = stuck ? prevStreak + 1 : 0;
2597
+ const escapeAfter = ctx.config.truncate.terminalEscapeAfter ?? 3;
2598
+ const triggered = stuck && escapeAfter > 0 && streak >= escapeAfter;
2599
+ const effects = {
2600
+ ...io.effects,
2601
+ truncatedCount: trunc.truncatedCount
2602
+ };
2603
+ if (trunc.savedTokens <= 0) {
2604
+ effects.truncationSkipped = trunc.candidatesFound === 0 ? `emergency-truncate ran at ${Math.round(usage * 100)}% usage but found no truncatable content (no oversized tool-result or text message outside the last ${ctx.config.preserveRecentMessages} messages)` : `emergency-truncate found ${trunc.candidatesFound} candidate(s) at ${Math.round(usage * 100)}% usage but none were large enough to save tokens`;
2605
+ }
2606
+ if (triggered) {
2607
+ effects.terminalEscape = {
2608
+ message: `Usage at ${Math.round(usage * 100)}% (${ctx.tokenCount}/${ctx.config.modelContextLimit} tokens) persists with nothing compressible above the benefit floor and no truncatable content: compression cannot reduce this context below the limit. Start a new session or use native compaction.`,
2609
+ usage,
2610
+ tokenCount: ctx.tokenCount,
2611
+ modelContextLimit: ctx.config.modelContextLimit,
2612
+ stuckEvents: streak
2613
+ };
2614
+ }
2556
2615
  return {
2557
2616
  ...io,
2558
2617
  messages: trunc.messages,
2559
- effects: { ...io.effects, truncatedCount: trunc.truncatedCount }
2618
+ state: { ...io.state, terminalStreak: streak },
2619
+ effects
2560
2620
  };
2561
2621
  }
2562
2622
  };
@@ -2649,7 +2709,8 @@ function applySingleRange(input) {
2649
2709
  for (const m of input.messages) {
2650
2710
  if (!m.id) continue;
2651
2711
  if (m.contentType === "reasoning") reasoningIds.add(m.id);
2652
- if (m.role === "assistant" && m.contentType === "tool-call") callIds.add(m.id);
2712
+ if (m.role === "assistant" && m.contentType === "tool-call")
2713
+ callIds.add(m.id);
2653
2714
  }
2654
2715
  const withdrawIds = /* @__PURE__ */ new Set();
2655
2716
  let splitTurnCount = 0;
@@ -2920,6 +2981,7 @@ function decideNudge(input) {
2920
2981
  const t1Eff = tiers[1]?.pending ?? 0;
2921
2982
  const t2Pen = tiers[2]?.pending ?? 0;
2922
2983
  const t3Pen = tiers[3]?.pending ?? 0;
2984
+ const maxPending = Math.max(0, t1Eff, t2Pen, t3Pen);
2923
2985
  const firstSightMassReady = state.nudge.lastNudgeShownTokens === 0 && baseline === 0 && usage >= config.nudge.minContextLimitPct && Math.max(t1Eff, t2Pen, t3Pen) >= nudgeGrowthTokens;
2924
2986
  const growthReady = firstSightMassReady || growthSinceReference >= growthFloor;
2925
2987
  const t2Count = tiers[2]?.targetBlocks.length ?? 0;
@@ -2992,10 +3054,6 @@ function decideNudge(input) {
2992
3054
  (t) => ((tiers[t]?.pending ?? 0) >= nudgeGrowthTokens || countReady(t)) && (state.nudge.lastShownByTier[t] ?? 0) > 0 && tokenCount - (state.nudge.lastShownByTier[t] ?? 0) < growthFloor
2993
3055
  ).map((t) => `T${t} (cadence)`);
2994
3056
  const blockedHint = blocked.length > 0 ? `, blocked: ${blocked.join(", ")}` : "";
2995
- const maxPending = Math.max(
2996
- 0,
2997
- ...Object.values(tiers).map((t) => t.pending)
2998
- );
2999
3057
  const pendingShort = maxPending < nudgeGrowthTokens;
3000
3058
  const growthShort = growthSinceReference < growthFloor;
3001
3059
  const parts = [];
@@ -3039,7 +3097,8 @@ function decideNudge(input) {
3039
3097
  minPressureBenefit,
3040
3098
  pendingT1: tiers[1].pending,
3041
3099
  pendingT2: tiers[2].pending,
3042
- pendingT3: tiers[3].pending
3100
+ pendingT3: tiers[3].pending,
3101
+ maxPending
3043
3102
  },
3044
3103
  contextBreakdown: ctxBreakdown
3045
3104
  };
@@ -3079,6 +3138,7 @@ function cloneState(state) {
3079
3138
  nudge: { ...state.nudge, anchors: { ...state.nudge.anchors } },
3080
3139
  stats: { ...state.stats },
3081
3140
  absorbed: (state.absorbed ?? []).map((record) => ({ ...record })),
3141
+ terminalStreak: state.terminalStreak,
3082
3142
  nextBlockId: state.nextBlockId,
3083
3143
  nextRunId: state.nextRunId
3084
3144
  };