acp-kernel 0.0.83 → 0.0.84

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.
Files changed (39) hide show
  1. package/DESIGN.md +16 -3
  2. package/README.md +104 -3
  3. package/dist/absorb.d.ts.map +1 -1
  4. package/dist/boundaries.d.ts.map +1 -1
  5. package/dist/ccr.d.ts +116 -0
  6. package/dist/ccr.d.ts.map +1 -0
  7. package/dist/{chunk-VPRKYTHW.js → chunk-6RNVHY67.js} +3 -3
  8. package/dist/{chunk-5TQA7ZQM.js → chunk-MXL3G3BN.js} +13 -2
  9. package/dist/chunk-MXL3G3BN.js.map +1 -0
  10. package/dist/compress-tools.d.ts +104 -2
  11. package/dist/compress-tools.d.ts.map +1 -1
  12. package/dist/compress.d.ts +8 -0
  13. package/dist/compress.d.ts.map +1 -1
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/content-store.d.ts +86 -0
  16. package/dist/content-store.d.ts.map +1 -0
  17. package/dist/crush.d.ts +142 -0
  18. package/dist/crush.d.ts.map +1 -0
  19. package/dist/image-compress.d.ts +156 -0
  20. package/dist/image-compress.d.ts.map +1 -0
  21. package/dist/index.d.ts +12 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +2909 -636
  24. package/dist/index.js.map +1 -1
  25. package/dist/output-steering.d.ts +176 -0
  26. package/dist/output-steering.d.ts.map +1 -0
  27. package/dist/panel/index.js +1 -1
  28. package/dist/persist/index.js +4 -2
  29. package/dist/persist/index.js.map +1 -1
  30. package/dist/persist/state-merge.d.ts.map +1 -1
  31. package/dist/pipeline.d.ts +3 -0
  32. package/dist/pipeline.d.ts.map +1 -1
  33. package/dist/rebuild.d.ts.map +1 -1
  34. package/dist/state.d.ts.map +1 -1
  35. package/dist/types.d.ts +82 -0
  36. package/dist/types.d.ts.map +1 -1
  37. package/package.json +1 -1
  38. package/dist/chunk-5TQA7ZQM.js.map +0 -1
  39. /package/dist/{chunk-VPRKYTHW.js.map → chunk-6RNVHY67.js.map} +0 -0
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  resolvePrompts,
17
17
  thinkingTokenValue,
18
18
  viableRanges
19
- } from "./chunk-VPRKYTHW.js";
19
+ } from "./chunk-6RNVHY67.js";
20
20
  import {
21
21
  activeBlocks,
22
22
  advanceSurvival,
@@ -26,7 +26,7 @@ import {
26
26
  coveredMessageIds,
27
27
  createInitialState,
28
28
  highestActiveTier
29
- } from "./chunk-5TQA7ZQM.js";
29
+ } from "./chunk-MXL3G3BN.js";
30
30
 
31
31
  // src/refs.ts
32
32
  var REF_WIDTH = 5;
@@ -372,9 +372,29 @@ function defaultConfig(modelContextLimit, overrides = {}) {
372
372
  absorb: {
373
373
  enabled: false,
374
374
  toolName: "absorb",
375
- minToolTokens: 1e3,
375
+ // Raised 1000 → 4000 (issue #352): lossless CCR takes over large-result
376
+ // handling; absorb's forced distillation only fires above the new bar.
377
+ minToolTokens: 4e3,
376
378
  contextThresholdPct: 0,
377
379
  excludeTools: []
380
+ },
381
+ crush: {
382
+ enabled: false,
383
+ minReduction: 0.1
384
+ },
385
+ imageCompression: {
386
+ enabled: false,
387
+ minTokens: 512,
388
+ maxDimension: 1280,
389
+ quality: 80,
390
+ format: "webp"
391
+ },
392
+ ccr: {
393
+ enabled: false,
394
+ toolName: "acp_retrieve",
395
+ minToolTokens: 4e3,
396
+ excludeTools: [],
397
+ maxHeadChars: 96
378
398
  }
379
399
  };
380
400
  return {
@@ -384,7 +404,10 @@ function defaultConfig(modelContextLimit, overrides = {}) {
384
404
  nudge: { ...base.nudge, ...overrides.nudge },
385
405
  truncate: { ...base.truncate, ...overrides.truncate },
386
406
  compress: { ...base.compress, ...overrides.compress },
387
- absorb: overrides.absorb ? { ...base.absorb, ...overrides.absorb } : base.absorb
407
+ absorb: overrides.absorb ? { ...base.absorb, ...overrides.absorb } : base.absorb,
408
+ crush: overrides.crush ? { ...base.crush, ...overrides.crush } : base.crush,
409
+ imageCompression: overrides.imageCompression ? { ...base.imageCompression, ...overrides.imageCompression } : base.imageCompression,
410
+ ccr: overrides.ccr ? { ...base.ccr, ...overrides.ccr } : base.ccr
388
411
  };
389
412
  }
390
413
  function validateConfig(config) {
@@ -439,470 +462,84 @@ function validateConfig(config) {
439
462
  errors.push("rules.maxRuleChars must be >= 1");
440
463
  }
441
464
  }
442
- return errors;
443
- }
444
-
445
- // src/boundaries.ts
446
- var MESSAGE_REF_PATTERN = /^m0*(\d{1,5})$/;
447
- var BLOCK_REF_PATTERN = /^b(\d{1,9})$/;
448
- function parseBoundary(ref) {
449
- const normalized = ref.trim().toLowerCase();
450
- const messageMatch = MESSAGE_REF_PATTERN.exec(normalized);
451
- if (messageMatch) {
452
- const numericId = Number(messageMatch[1]);
453
- if (numericId >= 1 && numericId <= 99999) {
454
- return { kind: "message", numericId, raw: normalized };
465
+ if (config.crush) {
466
+ if (!Number.isFinite(config.crush.minReduction) || config.crush.minReduction <= 0 || config.crush.minReduction > 1) {
467
+ errors.push("crush.minReduction must be in (0, 1]");
455
468
  }
456
- }
457
- const blockMatch = BLOCK_REF_PATTERN.exec(normalized);
458
- if (blockMatch) {
459
- const numericId = Number(blockMatch[1]);
460
- if (numericId >= 1) return { kind: "block", numericId, raw: normalized };
461
- }
462
- return null;
463
- }
464
- var BoundaryNotFoundError = class extends Error {
465
- code = "BOUNDARY_NOT_FOUND";
466
- kind;
467
- endpoint;
468
- constructor(kind, endpoint, message) {
469
- super(message);
470
- this.name = "BoundaryNotFoundError";
471
- this.code = "BOUNDARY_NOT_FOUND";
472
- this.kind = kind;
473
- this.endpoint = endpoint;
474
- }
475
- };
476
- function resolveBoundaries(input) {
477
- const start = parseBoundary(input.startRef);
478
- const end = parseBoundary(input.endRef);
479
- if (!start || !end) {
480
- throw new Error(
481
- `Invalid boundary ref(s): startId="${input.startRef}", endId="${input.endRef}". Use mNNNNN or bN.`
482
- );
483
- }
484
- const indexByMessageId = /* @__PURE__ */ new Map();
485
- input.messages.forEach(
486
- (message, index) => indexByMessageId.set(message.id, index)
487
- );
488
- let snappedBoundaries = [];
489
- const startAnchor = resolveAnchorIndex(
490
- start,
491
- input.state,
492
- indexByMessageId,
493
- "start"
494
- );
495
- if (startAnchor.snapped) snappedBoundaries.push(startAnchor.snapped);
496
- const endAnchor = resolveAnchorIndex(
497
- end,
498
- input.state,
499
- indexByMessageId,
500
- "end"
501
- );
502
- if (endAnchor.snapped) snappedBoundaries.push(endAnchor.snapped);
503
- let startIndex = startAnchor.index;
504
- let endIndex = endAnchor.index;
505
- if (startIndex > endIndex) {
506
- [startIndex, endIndex] = [endIndex, startIndex];
507
- }
508
- const messageIds = [];
509
- for (let index = startIndex; index <= endIndex; index++) {
510
- const message = input.messages[index];
511
- if (message && !isRenderedSummaryMessage(message))
512
- messageIds.push(message.id);
513
- }
514
- const boundaryKind = start.kind === "block" || end.kind === "block" ? "block" : "message";
515
- const nestedBlockIds = [];
516
- const nestedSeen = /* @__PURE__ */ new Set();
517
- for (const block of activeBlocks(input.state)) {
518
- if (blockVisibleInRange(block, indexByMessageId, startIndex, endIndex)) {
519
- if (!nestedSeen.has(block.blockId)) {
520
- nestedSeen.add(block.blockId);
521
- nestedBlockIds.push(block.blockId);
469
+ if (config.crush.strategies) {
470
+ for (const [id, ov] of Object.entries(config.crush.strategies)) {
471
+ if (!ov || typeof ov !== "object" || Array.isArray(ov)) {
472
+ errors.push(`crush.strategies.${id} must be an object`);
473
+ continue;
474
+ }
475
+ if (ov.enabled !== void 0 && typeof ov.enabled !== "boolean") {
476
+ errors.push(`crush.strategies.${id}.enabled must be a boolean`);
477
+ }
478
+ if (ov.excludeTools !== void 0 && (!Array.isArray(ov.excludeTools) || ov.excludeTools.some((t) => typeof t !== "string"))) {
479
+ errors.push(
480
+ `crush.strategies.${id}.excludeTools must be a string array`
481
+ );
482
+ }
522
483
  }
523
484
  }
524
485
  }
525
- const protectedGaps = [];
526
- return {
527
- startIndex,
528
- endIndex,
529
- messageIds,
530
- nestedBlockIds,
531
- boundaryKind,
532
- protectedGaps,
533
- snappedBoundaries
534
- };
535
- }
536
- function resolveAnchorIndex(boundary, state, indexByMessageId, endpoint) {
537
- const label = endpoint === "start" ? "startId" : "endId";
538
- if (boundary.kind === "message") {
539
- const rawId = state.messageRefs.byRef[boundary.raw] ?? state.messageRefs.byRef[formatPaddedRef(boundary.numericId)];
540
- if (!rawId) {
541
- throw new BoundaryNotFoundError(
542
- "unknown",
543
- endpoint,
544
- `${label}="${boundary.raw}" does not exist in this session (typo or wrong session) \u2014 run acp_status for current refs.`
545
- );
486
+ if (config.imageCompression) {
487
+ const ic = config.imageCompression;
488
+ if (ic.minTokens !== void 0 && (!Number.isFinite(ic.minTokens) || ic.minTokens < 0)) {
489
+ errors.push("imageCompression.minTokens must be finite and >= 0");
546
490
  }
547
- const index = indexByMessageId.get(rawId);
548
- if (index !== void 0) {
549
- return { index, snapped: null };
491
+ if (ic.maxDimension !== void 0 && (!Number.isInteger(ic.maxDimension) || ic.maxDimension < 16)) {
492
+ errors.push("imageCompression.maxDimension must be an integer >= 16");
550
493
  }
551
- const owner2 = activeOwnerAnchor(state, [rawId], indexByMessageId);
552
- if (owner2 !== null) {
553
- return {
554
- index: owner2,
555
- snapped: `${label}="${boundary.raw}" refers to a message already compressed into an active block \u2014 anchored to the active block covering it instead.`
556
- };
494
+ if (ic.quality !== void 0 && (!Number.isFinite(ic.quality) || ic.quality < 1 || ic.quality > 100)) {
495
+ errors.push("imageCompression.quality must be in [1, 100]");
557
496
  }
558
- throw new BoundaryNotFoundError(
559
- "consumed",
560
- endpoint,
561
- `${label}="${boundary.raw}" not found in visible context (likely consumed by an existing block).`
562
- );
563
- }
564
- const block = blockById(state, `b${boundary.numericId}`);
565
- if (!block) {
566
- throw new BoundaryNotFoundError(
567
- "unknown",
568
- endpoint,
569
- `${label}="b${boundary.numericId}" does not exist in this session (typo or wrong session) \u2014 run acp_status for current refs.`
570
- );
571
- }
572
- if (block.active) {
573
- const anchor = visibleBlockAnchor(block, indexByMessageId);
574
- if (anchor !== null) {
575
- return { index: anchor, snapped: null };
497
+ if (ic.format !== void 0 && ic.format !== "webp" && ic.format !== "jpeg" && ic.format !== "png") {
498
+ errors.push('imageCompression.format must be "webp", "jpeg", or "png"');
576
499
  }
577
500
  }
578
- const owner = activeOwnerAnchor(
579
- state,
580
- block.effectiveMessageIds,
581
- indexByMessageId
582
- );
583
- if (owner !== null) {
584
- return {
585
- index: owner,
586
- snapped: `${label}="b${boundary.numericId}" was consumed by a higher-tier block \u2014 anchored to the active block covering its content instead.`
587
- };
588
- }
589
- if (!block.active) {
590
- throw new BoundaryNotFoundError(
591
- "consumed",
592
- endpoint,
593
- `${label}="b${boundary.numericId}" not found in visible context (block distilled/consumed by a higher-tier block).`
594
- );
595
- }
596
- throw new BoundaryNotFoundError(
597
- "consumed",
598
- endpoint,
599
- `${label}="b${boundary.numericId}" is an active block but none of its content (raw messages or rendered summary) is visible in the current context \u2014 run acp_status to verify.`
600
- );
601
- }
602
- function activeOwnerAnchor(state, ownedIds, indexByMessageId) {
603
- if (ownedIds.length === 0) return null;
604
- const owned = new Set(ownedIds);
605
- let best = null;
606
- for (const block of state.blocks) {
607
- if (!block.active) continue;
608
- const inherited = inheritedContentIds(state, block);
609
- let ownsInherited = false;
610
- for (const id of owned) {
611
- if (inherited.has(id)) {
612
- ownsInherited = true;
613
- break;
614
- }
501
+ if (config.ccr) {
502
+ if (config.ccr.enabled && !config.ccr.toolName) {
503
+ errors.push("ccr.toolName must be a non-empty string when enabled");
615
504
  }
616
- if (!ownsInherited) continue;
617
- const anchor = visibleBlockAnchor(block, indexByMessageId);
618
- if (anchor === null) continue;
619
- if (best === null || anchor < best) {
620
- best = anchor;
505
+ if (!Number.isFinite(config.ccr.minToolTokens) || config.ccr.minToolTokens < 0) {
506
+ errors.push("ccr.minToolTokens must be >= 0");
507
+ }
508
+ if (!Number.isFinite(config.ccr.maxHeadChars) || config.ccr.maxHeadChars < 0) {
509
+ errors.push("ccr.maxHeadChars must be >= 0");
621
510
  }
622
511
  }
623
- return best;
512
+ return errors;
624
513
  }
625
- function inheritedContentIds(state, block) {
626
- const ids = /* @__PURE__ */ new Set();
627
- for (const childId of block.directBlockIds) {
628
- const child = blockById(state, childId);
629
- if (!child) continue;
630
- for (const id of child.effectiveMessageIds) ids.add(id);
514
+
515
+ // src/surface-config.ts
516
+ function applySectionOverrides(sections, overrides) {
517
+ const out = [];
518
+ for (const [key, text] of sections) {
519
+ const o = overrides?.[key];
520
+ if (o === null) continue;
521
+ out.push(typeof o === "string" ? o : text);
631
522
  }
632
- return ids;
633
- }
634
- function formatPaddedRef(index) {
635
- return `m${String(index).padStart(5, "0")}`;
636
- }
637
- function visibleBlockAnchor(block, indexByMessageId) {
638
- const summaryIndex = indexByMessageId.get(summaryMessageId(block.blockId));
639
- if (summaryIndex !== void 0) return summaryIndex;
640
- return earliestIndexOfIds(block.effectiveMessageIds, indexByMessageId);
523
+ return out;
641
524
  }
642
- function blockVisibleInRange(block, indexByMessageId, startIndex, endIndex) {
643
- const summaryIndex = indexByMessageId.get(summaryMessageId(block.blockId));
644
- if (summaryIndex !== void 0 && summaryIndex >= startIndex && summaryIndex <= endIndex) {
645
- return true;
646
- }
647
- const rawIndex = earliestIndexOfIds(
648
- block.effectiveMessageIds,
649
- indexByMessageId
650
- );
651
- return rawIndex !== null && rawIndex >= startIndex && rawIndex <= endIndex;
525
+ function cloneWithDescriptions(schema, overrides) {
526
+ if (Object.keys(overrides).length === 0) return schema;
527
+ return cloneNode(schema, overrides);
652
528
  }
653
- function earliestIndexOfIds(ids, indexByMessageId) {
654
- let earliest = null;
655
- for (const id of ids) {
656
- const index = indexByMessageId.get(id);
657
- if (index !== void 0 && (earliest === null || index < earliest)) {
658
- earliest = index;
529
+ function cloneNode(node, overrides) {
530
+ if (Array.isArray(node)) return node.map((item) => cloneNode(item, overrides));
531
+ if (node && typeof node === "object") {
532
+ const out = {};
533
+ for (const [name, value] of Object.entries(node)) {
534
+ const cloned = cloneNode(value, overrides);
535
+ if (Object.hasOwn(overrides, name) && cloned && typeof cloned === "object" && !Array.isArray(cloned)) {
536
+ cloned.description = overrides[name];
537
+ }
538
+ out[name] = cloned;
659
539
  }
540
+ return out;
660
541
  }
661
- return earliest;
662
- }
663
-
664
- // src/truncate-tools.ts
665
- var TRUNCATION_MARKER = "[truncated for context space]";
666
- var DEFAULTS = {
667
- minOutputTokens: 1e3,
668
- keepPrefixChars: 2e3,
669
- keepSuffixChars: 2e3,
670
- protectRecentMessages: 3
671
- };
672
- function truncateLargeToolOutputs(messages, tokenCount, config, countTokens, options = {}) {
673
- const opts = { ...DEFAULTS, ...options };
674
- const limit = config.modelContextLimit;
675
- if (limit <= 0 || tokenCount < config.truncate.threshold * limit) {
676
- return { messages, truncatedCount: 0, savedTokens: 0, candidatesFound: 0 };
677
- }
678
- const protectedIndex = messages.length - opts.protectRecentMessages;
679
- const findCandidates = (predicate) => {
680
- const found = [];
681
- for (let i = 0; i < protectedIndex; i++) {
682
- const message = messages[i];
683
- if (!predicate(message)) continue;
684
- const text = message.text ?? "";
685
- if (text.length === 0 || text.includes(TRUNCATION_MARKER)) continue;
686
- if (countTokens(text) < opts.minOutputTokens) continue;
687
- found.push(message);
688
- }
689
- return found.sort(
690
- (a, b) => countTokens(b.text ?? "") - countTokens(a.text ?? "")
691
- );
692
- };
693
- const toolResults = findCandidates((m) => m.contentType === "tool-result");
694
- const textMessages = options.includeTextMessages ? findCandidates(
695
- (m) => m.contentType === "text" && (m.role === "user" || m.role === "assistant") && !isRenderedSummaryMessage(m) && !m.text?.startsWith(SUMMARY_HEADER)
696
- ) : [];
697
- const candidatesFound = toolResults.length + textMessages.length;
698
- let truncatedCount = 0;
699
- let savedTokens = 0;
700
- let remaining = tokenCount;
701
- const targetTokens = config.truncate.threshold * limit * 0.9;
702
- const replacements = /* @__PURE__ */ new Map();
703
- const applyTo = (candidates) => {
704
- for (const candidate of candidates) {
705
- if (remaining <= targetTokens) break;
706
- const original = candidate.text ?? "";
707
- const tokens = countTokens(original);
708
- if (original.length <= opts.keepPrefixChars + opts.keepSuffixChars) {
709
- continue;
710
- }
711
- const prefix = clampPrefix(original, opts.keepPrefixChars);
712
- const suffix = clampWindow(original, original.length - opts.keepSuffixChars, original.length);
713
- const replacement = `${prefix}
714
-
715
- ...${TRUNCATION_MARKER} \u2014 original ~${tokens} tokens]...
716
-
717
- ${suffix}`;
718
- replacements.set(candidate.id, replacement);
719
- truncatedCount++;
720
- remaining -= tokens - countTokens(replacement);
721
- savedTokens += tokens - countTokens(replacement);
722
- }
723
- };
724
- applyTo(toolResults);
725
- applyTo(textMessages);
726
- if (replacements.size === 0) {
727
- return { messages, truncatedCount: 0, savedTokens: 0, candidatesFound };
728
- }
729
- return {
730
- messages: messages.map(
731
- (m) => replacements.has(m.id) ? { ...m, text: replacements.get(m.id) } : m
732
- ),
733
- truncatedCount,
734
- savedTokens,
735
- candidatesFound
736
- };
737
- }
738
-
739
- // src/hide-consumed.ts
740
- var KEEP_LAST_ORPHANED = 2;
741
- function rangeKey(startRef, endRef) {
742
- return `${startRef}::${endRef}`;
743
- }
744
- function parseCallText(text) {
745
- const raw = text ?? "";
746
- const start = raw.indexOf("{");
747
- if (start < 0) return null;
748
- let parsed;
749
- try {
750
- parsed = JSON.parse(raw.slice(start));
751
- } catch {
752
- return null;
753
- }
754
- if (!parsed || typeof parsed !== "object") return null;
755
- const obj = parsed;
756
- let content = null;
757
- let contentWasString = false;
758
- if (Array.isArray(obj.content)) {
759
- content = obj.content;
760
- } else if (typeof obj.content === "string") {
761
- contentWasString = true;
762
- try {
763
- const inner = JSON.parse(obj.content);
764
- if (Array.isArray(inner)) content = inner;
765
- } catch {
766
- content = null;
767
- }
768
- }
769
- if (!content || content.length === 0) return null;
770
- return { prefix: raw.slice(0, start), obj, content, contentWasString };
771
- }
772
- function rewriteCompressText(text, liveKeys) {
773
- const parsed = parseCallText(text);
774
- if (!parsed) return null;
775
- const { prefix, obj, content, contentWasString } = parsed;
776
- const kept = content.filter((entry) => {
777
- if (!entry || typeof entry !== "object") return false;
778
- const e = entry;
779
- const s = typeof e.startId === "string" ? e.startId : typeof e.messageId === "string" ? e.messageId : "";
780
- const end = typeof e.endId === "string" ? e.endId : typeof e.messageId === "string" ? e.messageId : "";
781
- return liveKeys.has(rangeKey(s, end));
782
- });
783
- if (kept.length === 0) return null;
784
- return prefix + serializeCompacted(obj, kept, contentWasString).text;
785
- }
786
- var SUMMARY_STUB_CHARS = 200;
787
- function compactEntry(entry) {
788
- if (!entry || typeof entry !== "object") return entry;
789
- const e = entry;
790
- if (typeof e.summary !== "string" || e.summary.length <= SUMMARY_STUB_CHARS) return entry;
791
- return { ...e, summary: `${clampPrefix(e.summary, SUMMARY_STUB_CHARS - 1)}\u2026` };
792
- }
793
- function serializeCompacted(obj, content, contentWasString) {
794
- let changed = false;
795
- const compacted = content.map((entry) => {
796
- const out = compactEntry(entry);
797
- if (out !== entry) changed = true;
798
- return out;
799
- });
800
- const outContent = contentWasString ? JSON.stringify(compacted) : compacted;
801
- return { text: JSON.stringify({ ...obj, content: outContent }), changed };
802
- }
803
- function compactCompressText(text) {
804
- const parsed = parseCallText(text);
805
- if (!parsed) return null;
806
- const { prefix, obj, content, contentWasString } = parsed;
807
- const { text: out, changed } = serializeCompacted(obj, content, contentWasString);
808
- return changed ? prefix + out : null;
809
- }
810
- function hideConsumedCompressCalls(state, messages) {
811
- const allBlockCallIds = /* @__PURE__ */ new Set();
812
- const activeCallIds = /* @__PURE__ */ new Set();
813
- const liveRangeKeysByCallId = /* @__PURE__ */ new Map();
814
- const legacyLiveByCallId = /* @__PURE__ */ new Set();
815
- for (const block of state.blocks) {
816
- if (!block.compressCallId) continue;
817
- allBlockCallIds.add(block.compressCallId);
818
- if (!block.active) continue;
819
- activeCallIds.add(block.compressCallId);
820
- if (block.startRef === void 0 || block.endRef === void 0) {
821
- legacyLiveByCallId.add(block.compressCallId);
822
- continue;
823
- }
824
- let keys = liveRangeKeysByCallId.get(block.compressCallId);
825
- if (!keys) {
826
- keys = /* @__PURE__ */ new Set();
827
- liveRangeKeysByCallId.set(block.compressCallId, keys);
828
- }
829
- keys.add(rangeKey(block.startRef, block.endRef));
830
- }
831
- const lastOrphanedCallIds = [];
832
- for (let i = messages.length - 1; i >= 0 && lastOrphanedCallIds.length < KEEP_LAST_ORPHANED; i--) {
833
- const message = messages[i];
834
- if (message.toolName !== "compress" || message.contentType !== "tool-call") continue;
835
- const callId = message.toolCallId;
836
- if (callId && !allBlockCallIds.has(callId)) {
837
- lastOrphanedCallIds.push(callId);
838
- }
839
- }
840
- const keepCallIds = /* @__PURE__ */ new Set([...activeCallIds, ...lastOrphanedCallIds]);
841
- const hiddenCallIds = /* @__PURE__ */ new Set();
842
- for (const message of messages) {
843
- if (message.toolName === "compress" && message.contentType === "tool-call" && (!message.toolCallId || !keepCallIds.has(message.toolCallId))) {
844
- if (message.toolCallId) hiddenCallIds.add(message.toolCallId);
845
- }
846
- }
847
- let hidden = 0;
848
- const result = [];
849
- for (const message of messages) {
850
- if (message.toolName === "compress" && message.contentType === "tool-call" && (!message.toolCallId || !keepCallIds.has(message.toolCallId))) {
851
- hidden++;
852
- continue;
853
- }
854
- if (message.contentType === "tool-result" && message.toolCallId && hiddenCallIds.has(message.toolCallId)) {
855
- hidden++;
856
- continue;
857
- }
858
- if (message.toolName === "compress" && message.contentType === "tool-call" && message.toolCallId && keepCallIds.has(message.toolCallId)) {
859
- const liveKeys = liveRangeKeysByCallId.get(message.toolCallId);
860
- if (liveKeys && liveKeys.size > 0 && !legacyLiveByCallId.has(message.toolCallId)) {
861
- const rewritten = rewriteCompressText(message.text, liveKeys);
862
- if (rewritten !== null) {
863
- result.push({ ...message, text: rewritten });
864
- continue;
865
- }
866
- }
867
- const compacted = compactCompressText(message.text);
868
- if (compacted !== null) {
869
- result.push({ ...message, text: compacted });
870
- continue;
871
- }
872
- }
873
- result.push(message);
874
- }
875
- return { messages: result, hidden };
876
- }
877
-
878
- // src/surface-config.ts
879
- function applySectionOverrides(sections, overrides) {
880
- const out = [];
881
- for (const [key, text] of sections) {
882
- const o = overrides?.[key];
883
- if (o === null) continue;
884
- out.push(typeof o === "string" ? o : text);
885
- }
886
- return out;
887
- }
888
- function cloneWithDescriptions(schema, overrides) {
889
- if (Object.keys(overrides).length === 0) return schema;
890
- return cloneNode(schema, overrides);
891
- }
892
- function cloneNode(node, overrides) {
893
- if (Array.isArray(node)) return node.map((item) => cloneNode(item, overrides));
894
- if (node && typeof node === "object") {
895
- const out = {};
896
- for (const [name, value] of Object.entries(node)) {
897
- const cloned = cloneNode(value, overrides);
898
- if (Object.hasOwn(overrides, name) && cloned && typeof cloned === "object" && !Array.isArray(cloned)) {
899
- cloned.description = overrides[name];
900
- }
901
- out[name] = cloned;
902
- }
903
- return out;
904
- }
905
- return node;
542
+ return node;
906
543
  }
907
544
  function applyAcpToolOverrides(tools, overrides) {
908
545
  if (!overrides) return [...tools];
@@ -942,6 +579,7 @@ var SEARCH_CONTEXT_TOOL_NAME = "search_context";
942
579
  var ACP_STATUS_TOOL_NAME = "acp_status";
943
580
  var ACP_CACHE_TOOL_NAME = "acp_cache";
944
581
  var ABSORB_TOOL_NAME = "absorb";
582
+ var IMAGE_FULL_TOOL_NAME = "image_full";
945
583
  var ACP_TEXT_OPEN = "<acp_compress>";
946
584
  var ACP_TEXT_CLOSE = "</acp_compress>";
947
585
  var ACP_STATUS_OPEN = "<acp_status>";
@@ -1475,6 +1113,67 @@ var ABSORB_TOOL_GOOGLE = {
1475
1113
  description: ABSORB_TOOL_DESCRIPTION,
1476
1114
  parameters: ABSORB_PARAMETERS
1477
1115
  };
1116
+ var IMAGE_FULL_TOOL_DESCRIPTION = 'Restore original-resolution images for a previously downscaled message. Call when you cannot read details (text, colors, alignment) in a reduced image: pass the message ref ("mNNNNN") from the [Downscaled screenshots] note. Full resolution applies for the rest of this session.';
1117
+ var IMAGE_FULL_PARAMETERS = {
1118
+ type: "object",
1119
+ properties: {
1120
+ ref: {
1121
+ type: "string",
1122
+ description: "mNNNNN ref of the message whose image(s) should be restored to full resolution"
1123
+ }
1124
+ },
1125
+ required: ["ref"]
1126
+ };
1127
+ var RETRIEVE_TOOL_NAME = "acp_retrieve";
1128
+ var RETRIEVE_TOOL_DESCRIPTION = "Retrieve the full original text of a stored tool result. REQUIRED when you need details from a placeholder that starts with \u{1F4E6} [acp-stored #mNNNNN: pass its ref. Returns the exact original text; the placeholder stays in context. Unknown refs return not-found.";
1129
+ var RETRIEVE_PARAMETERS = {
1130
+ type: "object",
1131
+ properties: {
1132
+ ref: {
1133
+ type: "string",
1134
+ description: "mNNNNN ref shown in the \u{1F4E6} [acp-stored placeholder"
1135
+ }
1136
+ },
1137
+ required: ["ref"]
1138
+ };
1139
+ var IMAGE_FULL_TOOL = {
1140
+ name: IMAGE_FULL_TOOL_NAME,
1141
+ description: IMAGE_FULL_TOOL_DESCRIPTION,
1142
+ input_schema: IMAGE_FULL_PARAMETERS
1143
+ };
1144
+ var IMAGE_FULL_TOOL_OPENAI = {
1145
+ type: "function",
1146
+ function: {
1147
+ name: IMAGE_FULL_TOOL_NAME,
1148
+ description: IMAGE_FULL_TOOL_DESCRIPTION,
1149
+ parameters: IMAGE_FULL_PARAMETERS
1150
+ }
1151
+ };
1152
+ var IMAGE_FULL_TOOL_RESPONSES = {
1153
+ type: "function",
1154
+ name: IMAGE_FULL_TOOL_OPENAI.function.name,
1155
+ description: IMAGE_FULL_TOOL_OPENAI.function.description,
1156
+ parameters: IMAGE_FULL_TOOL_OPENAI.function.parameters
1157
+ };
1158
+ var RETRIEVE_TOOL = {
1159
+ name: RETRIEVE_TOOL_NAME,
1160
+ description: RETRIEVE_TOOL_DESCRIPTION,
1161
+ input_schema: RETRIEVE_PARAMETERS
1162
+ };
1163
+ var RETRIEVE_TOOL_OPENAI = {
1164
+ type: "function",
1165
+ function: {
1166
+ name: RETRIEVE_TOOL_NAME,
1167
+ description: RETRIEVE_TOOL_DESCRIPTION,
1168
+ parameters: RETRIEVE_PARAMETERS
1169
+ }
1170
+ };
1171
+ var RETRIEVE_TOOL_RESPONSES = {
1172
+ type: "function",
1173
+ name: RETRIEVE_TOOL_NAME,
1174
+ description: RETRIEVE_TOOL_DESCRIPTION,
1175
+ parameters: RETRIEVE_PARAMETERS
1176
+ };
1478
1177
 
1479
1178
  // src/protected.ts
1480
1179
  var ALWAYS_PROTECTED_TOOLS = ["compress", "acp_rule"];
@@ -1552,223 +1251,2268 @@ function isMessageLatestProtected(msg, latest) {
1552
1251
  return false;
1553
1252
  }
1554
1253
 
1555
- // src/absorb.ts
1556
- var ABSORB_PROMPT_MARKER = "[ACP absorb]";
1557
- var DEFAULT_ABSORB_CONFIG = {
1254
+ // src/content-store.ts
1255
+ import { createHash } from "crypto";
1256
+ function createContentStore() {
1257
+ return { version: 1, byHash: {}, byRef: {} };
1258
+ }
1259
+ function hashContent(text) {
1260
+ return createHash("sha256").update(text, "utf8").digest("hex");
1261
+ }
1262
+ function storeOriginal(store, spec) {
1263
+ if (!spec.ref || store.byRef[spec.ref]) return store;
1264
+ const hash = hashContent(spec.text);
1265
+ const entry = {
1266
+ hash,
1267
+ rawId: spec.rawId,
1268
+ kind: spec.kind,
1269
+ tokens: spec.tokens,
1270
+ chars: spec.text.length,
1271
+ head: spec.head
1272
+ };
1273
+ if (spec.toolName !== void 0) entry.toolName = spec.toolName;
1274
+ const byHash = store.byHash[hash] === void 0 ? { ...store.byHash, [hash]: spec.text } : store.byHash;
1275
+ return { ...store, byHash, byRef: { ...store.byRef, [spec.ref]: entry } };
1276
+ }
1277
+ function retrieveByRef(store, ref) {
1278
+ const entry = store.byRef[ref];
1279
+ if (!entry) return { ok: false, reason: "not-found" };
1280
+ const text = store.byHash[entry.hash];
1281
+ if (text === void 0) return { ok: false, reason: "not-found" };
1282
+ return { ok: true, text, entry };
1283
+ }
1284
+ function hasStoredRef(store, ref) {
1285
+ return store.byRef[ref] !== void 0;
1286
+ }
1287
+ function contentStoreStats(store) {
1288
+ let totalChars = 0;
1289
+ for (const text of Object.values(store.byHash)) totalChars += text.length;
1290
+ return {
1291
+ entries: Object.keys(store.byRef).length,
1292
+ uniqueContents: Object.keys(store.byHash).length,
1293
+ totalChars
1294
+ };
1295
+ }
1296
+
1297
+ // src/ccr.ts
1298
+ var RETRIEVE_TOOL_NAME2 = "acp_retrieve";
1299
+ var DEFAULT_CCR_CONFIG = {
1558
1300
  enabled: false,
1559
- toolName: ABSORB_TOOL_NAME,
1560
- minToolTokens: 1e3,
1561
- contextThresholdPct: 0,
1562
- excludeTools: []
1301
+ toolName: RETRIEVE_TOOL_NAME2,
1302
+ minToolTokens: 4e3,
1303
+ excludeTools: [],
1304
+ maxHeadChars: 96
1563
1305
  };
1564
- function resolveAbsorbConfig(config) {
1565
- return { ...DEFAULT_ABSORB_CONFIG, ...config.absorb ?? {} };
1306
+ function resolveCcrConfig(config) {
1307
+ return { ...DEFAULT_CCR_CONFIG, ...config.ccr };
1308
+ }
1309
+ var STORED_PLACEHOLDER_MARKER = "[acp-stored";
1310
+ var RETRIEVED_ID_PREFIX = "acp_retrieved_";
1311
+ var KIND_LABELS = {
1312
+ bash: "shell output",
1313
+ shell: "shell output",
1314
+ exec: "shell output",
1315
+ execute_command: "shell output",
1316
+ run: "shell output",
1317
+ terminal: "shell output",
1318
+ read: "file read",
1319
+ read_file: "file read",
1320
+ cat: "file read",
1321
+ open: "file read",
1322
+ grep: "search output",
1323
+ rg: "search output",
1324
+ search: "search output",
1325
+ glob: "search output",
1326
+ find: "search output",
1327
+ webfetch: "web fetch",
1328
+ web_fetch: "web fetch",
1329
+ fetch: "web fetch",
1330
+ curl: "web fetch"
1331
+ };
1332
+ function classifyKind(toolName) {
1333
+ if (!toolName) return "tool result";
1334
+ return KIND_LABELS[toolName.toLowerCase()] ?? "tool result";
1335
+ }
1336
+ function groupThousands(value) {
1337
+ return String(Math.max(0, Math.round(value))).replace(
1338
+ /\B(?=(\d{3})+(?!\d))/g,
1339
+ ","
1340
+ );
1566
1341
  }
1567
- function formatTokenCount(tokens) {
1568
- if (tokens < 1e3) return String(tokens);
1569
- if (tokens < 1e4) return (tokens / 1e3).toFixed(1) + "K";
1570
- return Math.round(tokens / 1e3) + "K";
1342
+ function normalizeHead(text, maxChars) {
1343
+ const singleLine = (text || "").replace(/\s+/g, " ").trim();
1344
+ if (singleLine.length <= maxChars) return singleLine;
1345
+ return singleLine.slice(0, maxChars) + "\u2026";
1571
1346
  }
1572
- function buildAbsorbPrompt(ref, tokens, toolName = ABSORB_TOOL_NAME) {
1573
- 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)".`;
1347
+ var COMMAND_FIELDS = ["command", "cmd", "script", "query", "path", "url"];
1348
+ function extractCommand(toolCallText, maxChars) {
1349
+ if (!toolCallText) return void 0;
1350
+ let parsed;
1351
+ try {
1352
+ parsed = JSON.parse(toolCallText);
1353
+ } catch {
1354
+ return void 0;
1355
+ }
1356
+ if (typeof parsed !== "object" || parsed === null) return void 0;
1357
+ const record = parsed;
1358
+ for (const field of COMMAND_FIELDS) {
1359
+ const value = record[field];
1360
+ if (typeof value === "string" && value.trim().length > 0) {
1361
+ const normalized = value.replace(/\s+/g, " ").trim();
1362
+ return normalized.length <= maxChars ? normalized : normalized.slice(0, maxChars) + "\u2026";
1363
+ }
1364
+ }
1365
+ return void 0;
1574
1366
  }
1575
- function buildAbsorbSystemPrompt(toolName = ABSORB_TOOL_NAME) {
1576
- return `INSTANT TOOL-RESULT ABSORPTION (${toolName})
1577
-
1578
- Some tool results end with a ${ABSORB_PROMPT_MARKER} instruction. When you see one, your IMMEDIATE next action must be calling ${toolName}({ ref, summary }) \u2014 distill that tool result's essentials into summary: outcome, key values, exact paths:lines, error text verbatim, decisions. The original output is then removed from context; your ${toolName} summary becomes the only durable record of it, so distill carefully. Never call another tool or answer the user before absorbing a marked result. Do not re-run the original tool afterwards \u2014 work from your summary. ${toolName} calls are ordinary context: the regular compression system may fold them later like any other message.`;
1367
+ function buildStoredPlaceholder(input) {
1368
+ const title = input.command ?? input.head;
1369
+ const titlePart = title ? ` \`${title}\`` : "";
1370
+ return `\u{1F4E6} ${STORED_PLACEHOLDER_MARKER} #${input.ref} \xB7 ${input.kind} \xB7 ${groupThousands(input.tokens)} tok]${titlePart}
1371
+ \u2192 ${input.retrieveToolName}("${input.ref}") returns the full text`;
1579
1372
  }
1580
- function isAcpOrConfiguredTool(toolName, cfg) {
1581
- if (!toolName) return false;
1582
- if (toolName === cfg.toolName) return true;
1583
- return ACP_TOOL_NAMES.has(toolName);
1373
+ var LEADING_TAG_RE = new RegExp("^\\x3cacp [^>]*>[^\\x3c]*\\x3c\\/acp>\\n?");
1374
+ function stripLeadingTag(text) {
1375
+ return text.replace(LEADING_TAG_RE, "");
1584
1376
  }
1585
- function isAbsorbCandidate(msg, config, latest) {
1586
- if (msg.contentType !== "tool-result" || !msg.toolCallId) return false;
1587
- const cfg = resolveAbsorbConfig(config);
1588
- if (isAcpOrConfiguredTool(msg.toolName, cfg)) return false;
1589
- if (isMessageProtected(msg, config)) return false;
1590
- if (latest && isMessageLatestProtected(msg, latest)) return false;
1591
- for (const pattern of cfg.excludeTools) {
1592
- if (msg.toolName && matchToolPattern(msg.toolName, pattern)) return false;
1593
- }
1594
- return true;
1377
+ function isStoredPlaceholderText(text) {
1378
+ return stripLeadingTag(text).includes(STORED_PLACEHOLDER_MARKER);
1595
1379
  }
1596
- function hideAbsorbedMessages(messages, state) {
1597
- const records = state.absorbed ?? [];
1598
- if (records.length === 0) return messages;
1599
- const hidden = /* @__PURE__ */ new Set();
1600
- for (const record of records) {
1601
- if (record.callMessageId) hidden.add(record.callMessageId);
1602
- if (record.resultMessageId) hidden.add(record.resultMessageId);
1603
- }
1604
- return messages.filter((msg) => !hidden.has(msg.id));
1380
+ function retrievedMessageId(ref) {
1381
+ return RETRIEVED_ID_PREFIX + ref;
1605
1382
  }
1606
- function appendAbsorbPrompts(messages, state, config, tokenCount, countTokens) {
1607
- const cfg = resolveAbsorbConfig(config);
1608
- if (!cfg.enabled) return { messages, promptedCount: 0 };
1609
- const limit = config.modelContextLimit;
1610
- if (cfg.contextThresholdPct > 0 && limit > 0 && tokenCount < cfg.contextThresholdPct * limit) {
1611
- return { messages, promptedCount: 0 };
1383
+ function isRetrievedMessage(message) {
1384
+ return message.id.startsWith(RETRIEVED_ID_PREFIX) && message.role === "system" && message.contentType === "text";
1385
+ }
1386
+ function buildRetrievalInjection(ref, entry, text) {
1387
+ const header = `[acp-retrieved #${ref} \xB7 ${entry.kind} \xB7 ${groupThousands(entry.tokens)} tok]`;
1388
+ return {
1389
+ ackText: `retrieved ${ref}: ${groupThousands(entry.tokens)} tok (${entry.chars} chars)`,
1390
+ injection: {
1391
+ id: retrievedMessageId(ref),
1392
+ role: "system",
1393
+ contentType: "text",
1394
+ text: `${header}
1395
+ ${text}`
1396
+ }
1397
+ };
1398
+ }
1399
+ function applyRetrieve(input) {
1400
+ const ref = input.ref.trim();
1401
+ const found = retrieveByRef(input.store, ref);
1402
+ if (!found.ok) {
1403
+ return {
1404
+ ok: false,
1405
+ reason: "not-found",
1406
+ ackText: `retrieve ${ref}: not found \u2014 no stored original for this ref`
1407
+ };
1612
1408
  }
1613
- const absorbedIds = /* @__PURE__ */ new Set();
1614
- for (const record of state.absorbed ?? []) {
1615
- if (record.resultMessageId) absorbedIds.add(record.resultMessageId);
1409
+ const built = buildRetrievalInjection(ref, found.entry, found.text);
1410
+ return {
1411
+ ok: true,
1412
+ text: found.text,
1413
+ ackText: built.ackText,
1414
+ injection: built.injection,
1415
+ entry: found.entry
1416
+ };
1417
+ }
1418
+ function storeLargeResults(input) {
1419
+ const cfg = resolveCcrConfig(input.config);
1420
+ if (!cfg.enabled)
1421
+ return { messages: input.messages, store: input.store, storedCount: 0 };
1422
+ let current = input.store;
1423
+ let storedCount = 0;
1424
+ const callById = /* @__PURE__ */ new Map();
1425
+ for (const message of input.messages) {
1426
+ if (message.contentType === "tool-call" && message.toolCallId) {
1427
+ callById.set(message.toolCallId, message);
1428
+ }
1616
1429
  }
1617
- let promptedCount = 0;
1618
- const latest = collectLatestProtected(messages, config);
1619
- const out = messages.map((msg) => {
1620
- if (!isAbsorbCandidate(msg, config, latest)) return msg;
1621
- if (absorbedIds.has(msg.id)) return msg;
1622
- const text = msg.text ?? "";
1623
- if (text.includes(ABSORB_PROMPT_MARKER)) return msg;
1624
- const tokens = countTokens(text);
1625
- if (tokens < cfg.minToolTokens) return msg;
1626
- const ref = refForRaw(state.messageRefs, msg.id);
1627
- if (!ref || ref === BLOCKED_REF) return msg;
1628
- promptedCount++;
1430
+ const updated = input.messages.map((message) => {
1431
+ if (message.contentType !== "tool-result") return message;
1432
+ const text = message.text ?? "";
1433
+ if (text.length === 0) return message;
1434
+ if (isStoredPlaceholderText(text)) return message;
1435
+ if (!message.toolCallId) return message;
1436
+ const toolName = message.toolName;
1437
+ if (toolName && (ACP_TOOL_NAMES.has(toolName) || toolName === cfg.toolName)) {
1438
+ return message;
1439
+ }
1440
+ if (toolName && cfg.excludeTools.some((pattern) => matchToolPattern(toolName, pattern))) {
1441
+ return message;
1442
+ }
1443
+ if (isMessageProtected(message, input.config)) return message;
1444
+ const tokens = input.countTokens(text);
1445
+ if (tokens < cfg.minToolTokens) return message;
1446
+ const ref = refForRaw(input.state.messageRefs, message.id);
1447
+ if (!ref || ref === BLOCKED_REF) return message;
1448
+ if (hasStoredRef(current, ref)) return message;
1449
+ const kind = classifyKind(toolName);
1450
+ const head = normalizeHead(text, cfg.maxHeadChars);
1451
+ const command = extractCommand(
1452
+ callById.get(message.toolCallId)?.text,
1453
+ cfg.maxHeadChars
1454
+ );
1455
+ current = storeOriginal(current, {
1456
+ ref,
1457
+ rawId: message.id,
1458
+ text,
1459
+ kind,
1460
+ toolName,
1461
+ tokens,
1462
+ head
1463
+ });
1464
+ storedCount += 1;
1629
1465
  return {
1630
- ...msg,
1631
- text: text + "\n\n" + buildAbsorbPrompt(ref, tokens, cfg.toolName)
1466
+ ...message,
1467
+ text: buildStoredPlaceholder({
1468
+ ref,
1469
+ kind,
1470
+ tokens,
1471
+ head,
1472
+ command,
1473
+ retrieveToolName: cfg.toolName
1474
+ })
1632
1475
  };
1633
1476
  });
1634
- return { messages: out, promptedCount };
1477
+ return { messages: updated, store: current, storedCount };
1635
1478
  }
1636
- function parseAbsorbInput(input, callId, onWarn) {
1637
- let obj = null;
1638
- if (typeof input === "string") {
1639
- try {
1640
- const parsed = JSON.parse(input);
1641
- if (parsed && typeof parsed === "object") {
1642
- obj = parsed;
1643
- }
1644
- } catch {
1645
- obj = null;
1479
+ var ccrStoreNode = {
1480
+ name: "ccr-store",
1481
+ enabled: (_io, ctx) => resolveCcrConfig(ctx.config).enabled,
1482
+ run(io, ctx) {
1483
+ const applied = storeLargeResults({
1484
+ messages: io.messages,
1485
+ state: io.state,
1486
+ store: ctx.contentStore,
1487
+ config: ctx.config,
1488
+ countTokens: ctx.countTokens
1489
+ });
1490
+ const effect = {
1491
+ store: applied.store,
1492
+ storedCount: applied.storedCount
1493
+ };
1494
+ const stats = applied.storedCount > 0 ? {
1495
+ ...io.state.stats,
1496
+ storedCount: (io.state.stats.storedCount ?? 0) + applied.storedCount
1497
+ } : io.state.stats;
1498
+ return {
1499
+ ...io,
1500
+ messages: applied.messages,
1501
+ state: stats === io.state.stats ? io.state : { ...io.state, stats },
1502
+ effects: { ...io.effects, ccr: effect }
1503
+ };
1504
+ }
1505
+ };
1506
+ function storeCoveredOriginals(store, messages, state, blockIds, countTokens, maxHeadChars = DEFAULT_CCR_CONFIG.maxHeadChars) {
1507
+ const covered = /* @__PURE__ */ new Set();
1508
+ for (const block of state.blocks) {
1509
+ if (!block.active || !blockIds.includes(block.blockId)) continue;
1510
+ for (const id of block.effectiveMessageIds) covered.add(id);
1511
+ }
1512
+ let current = store;
1513
+ for (const message of messages) {
1514
+ if (!covered.has(message.id)) continue;
1515
+ if (message.contentType === "reasoning") continue;
1516
+ const text = stripLeadingTag(message.text ?? "");
1517
+ if (text.length === 0) continue;
1518
+ const ref = refForRaw(state.messageRefs, message.id);
1519
+ if (!ref || ref === BLOCKED_REF) continue;
1520
+ if (isStoredPlaceholderText(text) && hasStoredRef(current, ref)) continue;
1521
+ current = storeOriginal(current, {
1522
+ ref,
1523
+ rawId: message.id,
1524
+ text,
1525
+ kind: classifyKind(message.toolName),
1526
+ toolName: message.toolName,
1527
+ tokens: countTokens(text),
1528
+ head: normalizeHead(text, maxHeadChars)
1529
+ });
1530
+ }
1531
+ return current;
1532
+ }
1533
+ function noteRetrieval(state) {
1534
+ return {
1535
+ ...state,
1536
+ stats: {
1537
+ ...state.stats,
1538
+ retrievalCount: (state.stats.retrievalCount ?? 0) + 1
1539
+ }
1540
+ };
1541
+ }
1542
+
1543
+ // src/boundaries.ts
1544
+ var MESSAGE_REF_PATTERN = /^m0*(\d{1,5})$/;
1545
+ var BLOCK_REF_PATTERN = /^b(\d{1,9})$/;
1546
+ function parseBoundary(ref) {
1547
+ const normalized = ref.trim().toLowerCase();
1548
+ const messageMatch = MESSAGE_REF_PATTERN.exec(normalized);
1549
+ if (messageMatch) {
1550
+ const numericId = Number(messageMatch[1]);
1551
+ if (numericId >= 1 && numericId <= 99999) {
1552
+ return { kind: "message", numericId, raw: normalized };
1646
1553
  }
1647
- } else if (input && typeof input === "object") {
1648
- obj = input;
1649
1554
  }
1650
- if (!obj) {
1651
- onWarn?.(`[acp-absorb-input] rejected: not an object (${typeof input})`);
1652
- return null;
1555
+ const blockMatch = BLOCK_REF_PATTERN.exec(normalized);
1556
+ if (blockMatch) {
1557
+ const numericId = Number(blockMatch[1]);
1558
+ if (numericId >= 1) return { kind: "block", numericId, raw: normalized };
1653
1559
  }
1654
- const ref = pickString(obj, "ref", "messageId", "of");
1655
- const summary = pickString(obj, "summary", "content");
1656
- if (typeof ref !== "string" || typeof summary !== "string") {
1657
- onWarn?.(
1658
- `[acp-absorb-input] rejected: need ref (string) + summary (string); keys: ${Object.keys(obj).join(",")}`
1560
+ return null;
1561
+ }
1562
+ var BoundaryNotFoundError = class extends Error {
1563
+ code = "BOUNDARY_NOT_FOUND";
1564
+ kind;
1565
+ endpoint;
1566
+ constructor(kind, endpoint, message) {
1567
+ super(message);
1568
+ this.name = "BoundaryNotFoundError";
1569
+ this.code = "BOUNDARY_NOT_FOUND";
1570
+ this.kind = kind;
1571
+ this.endpoint = endpoint;
1572
+ }
1573
+ };
1574
+ function resolveBoundaries(input) {
1575
+ const start = parseBoundary(input.startRef);
1576
+ const end = parseBoundary(input.endRef);
1577
+ if (!start || !end) {
1578
+ throw new Error(
1579
+ `Invalid boundary ref(s): startId="${input.startRef}", endId="${input.endRef}". Use mNNNNN or bN.`
1659
1580
  );
1660
- return null;
1661
1581
  }
1582
+ const indexByMessageId = /* @__PURE__ */ new Map();
1583
+ input.messages.forEach(
1584
+ (message, index) => indexByMessageId.set(message.id, index)
1585
+ );
1586
+ let snappedBoundaries = [];
1587
+ const startAnchor = resolveAnchorIndex(
1588
+ start,
1589
+ input.state,
1590
+ indexByMessageId,
1591
+ "start"
1592
+ );
1593
+ if (startAnchor.snapped) snappedBoundaries.push(startAnchor.snapped);
1594
+ const endAnchor = resolveAnchorIndex(
1595
+ end,
1596
+ input.state,
1597
+ indexByMessageId,
1598
+ "end"
1599
+ );
1600
+ if (endAnchor.snapped) snappedBoundaries.push(endAnchor.snapped);
1601
+ let startIndex = startAnchor.index;
1602
+ let endIndex = endAnchor.index;
1603
+ if (startIndex > endIndex) {
1604
+ [startIndex, endIndex] = [endIndex, startIndex];
1605
+ }
1606
+ const messageIds = [];
1607
+ for (let index = startIndex; index <= endIndex; index++) {
1608
+ const message = input.messages[index];
1609
+ if (message && !isRenderedSummaryMessage(message) && !isRetrievedMessage(message))
1610
+ messageIds.push(message.id);
1611
+ }
1612
+ const boundaryKind = start.kind === "block" || end.kind === "block" ? "block" : "message";
1613
+ const nestedBlockIds = [];
1614
+ const nestedSeen = /* @__PURE__ */ new Set();
1615
+ for (const block of activeBlocks(input.state)) {
1616
+ if (blockVisibleInRange(block, indexByMessageId, startIndex, endIndex)) {
1617
+ if (!nestedSeen.has(block.blockId)) {
1618
+ nestedSeen.add(block.blockId);
1619
+ nestedBlockIds.push(block.blockId);
1620
+ }
1621
+ }
1622
+ }
1623
+ const protectedGaps = [];
1662
1624
  return {
1663
- ref: ref.trim(),
1664
- summary,
1665
- ...callId ? { absorbCallId: callId } : {}
1625
+ startIndex,
1626
+ endIndex,
1627
+ messageIds,
1628
+ nestedBlockIds,
1629
+ boundaryKind,
1630
+ protectedGaps,
1631
+ snappedBoundaries
1666
1632
  };
1667
1633
  }
1668
- function pickString(obj, ...keys) {
1669
- for (const key of keys) {
1670
- const value = obj[key];
1671
- if (typeof value === "string") return value;
1672
- }
1673
- return void 0;
1634
+ function resolveAnchorIndex(boundary, state, indexByMessageId, endpoint) {
1635
+ const label = endpoint === "start" ? "startId" : "endId";
1636
+ if (boundary.kind === "message") {
1637
+ const rawId = state.messageRefs.byRef[boundary.raw] ?? state.messageRefs.byRef[formatPaddedRef(boundary.numericId)];
1638
+ if (!rawId) {
1639
+ throw new BoundaryNotFoundError(
1640
+ "unknown",
1641
+ endpoint,
1642
+ `${label}="${boundary.raw}" does not exist in this session (typo or wrong session) \u2014 run acp_status for current refs.`
1643
+ );
1644
+ }
1645
+ const index = indexByMessageId.get(rawId);
1646
+ if (index !== void 0) {
1647
+ return { index, snapped: null };
1648
+ }
1649
+ const owner2 = activeOwnerAnchor(state, [rawId], indexByMessageId);
1650
+ if (owner2 !== null) {
1651
+ return {
1652
+ index: owner2,
1653
+ snapped: `${label}="${boundary.raw}" refers to a message already compressed into an active block \u2014 anchored to the active block covering it instead.`
1654
+ };
1655
+ }
1656
+ throw new BoundaryNotFoundError(
1657
+ "consumed",
1658
+ endpoint,
1659
+ `${label}="${boundary.raw}" not found in visible context (likely consumed by an existing block).`
1660
+ );
1661
+ }
1662
+ const block = blockById(state, `b${boundary.numericId}`);
1663
+ if (!block) {
1664
+ throw new BoundaryNotFoundError(
1665
+ "unknown",
1666
+ endpoint,
1667
+ `${label}="b${boundary.numericId}" does not exist in this session (typo or wrong session) \u2014 run acp_status for current refs.`
1668
+ );
1669
+ }
1670
+ if (block.active) {
1671
+ const anchor = visibleBlockAnchor(block, indexByMessageId);
1672
+ if (anchor !== null) {
1673
+ return { index: anchor, snapped: null };
1674
+ }
1675
+ }
1676
+ const owner = activeOwnerAnchor(
1677
+ state,
1678
+ block.effectiveMessageIds,
1679
+ indexByMessageId
1680
+ );
1681
+ if (owner !== null) {
1682
+ return {
1683
+ index: owner,
1684
+ snapped: `${label}="b${boundary.numericId}" was consumed by a higher-tier block \u2014 anchored to the active block covering its content instead.`
1685
+ };
1686
+ }
1687
+ if (!block.active) {
1688
+ throw new BoundaryNotFoundError(
1689
+ "consumed",
1690
+ endpoint,
1691
+ `${label}="b${boundary.numericId}" not found in visible context (block distilled/consumed by a higher-tier block).`
1692
+ );
1693
+ }
1694
+ throw new BoundaryNotFoundError(
1695
+ "consumed",
1696
+ endpoint,
1697
+ `${label}="b${boundary.numericId}" is an active block but none of its content (raw messages or rendered summary) is visible in the current context \u2014 run acp_status to verify.`
1698
+ );
1699
+ }
1700
+ function activeOwnerAnchor(state, ownedIds, indexByMessageId) {
1701
+ if (ownedIds.length === 0) return null;
1702
+ const owned = new Set(ownedIds);
1703
+ let best = null;
1704
+ for (const block of state.blocks) {
1705
+ if (!block.active) continue;
1706
+ const inherited = inheritedContentIds(state, block);
1707
+ let ownsInherited = false;
1708
+ for (const id of owned) {
1709
+ if (inherited.has(id)) {
1710
+ ownsInherited = true;
1711
+ break;
1712
+ }
1713
+ }
1714
+ if (!ownsInherited) continue;
1715
+ const anchor = visibleBlockAnchor(block, indexByMessageId);
1716
+ if (anchor === null) continue;
1717
+ if (best === null || anchor < best) {
1718
+ best = anchor;
1719
+ }
1720
+ }
1721
+ return best;
1722
+ }
1723
+ function inheritedContentIds(state, block) {
1724
+ const ids = /* @__PURE__ */ new Set();
1725
+ for (const childId of block.directBlockIds) {
1726
+ const child = blockById(state, childId);
1727
+ if (!child) continue;
1728
+ for (const id of child.effectiveMessageIds) ids.add(id);
1729
+ }
1730
+ return ids;
1731
+ }
1732
+ function formatPaddedRef(index) {
1733
+ return `m${String(index).padStart(5, "0")}`;
1734
+ }
1735
+ function visibleBlockAnchor(block, indexByMessageId) {
1736
+ const summaryIndex = indexByMessageId.get(summaryMessageId(block.blockId));
1737
+ if (summaryIndex !== void 0) return summaryIndex;
1738
+ return earliestIndexOfIds(block.effectiveMessageIds, indexByMessageId);
1739
+ }
1740
+ function blockVisibleInRange(block, indexByMessageId, startIndex, endIndex) {
1741
+ const summaryIndex = indexByMessageId.get(summaryMessageId(block.blockId));
1742
+ if (summaryIndex !== void 0 && summaryIndex >= startIndex && summaryIndex <= endIndex) {
1743
+ return true;
1744
+ }
1745
+ const rawIndex = earliestIndexOfIds(
1746
+ block.effectiveMessageIds,
1747
+ indexByMessageId
1748
+ );
1749
+ return rawIndex !== null && rawIndex >= startIndex && rawIndex <= endIndex;
1750
+ }
1751
+ function earliestIndexOfIds(ids, indexByMessageId) {
1752
+ let earliest = null;
1753
+ for (const id of ids) {
1754
+ const index = indexByMessageId.get(id);
1755
+ if (index !== void 0 && (earliest === null || index < earliest)) {
1756
+ earliest = index;
1757
+ }
1758
+ }
1759
+ return earliest;
1760
+ }
1761
+
1762
+ // src/truncate-tools.ts
1763
+ var TRUNCATION_MARKER = "[truncated for context space]";
1764
+ var DEFAULTS = {
1765
+ minOutputTokens: 1e3,
1766
+ keepPrefixChars: 2e3,
1767
+ keepSuffixChars: 2e3,
1768
+ protectRecentMessages: 3
1769
+ };
1770
+ function truncateLargeToolOutputs(messages, tokenCount, config, countTokens, options = {}) {
1771
+ const opts = { ...DEFAULTS, ...options };
1772
+ const limit = config.modelContextLimit;
1773
+ if (limit <= 0 || tokenCount < config.truncate.threshold * limit) {
1774
+ return { messages, truncatedCount: 0, savedTokens: 0, candidatesFound: 0 };
1775
+ }
1776
+ const protectedIndex = messages.length - opts.protectRecentMessages;
1777
+ const findCandidates = (predicate) => {
1778
+ const found = [];
1779
+ for (let i = 0; i < protectedIndex; i++) {
1780
+ const message = messages[i];
1781
+ if (!predicate(message)) continue;
1782
+ const text = message.text ?? "";
1783
+ if (text.length === 0 || text.includes(TRUNCATION_MARKER)) continue;
1784
+ if (countTokens(text) < opts.minOutputTokens) continue;
1785
+ found.push(message);
1786
+ }
1787
+ return found.sort(
1788
+ (a, b) => countTokens(b.text ?? "") - countTokens(a.text ?? "")
1789
+ );
1790
+ };
1791
+ const toolResults = findCandidates((m) => m.contentType === "tool-result");
1792
+ const textMessages = options.includeTextMessages ? findCandidates(
1793
+ (m) => m.contentType === "text" && (m.role === "user" || m.role === "assistant") && !isRenderedSummaryMessage(m) && !m.text?.startsWith(SUMMARY_HEADER)
1794
+ ) : [];
1795
+ const candidatesFound = toolResults.length + textMessages.length;
1796
+ let truncatedCount = 0;
1797
+ let savedTokens = 0;
1798
+ let remaining = tokenCount;
1799
+ const targetTokens = config.truncate.threshold * limit * 0.9;
1800
+ const replacements = /* @__PURE__ */ new Map();
1801
+ const applyTo = (candidates) => {
1802
+ for (const candidate of candidates) {
1803
+ if (remaining <= targetTokens) break;
1804
+ const original = candidate.text ?? "";
1805
+ const tokens = countTokens(original);
1806
+ if (original.length <= opts.keepPrefixChars + opts.keepSuffixChars) {
1807
+ continue;
1808
+ }
1809
+ const prefix = clampPrefix(original, opts.keepPrefixChars);
1810
+ const suffix = clampWindow(original, original.length - opts.keepSuffixChars, original.length);
1811
+ const replacement = `${prefix}
1812
+
1813
+ ...${TRUNCATION_MARKER} \u2014 original ~${tokens} tokens]...
1814
+
1815
+ ${suffix}`;
1816
+ replacements.set(candidate.id, replacement);
1817
+ truncatedCount++;
1818
+ remaining -= tokens - countTokens(replacement);
1819
+ savedTokens += tokens - countTokens(replacement);
1820
+ }
1821
+ };
1822
+ applyTo(toolResults);
1823
+ applyTo(textMessages);
1824
+ if (replacements.size === 0) {
1825
+ return { messages, truncatedCount: 0, savedTokens: 0, candidatesFound };
1826
+ }
1827
+ return {
1828
+ messages: messages.map(
1829
+ (m) => replacements.has(m.id) ? { ...m, text: replacements.get(m.id) } : m
1830
+ ),
1831
+ truncatedCount,
1832
+ savedTokens,
1833
+ candidatesFound
1834
+ };
1835
+ }
1836
+
1837
+ // src/hide-consumed.ts
1838
+ var KEEP_LAST_ORPHANED = 2;
1839
+ function rangeKey(startRef, endRef) {
1840
+ return `${startRef}::${endRef}`;
1841
+ }
1842
+ function parseCallText(text) {
1843
+ const raw = text ?? "";
1844
+ const start = raw.indexOf("{");
1845
+ if (start < 0) return null;
1846
+ let parsed;
1847
+ try {
1848
+ parsed = JSON.parse(raw.slice(start));
1849
+ } catch {
1850
+ return null;
1851
+ }
1852
+ if (!parsed || typeof parsed !== "object") return null;
1853
+ const obj = parsed;
1854
+ let content = null;
1855
+ let contentWasString = false;
1856
+ if (Array.isArray(obj.content)) {
1857
+ content = obj.content;
1858
+ } else if (typeof obj.content === "string") {
1859
+ contentWasString = true;
1860
+ try {
1861
+ const inner = JSON.parse(obj.content);
1862
+ if (Array.isArray(inner)) content = inner;
1863
+ } catch {
1864
+ content = null;
1865
+ }
1866
+ }
1867
+ if (!content || content.length === 0) return null;
1868
+ return { prefix: raw.slice(0, start), obj, content, contentWasString };
1869
+ }
1870
+ function rewriteCompressText(text, liveKeys) {
1871
+ const parsed = parseCallText(text);
1872
+ if (!parsed) return null;
1873
+ const { prefix, obj, content, contentWasString } = parsed;
1874
+ const kept = content.filter((entry) => {
1875
+ if (!entry || typeof entry !== "object") return false;
1876
+ const e = entry;
1877
+ const s = typeof e.startId === "string" ? e.startId : typeof e.messageId === "string" ? e.messageId : "";
1878
+ const end = typeof e.endId === "string" ? e.endId : typeof e.messageId === "string" ? e.messageId : "";
1879
+ return liveKeys.has(rangeKey(s, end));
1880
+ });
1881
+ if (kept.length === 0) return null;
1882
+ return prefix + serializeCompacted(obj, kept, contentWasString).text;
1883
+ }
1884
+ var SUMMARY_STUB_CHARS = 200;
1885
+ function compactEntry(entry) {
1886
+ if (!entry || typeof entry !== "object") return entry;
1887
+ const e = entry;
1888
+ if (typeof e.summary !== "string" || e.summary.length <= SUMMARY_STUB_CHARS) return entry;
1889
+ return { ...e, summary: `${clampPrefix(e.summary, SUMMARY_STUB_CHARS - 1)}\u2026` };
1890
+ }
1891
+ function serializeCompacted(obj, content, contentWasString) {
1892
+ let changed = false;
1893
+ const compacted = content.map((entry) => {
1894
+ const out = compactEntry(entry);
1895
+ if (out !== entry) changed = true;
1896
+ return out;
1897
+ });
1898
+ const outContent = contentWasString ? JSON.stringify(compacted) : compacted;
1899
+ return { text: JSON.stringify({ ...obj, content: outContent }), changed };
1900
+ }
1901
+ function compactCompressText(text) {
1902
+ const parsed = parseCallText(text);
1903
+ if (!parsed) return null;
1904
+ const { prefix, obj, content, contentWasString } = parsed;
1905
+ const { text: out, changed } = serializeCompacted(obj, content, contentWasString);
1906
+ return changed ? prefix + out : null;
1907
+ }
1908
+ function hideConsumedCompressCalls(state, messages) {
1909
+ const allBlockCallIds = /* @__PURE__ */ new Set();
1910
+ const activeCallIds = /* @__PURE__ */ new Set();
1911
+ const liveRangeKeysByCallId = /* @__PURE__ */ new Map();
1912
+ const legacyLiveByCallId = /* @__PURE__ */ new Set();
1913
+ for (const block of state.blocks) {
1914
+ if (!block.compressCallId) continue;
1915
+ allBlockCallIds.add(block.compressCallId);
1916
+ if (!block.active) continue;
1917
+ activeCallIds.add(block.compressCallId);
1918
+ if (block.startRef === void 0 || block.endRef === void 0) {
1919
+ legacyLiveByCallId.add(block.compressCallId);
1920
+ continue;
1921
+ }
1922
+ let keys = liveRangeKeysByCallId.get(block.compressCallId);
1923
+ if (!keys) {
1924
+ keys = /* @__PURE__ */ new Set();
1925
+ liveRangeKeysByCallId.set(block.compressCallId, keys);
1926
+ }
1927
+ keys.add(rangeKey(block.startRef, block.endRef));
1928
+ }
1929
+ const lastOrphanedCallIds = [];
1930
+ for (let i = messages.length - 1; i >= 0 && lastOrphanedCallIds.length < KEEP_LAST_ORPHANED; i--) {
1931
+ const message = messages[i];
1932
+ if (message.toolName !== "compress" || message.contentType !== "tool-call") continue;
1933
+ const callId = message.toolCallId;
1934
+ if (callId && !allBlockCallIds.has(callId)) {
1935
+ lastOrphanedCallIds.push(callId);
1936
+ }
1937
+ }
1938
+ const keepCallIds = /* @__PURE__ */ new Set([...activeCallIds, ...lastOrphanedCallIds]);
1939
+ const hiddenCallIds = /* @__PURE__ */ new Set();
1940
+ for (const message of messages) {
1941
+ if (message.toolName === "compress" && message.contentType === "tool-call" && (!message.toolCallId || !keepCallIds.has(message.toolCallId))) {
1942
+ if (message.toolCallId) hiddenCallIds.add(message.toolCallId);
1943
+ }
1944
+ }
1945
+ let hidden = 0;
1946
+ const result = [];
1947
+ for (const message of messages) {
1948
+ if (message.toolName === "compress" && message.contentType === "tool-call" && (!message.toolCallId || !keepCallIds.has(message.toolCallId))) {
1949
+ hidden++;
1950
+ continue;
1951
+ }
1952
+ if (message.contentType === "tool-result" && message.toolCallId && hiddenCallIds.has(message.toolCallId)) {
1953
+ hidden++;
1954
+ continue;
1955
+ }
1956
+ if (message.toolName === "compress" && message.contentType === "tool-call" && message.toolCallId && keepCallIds.has(message.toolCallId)) {
1957
+ const liveKeys = liveRangeKeysByCallId.get(message.toolCallId);
1958
+ if (liveKeys && liveKeys.size > 0 && !legacyLiveByCallId.has(message.toolCallId)) {
1959
+ const rewritten = rewriteCompressText(message.text, liveKeys);
1960
+ if (rewritten !== null) {
1961
+ result.push({ ...message, text: rewritten });
1962
+ continue;
1963
+ }
1964
+ }
1965
+ const compacted = compactCompressText(message.text);
1966
+ if (compacted !== null) {
1967
+ result.push({ ...message, text: compacted });
1968
+ continue;
1969
+ }
1970
+ }
1971
+ result.push(message);
1972
+ }
1973
+ return { messages: result, hidden };
1974
+ }
1975
+
1976
+ // src/absorb.ts
1977
+ var ABSORB_PROMPT_MARKER = "[ACP absorb]";
1978
+ var DEFAULT_ABSORB_CONFIG = {
1979
+ enabled: false,
1980
+ toolName: ABSORB_TOOL_NAME,
1981
+ // Raised 1000 → 4000 (issue #352): lossless CCR takes over large-result
1982
+ // handling; absorb's forced distillation only fires above the new bar.
1983
+ minToolTokens: 4e3,
1984
+ contextThresholdPct: 0,
1985
+ excludeTools: []
1986
+ };
1987
+ function resolveAbsorbConfig(config) {
1988
+ return { ...DEFAULT_ABSORB_CONFIG, ...config.absorb ?? {} };
1989
+ }
1990
+ function formatTokenCount(tokens) {
1991
+ if (tokens < 1e3) return String(tokens);
1992
+ if (tokens < 1e4) return (tokens / 1e3).toFixed(1) + "K";
1993
+ return Math.round(tokens / 1e3) + "K";
1994
+ }
1995
+ function buildAbsorbPrompt(ref, tokens, toolName = ABSORB_TOOL_NAME) {
1996
+ 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)".`;
1997
+ }
1998
+ function buildAbsorbSystemPrompt(toolName = ABSORB_TOOL_NAME) {
1999
+ return `INSTANT TOOL-RESULT ABSORPTION (${toolName})
2000
+
2001
+ Some tool results end with a ${ABSORB_PROMPT_MARKER} instruction. When you see one, your IMMEDIATE next action must be calling ${toolName}({ ref, summary }) \u2014 distill that tool result's essentials into summary: outcome, key values, exact paths:lines, error text verbatim, decisions. The original output is then removed from context; your ${toolName} summary becomes the only durable record of it, so distill carefully. Never call another tool or answer the user before absorbing a marked result. Do not re-run the original tool afterwards \u2014 work from your summary. ${toolName} calls are ordinary context: the regular compression system may fold them later like any other message.`;
2002
+ }
2003
+ function isAcpOrConfiguredTool(toolName, cfg) {
2004
+ if (!toolName) return false;
2005
+ if (toolName === cfg.toolName) return true;
2006
+ return ACP_TOOL_NAMES.has(toolName);
2007
+ }
2008
+ function isAbsorbCandidate(msg, config, latest) {
2009
+ if (msg.contentType !== "tool-result" || !msg.toolCallId) return false;
2010
+ if (isStoredPlaceholderText(msg.text ?? "")) return false;
2011
+ const cfg = resolveAbsorbConfig(config);
2012
+ if (isAcpOrConfiguredTool(msg.toolName, cfg)) return false;
2013
+ if (isMessageProtected(msg, config)) return false;
2014
+ if (latest && isMessageLatestProtected(msg, latest)) return false;
2015
+ for (const pattern of cfg.excludeTools) {
2016
+ if (msg.toolName && matchToolPattern(msg.toolName, pattern)) return false;
2017
+ }
2018
+ return true;
2019
+ }
2020
+ function hideAbsorbedMessages(messages, state) {
2021
+ const records = state.absorbed ?? [];
2022
+ if (records.length === 0) return messages;
2023
+ const hidden = /* @__PURE__ */ new Set();
2024
+ for (const record of records) {
2025
+ if (record.callMessageId) hidden.add(record.callMessageId);
2026
+ if (record.resultMessageId) hidden.add(record.resultMessageId);
2027
+ }
2028
+ return messages.filter((msg) => !hidden.has(msg.id));
2029
+ }
2030
+ function appendAbsorbPrompts(messages, state, config, tokenCount, countTokens) {
2031
+ const cfg = resolveAbsorbConfig(config);
2032
+ if (!cfg.enabled) return { messages, promptedCount: 0 };
2033
+ const limit = config.modelContextLimit;
2034
+ if (cfg.contextThresholdPct > 0 && limit > 0 && tokenCount < cfg.contextThresholdPct * limit) {
2035
+ return { messages, promptedCount: 0 };
2036
+ }
2037
+ const absorbedIds = /* @__PURE__ */ new Set();
2038
+ for (const record of state.absorbed ?? []) {
2039
+ if (record.resultMessageId) absorbedIds.add(record.resultMessageId);
2040
+ }
2041
+ let promptedCount = 0;
2042
+ const latest = collectLatestProtected(messages, config);
2043
+ const out = messages.map((msg) => {
2044
+ if (!isAbsorbCandidate(msg, config, latest)) return msg;
2045
+ if (absorbedIds.has(msg.id)) return msg;
2046
+ const text = msg.text ?? "";
2047
+ if (text.includes(ABSORB_PROMPT_MARKER)) return msg;
2048
+ const tokens = countTokens(text);
2049
+ if (tokens < cfg.minToolTokens) return msg;
2050
+ const ref = refForRaw(state.messageRefs, msg.id);
2051
+ if (!ref || ref === BLOCKED_REF) return msg;
2052
+ promptedCount++;
2053
+ return {
2054
+ ...msg,
2055
+ text: text + "\n\n" + buildAbsorbPrompt(ref, tokens, cfg.toolName)
2056
+ };
2057
+ });
2058
+ return { messages: out, promptedCount };
2059
+ }
2060
+ function parseAbsorbInput(input, callId, onWarn) {
2061
+ let obj = null;
2062
+ if (typeof input === "string") {
2063
+ try {
2064
+ const parsed = JSON.parse(input);
2065
+ if (parsed && typeof parsed === "object") {
2066
+ obj = parsed;
2067
+ }
2068
+ } catch {
2069
+ obj = null;
2070
+ }
2071
+ } else if (input && typeof input === "object") {
2072
+ obj = input;
2073
+ }
2074
+ if (!obj) {
2075
+ onWarn?.(`[acp-absorb-input] rejected: not an object (${typeof input})`);
2076
+ return null;
2077
+ }
2078
+ const ref = pickString(obj, "ref", "messageId", "of");
2079
+ const summary = pickString(obj, "summary", "content");
2080
+ if (typeof ref !== "string" || typeof summary !== "string") {
2081
+ onWarn?.(
2082
+ `[acp-absorb-input] rejected: need ref (string) + summary (string); keys: ${Object.keys(obj).join(",")}`
2083
+ );
2084
+ return null;
2085
+ }
2086
+ return {
2087
+ ref: ref.trim(),
2088
+ summary,
2089
+ ...callId ? { absorbCallId: callId } : {}
2090
+ };
2091
+ }
2092
+ function pickString(obj, ...keys) {
2093
+ for (const key of keys) {
2094
+ const value = obj[key];
2095
+ if (typeof value === "string") return value;
2096
+ }
2097
+ return void 0;
2098
+ }
2099
+ function applyAbsorb(input) {
2100
+ const countTokens = input.countTokens ?? ((text) => Math.ceil(text.length / 4));
2101
+ const summary = input.summary?.trim() ?? "";
2102
+ if (!summary) {
2103
+ return {
2104
+ state: input.state,
2105
+ ok: false,
2106
+ resultText: "absorb failed: summary is empty \u2014 provide the distilled key info of the tool result."
2107
+ };
2108
+ }
2109
+ const cfg = resolveAbsorbConfig(input.config);
2110
+ const rawId = rawForRef(input.state.messageRefs, input.ref.trim());
2111
+ if (!rawId) {
2112
+ return {
2113
+ state: input.state,
2114
+ ok: false,
2115
+ resultText: `absorb failed: ref ${input.ref} does not exist in this session (it may be hidden, already compressed, or stale).`
2116
+ };
2117
+ }
2118
+ const existing = (input.state.absorbed ?? []).find(
2119
+ (record2) => record2.resultMessageId === rawId
2120
+ );
2121
+ if (existing) {
2122
+ return {
2123
+ state: input.state,
2124
+ ok: true,
2125
+ resultText: `already absorbed (${input.ref}) \u2014 no change.`
2126
+ };
2127
+ }
2128
+ const target = input.messages.find((m) => m.id === rawId);
2129
+ if (!target) {
2130
+ return {
2131
+ state: input.state,
2132
+ ok: false,
2133
+ resultText: `absorb failed: ref ${input.ref} is not visible in this session (hidden or compressed).`
2134
+ };
2135
+ }
2136
+ if (target.contentType !== "tool-result") {
2137
+ return {
2138
+ state: input.state,
2139
+ ok: false,
2140
+ resultText: `absorb failed: ref ${input.ref} is a ${target.contentType}, not a tool result.`
2141
+ };
2142
+ }
2143
+ if (isAcpOrConfiguredTool(target.toolName, cfg)) {
2144
+ return {
2145
+ state: input.state,
2146
+ ok: false,
2147
+ resultText: `absorb failed: ${target.toolName} is an ACP-managed tool result \u2014 it is not absorbable.`
2148
+ };
2149
+ }
2150
+ if (isMessageProtected(target, input.config) || isMessageLatestProtected(
2151
+ target,
2152
+ collectLatestProtected(input.messages, input.config)
2153
+ )) {
2154
+ return {
2155
+ state: input.state,
2156
+ ok: false,
2157
+ resultText: `absorb failed: ${target.toolName} is a protected tool \u2014 its results must stay visible.`
2158
+ };
2159
+ }
2160
+ if (!target.toolCallId) {
2161
+ return {
2162
+ state: input.state,
2163
+ ok: false,
2164
+ resultText: `absorb failed: ref ${input.ref} has no tool-call id \u2014 cannot pair it for hiding.`
2165
+ };
2166
+ }
2167
+ const call = input.messages.find(
2168
+ (m) => m.contentType === "tool-call" && m.toolCallId === target.toolCallId
2169
+ );
2170
+ const tokens = countTokens(target.text ?? "");
2171
+ const summaryTokens = countTokens(summary);
2172
+ const record = {
2173
+ toolCallId: target.toolCallId,
2174
+ callMessageId: call?.id ?? "",
2175
+ resultMessageId: target.id,
2176
+ ...input.absorbCallId ? { absorbCallId: input.absorbCallId } : {},
2177
+ summary,
2178
+ tokensReclaimed: tokens,
2179
+ createdAt: Date.now()
2180
+ };
2181
+ const state = {
2182
+ ...input.state,
2183
+ absorbed: [...input.state.absorbed ?? [], record],
2184
+ stats: {
2185
+ ...input.state.stats,
2186
+ absorbedTokens: (input.state.stats.absorbedTokens ?? 0) + tokens
2187
+ }
2188
+ };
2189
+ const bloat = summaryTokens >= tokens && tokens > 0 ? ` WARNING: your summary (~${formatTokenCount(summaryTokens)} tokens) is not smaller than the original (~${formatTokenCount(tokens)} tokens) \u2014 distill harder next time.` : "";
2190
+ return {
2191
+ state,
2192
+ ok: true,
2193
+ resultText: `absorbed ${input.ref} (~${formatTokenCount(tokens)} tokens \u2192 summary ~${formatTokenCount(summaryTokens)}). The original tool output is now hidden; your summary is the durable record.${bloat}`
2194
+ };
2195
+ }
2196
+
2197
+ // src/crush.ts
2198
+ var DEFAULT_CRUSH_CONFIG = {
2199
+ enabled: false,
2200
+ minReduction: 0.1
2201
+ };
2202
+ var MAX_INPUT_CHARS = 2e6;
2203
+ var MAX_DEPTH = 12;
2204
+ var MAX_FOLDS = 1e3;
2205
+ var RUN_MIN = 4;
2206
+ var HOIST_MIN = 4;
2207
+ var CODE_KEEP_RATIO = 0.97;
2208
+ var CRUSH_KEY = "__acp_crush";
2209
+ function isPlainObject(v) {
2210
+ if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
2211
+ const p = Object.getPrototypeOf(v);
2212
+ return p === null || p === Object.prototype;
2213
+ }
2214
+ function classifyCrushText(text) {
2215
+ const trimmed = text.trim();
2216
+ if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]"))
2217
+ return "json";
2218
+ if (detectLanguage(text) !== null) return "code";
2219
+ return "log";
2220
+ }
2221
+ function crushText(text, options = {}) {
2222
+ const countTokens = options.countTokens ?? defaultCountTokens;
2223
+ const minReduction = options.minReduction ?? DEFAULT_CRUSH_CONFIG.minReduction;
2224
+ const meta = options.meta ?? {};
2225
+ if (text.length === 0 || text.length > MAX_INPUT_CHARS) return null;
2226
+ const rawTok = countTokens(text);
2227
+ if (rawTok <= 0) return null;
2228
+ const kind = classifyCrushText(text);
2229
+ const wanted = options.plugins ?? registeredPlugins();
2230
+ for (const plugin of wanted) {
2231
+ let res;
2232
+ try {
2233
+ if (!Array.isArray(plugin.kinds) || !plugin.kinds.includes(kind))
2234
+ continue;
2235
+ res = plugin.run(text, meta);
2236
+ } catch {
2237
+ continue;
2238
+ }
2239
+ if (!res || typeof res.text !== "string" || res.text === "" || res.text === text)
2240
+ continue;
2241
+ const newTok = countTokens(res.text);
2242
+ const reduction = (rawTok - newTok) / rawTok;
2243
+ if (reduction < minReduction) continue;
2244
+ if (kind === "log" && res.lossy && !errorLinesSurvive(text, res.text))
2245
+ continue;
2246
+ const out = {
2247
+ text: res.text,
2248
+ strategy: plugin.id,
2249
+ lossy: res.lossy
2250
+ };
2251
+ if (res.stats) out.stats = res.stats;
2252
+ return out;
2253
+ }
2254
+ return null;
2255
+ }
2256
+ function canonOf(v, cache2) {
2257
+ if (!isPlainObject(v) && !Array.isArray(v))
2258
+ return JSON.stringify(v) ?? "null";
2259
+ const key = v;
2260
+ const hit = cache2.get(key);
2261
+ if (hit !== void 0) return hit;
2262
+ let s;
2263
+ if (Array.isArray(v)) {
2264
+ s = `[${v.map((e) => canonOf(e, cache2)).join(",")}]`;
2265
+ } else {
2266
+ const entries = Object.entries(v).map(([k, val]) => `${JSON.stringify(k)}:${canonOf(val, cache2)}`).sort();
2267
+ s = `{${entries.join(",")}}`;
2268
+ }
2269
+ cache2.set(key, s);
2270
+ return s;
2271
+ }
2272
+ function strLen(v) {
2273
+ return JSON.stringify(v).length;
2274
+ }
2275
+ function compactValue(v, depth, stats, cache2) {
2276
+ if (depth > MAX_DEPTH || stats.folds >= MAX_FOLDS) return v;
2277
+ if (Array.isArray(v)) return compactArray(v, depth, stats, cache2);
2278
+ if (isPlainObject(v)) {
2279
+ let changed = false;
2280
+ const out = {};
2281
+ for (const [k, val] of Object.entries(v)) {
2282
+ const nv = compactValue(val, depth + 1, stats, cache2);
2283
+ out[k] = nv;
2284
+ if (nv !== val) changed = true;
2285
+ }
2286
+ return changed ? out : v;
2287
+ }
2288
+ return v;
2289
+ }
2290
+ function compactArray(arr, depth, stats, cache2) {
2291
+ const n = arr.length;
2292
+ if (n < RUN_MIN && n < HOIST_MIN)
2293
+ return arr.map((e) => compactValue(e, depth + 1, stats, cache2));
2294
+ if (stats.folds >= MAX_FOLDS) return arr;
2295
+ const plainStats = { folds: 0 };
2296
+ const plain = arr.map((e) => compactValue(e, depth + 1, plainStats, cache2));
2297
+ let winner = plain;
2298
+ let winnerLen = strLen(plain);
2299
+ let winnerFolds = plainStats.folds;
2300
+ if (n >= RUN_MIN) {
2301
+ const rf = buildRunFold(arr, depth, cache2);
2302
+ if (rf && rf.len < winnerLen) {
2303
+ winner = rf.value;
2304
+ winnerLen = rf.len;
2305
+ winnerFolds = rf.folds;
2306
+ }
2307
+ }
2308
+ if (n >= HOIST_MIN && arr.every((e) => isPlainObject(e))) {
2309
+ const hc = buildConstHoist(arr, depth, cache2);
2310
+ if (hc && hc.len < winnerLen) {
2311
+ winner = hc.value;
2312
+ winnerLen = hc.len;
2313
+ winnerFolds = hc.folds;
2314
+ }
2315
+ }
2316
+ stats.folds += winnerFolds;
2317
+ return winner;
2318
+ }
2319
+ function isRunMarker(v) {
2320
+ return isPlainObject(v) && v[CRUSH_KEY] === "identical-run";
2321
+ }
2322
+ function buildRunFold(arr, depth, cache2) {
2323
+ const n = arr.length;
2324
+ const canon = arr.map((e) => canonOf(e, cache2));
2325
+ const segs = [];
2326
+ let runs = 0;
2327
+ let i = 0;
2328
+ while (i < n) {
2329
+ let j = i;
2330
+ while (j + 1 < n && canon[j + 1] === canon[i]) j++;
2331
+ const runLen = j - i + 1;
2332
+ if (runLen >= RUN_MIN) {
2333
+ segs.push({ [CRUSH_KEY]: "identical-run", count: runLen, item: arr[i] });
2334
+ runs++;
2335
+ } else {
2336
+ for (let k = i; k <= j; k++) segs.push(arr[k]);
2337
+ }
2338
+ i = j + 1;
2339
+ }
2340
+ if (runs === 0) return null;
2341
+ const inner = { folds: 0 };
2342
+ const final = segs.map(
2343
+ (s) => isRunMarker(s) ? { ...s, item: compactValue(s.item, depth + 1, inner, cache2) } : compactValue(s, depth + 1, inner, cache2)
2344
+ );
2345
+ return { value: final, len: strLen(final), folds: runs + inner.folds };
2346
+ }
2347
+ function buildConstHoist(arr, depth, cache2) {
2348
+ const n = arr.length;
2349
+ const keys = [];
2350
+ const seen = /* @__PURE__ */ new Set();
2351
+ for (const e of arr) {
2352
+ for (const k of Object.keys(e)) {
2353
+ if (!seen.has(k)) {
2354
+ seen.add(k);
2355
+ keys.push(k);
2356
+ }
2357
+ }
2358
+ }
2359
+ const first = arr[0];
2360
+ if (!first) return null;
2361
+ const constKeys = [];
2362
+ for (const k of keys) {
2363
+ if (!(k in first)) continue;
2364
+ const c0 = canonOf(first[k], cache2);
2365
+ let constant = true;
2366
+ for (let i = 1; i < n; i++) {
2367
+ const row = arr[i];
2368
+ if (!(k in row) || canonOf(row[k], cache2) !== c0) {
2369
+ constant = false;
2370
+ break;
2371
+ }
2372
+ }
2373
+ if (constant) constKeys.push(k);
2374
+ }
2375
+ if (constKeys.length === 0) return null;
2376
+ const constSet = new Set(constKeys);
2377
+ const constObj = {};
2378
+ for (const k of constKeys) constObj[k] = first[k];
2379
+ const items = arr.map((e) => {
2380
+ const o = {};
2381
+ for (const [k, v] of Object.entries(e)) {
2382
+ if (!constSet.has(k)) o[k] = v;
2383
+ }
2384
+ return o;
2385
+ });
2386
+ const hasVarying = items.some((o) => Object.keys(o).length > 0);
2387
+ const inner = { folds: 0 };
2388
+ const env = {
2389
+ [CRUSH_KEY]: "rows",
2390
+ rows: n,
2391
+ const: compactValue(constObj, depth + 1, inner, cache2)
2392
+ };
2393
+ if (hasVarying)
2394
+ env.items = items.map((it) => compactValue(it, depth + 1, inner, cache2));
2395
+ const len = strLen(env);
2396
+ if (len >= strLen(arr)) return null;
2397
+ return { value: env, len, folds: 1 + inner.folds };
2398
+ }
2399
+ function crushJson(text) {
2400
+ const parsed = JSON.parse(text);
2401
+ const cache2 = /* @__PURE__ */ new Map();
2402
+ const stats = { folds: 0 };
2403
+ const out = compactValue(parsed, 0, stats, cache2);
2404
+ if (stats.folds === 0) return null;
2405
+ const s = JSON.stringify(out);
2406
+ return s.length < text.length ? s : null;
2407
+ }
2408
+ function detectLanguage(text) {
2409
+ const sample = text.split(/\r?\n/).slice(0, 300);
2410
+ let py = 0;
2411
+ let js = 0;
2412
+ for (const line of sample) {
2413
+ if (/^\s*#!.*python/.test(line)) py += 4;
2414
+ if (/^\s*(async\s+)?def\s+\w/.test(line) || /^\s*class\s+\w/.test(line))
2415
+ py += 2;
2416
+ if (/\bfrom\s+['"]/.test(line) || /^\s*import\s+['"]/.test(line)) js += 2;
2417
+ else if (/^\s*(import|from)\s+[A-Za-z_]\w*/.test(line)) py += 1;
2418
+ if (/^\s*(function\b|const\s|let\s|var\s)/.test(line)) js += 2;
2419
+ if (/=>/.test(line)) js += 1;
2420
+ if (/^\s*(public|private|protected|static|final|void|return)\b/.test(line))
2421
+ js += 1;
2422
+ if (/;\s*$/.test(line) && line.trim().length > 0) js += 1;
2423
+ if (/\bself\./.test(line) || /^\s*(elif|except|yield)\b/.test(line))
2424
+ py += 1;
2425
+ if (/\bconsole\.log\b|\brequire\s*\(|module\.exports|\bawait\s/.test(line))
2426
+ js += 1;
2427
+ }
2428
+ const MARGIN = 1.5;
2429
+ if (py >= 3 && py > js * MARGIN) return "python";
2430
+ if (js >= 4 && js > py * MARGIN) return "js";
2431
+ return null;
2432
+ }
2433
+ function finalizeTrimmed(original, emitted) {
2434
+ if (emitted.length >= original.split(/\r?\n/).length) return null;
2435
+ const out = emitted.join("\n") + (original.endsWith("\n") ? "\n" : "");
2436
+ return out.length < original.length * CODE_KEEP_RATIO ? out : null;
2437
+ }
2438
+ function trimPython(text) {
2439
+ const lines = text.split(/\r?\n/);
2440
+ const emitted = [];
2441
+ let pending = 0;
2442
+ const flush = () => {
2443
+ if (pending > 0) {
2444
+ emitted.push(
2445
+ `# [acp-crush: elided ${pending} line${pending === 1 ? "" : "s"}]`
2446
+ );
2447
+ pending = 0;
2448
+ }
2449
+ };
2450
+ let state = "code";
2451
+ let quote = "";
2452
+ let isDocstring = false;
2453
+ let blankRun = 0;
2454
+ let atModuleStart = true;
2455
+ let pendingDefClass = false;
2456
+ let docstringSlot = false;
2457
+ const classifySig = (codePart) => {
2458
+ const t = codePart.trim();
2459
+ if (/^(async\s+)?(def|class)\b/.test(t)) pendingDefClass = true;
2460
+ if (t.endsWith(":")) {
2461
+ docstringSlot = pendingDefClass;
2462
+ pendingDefClass = false;
2463
+ } else if (t.length > 0 && !t.startsWith("@")) {
2464
+ docstringSlot = false;
2465
+ }
2466
+ atModuleStart = false;
2467
+ };
2468
+ for (let li = 0; li < lines.length; li++) {
2469
+ const line = lines[li];
2470
+ if (state === "triple") {
2471
+ const close = findTripleClose(line, quote);
2472
+ if (close === -1) {
2473
+ if (isDocstring) pending++;
2474
+ else emitted.push(line);
2475
+ continue;
2476
+ }
2477
+ state = "code";
2478
+ const rest = line.slice(close + 3).trim();
2479
+ if (rest.length > 0) {
2480
+ flush();
2481
+ emitted.push(line);
2482
+ classifySig(rest);
2483
+ } else {
2484
+ if (isDocstring) pending++;
2485
+ else emitted.push(line);
2486
+ }
2487
+ isDocstring = false;
2488
+ docstringSlot = false;
2489
+ pendingDefClass = false;
2490
+ atModuleStart = false;
2491
+ blankRun = 0;
2492
+ continue;
2493
+ }
2494
+ if (line.trim().length === 0) {
2495
+ blankRun++;
2496
+ if (blankRun >= 3) {
2497
+ flush();
2498
+ emitted.push("");
2499
+ blankRun = 1;
2500
+ }
2501
+ continue;
2502
+ }
2503
+ if (li === 0 && line.startsWith("#!")) {
2504
+ flush();
2505
+ emitted.push(line);
2506
+ blankRun = 0;
2507
+ continue;
2508
+ }
2509
+ const m = line.match(/^\s*("""|''')/);
2510
+ if (m) {
2511
+ flush();
2512
+ const q = m[1];
2513
+ const opensHere = line.indexOf(q);
2514
+ const closeOnSameLine = findTripleClose(line.slice(opensHere + 3), q);
2515
+ if (closeOnSameLine !== -1) {
2516
+ const restAfter = line.slice(opensHere + 3 + closeOnSameLine + 3).trim();
2517
+ if (restAfter.length > 0) {
2518
+ emitted.push(line);
2519
+ classifySig(restAfter);
2520
+ } else if (atModuleStart || docstringSlot) {
2521
+ pending++;
2522
+ atModuleStart = false;
2523
+ docstringSlot = false;
2524
+ pendingDefClass = false;
2525
+ } else {
2526
+ emitted.push(line);
2527
+ }
2528
+ blankRun = 0;
2529
+ continue;
2530
+ }
2531
+ state = "triple";
2532
+ quote = q;
2533
+ isDocstring = atModuleStart || docstringSlot;
2534
+ atModuleStart = false;
2535
+ docstringSlot = false;
2536
+ if (!isDocstring) emitted.push(line);
2537
+ blankRun = 0;
2538
+ continue;
2539
+ }
2540
+ const probe = findCommentOrTriple(line);
2541
+ if (probe.kind === "comment-full") {
2542
+ pending++;
2543
+ continue;
2544
+ }
2545
+ if (probe.kind === "triple-mid") {
2546
+ flush();
2547
+ state = "triple";
2548
+ quote = probe.quote;
2549
+ isDocstring = false;
2550
+ emitted.push(line);
2551
+ classifySig(line.slice(0, probe.idx));
2552
+ blankRun = 0;
2553
+ continue;
2554
+ }
2555
+ flush();
2556
+ emitted.push(line);
2557
+ classifySig(line);
2558
+ blankRun = 0;
2559
+ }
2560
+ if (state === "triple") return null;
2561
+ flush();
2562
+ return finalizeTrimmed(text, emitted);
2563
+ }
2564
+ function findTripleClose(line, quote) {
2565
+ let i = 0;
2566
+ while (i < line.length) {
2567
+ if (line[i] === "\\") {
2568
+ i += 2;
2569
+ continue;
2570
+ }
2571
+ if (line.startsWith(quote, i)) return i;
2572
+ i++;
2573
+ }
2574
+ return -1;
2575
+ }
2576
+ function findCommentOrTriple(line) {
2577
+ let i = 0;
2578
+ let inStr = null;
2579
+ while (i < line.length) {
2580
+ const c = line[i];
2581
+ if (inStr) {
2582
+ if (c === "\\") i += 2;
2583
+ else if (c === inStr) inStr = null;
2584
+ else i++;
2585
+ continue;
2586
+ }
2587
+ if (c === "'" || c === '"') {
2588
+ inStr = c;
2589
+ i++;
2590
+ continue;
2591
+ }
2592
+ if (c === "#") {
2593
+ return {
2594
+ kind: line.slice(0, i).trim().length === 0 ? "comment-full" : "none",
2595
+ idx: i,
2596
+ quote: ""
2597
+ };
2598
+ }
2599
+ if (line.startsWith('"""', i) || line.startsWith("'''", i)) {
2600
+ return { kind: "triple-mid", idx: i, quote: line.slice(i, i + 3) };
2601
+ }
2602
+ i++;
2603
+ }
2604
+ return { kind: "none", idx: -1, quote: "" };
2605
+ }
2606
+ function scanTemplateRest(line, from, st) {
2607
+ let m = st.mode;
2608
+ let q = st.quote;
2609
+ let d = st.depth;
2610
+ let i = from;
2611
+ while (i < line.length) {
2612
+ const c = line[i];
2613
+ if (m === "str") {
2614
+ if (c === "\\") i += 2;
2615
+ else if (c === q) m = "expr";
2616
+ else i++;
2617
+ continue;
2618
+ }
2619
+ if (m === "expr") {
2620
+ if (c === "'" || c === '"') {
2621
+ q = c;
2622
+ m = "str";
2623
+ i++;
2624
+ continue;
2625
+ }
2626
+ if (c === "`")
2627
+ return {
2628
+ mode: m,
2629
+ quote: q,
2630
+ depth: d,
2631
+ closed: false,
2632
+ bail: true,
2633
+ consumed: 0
2634
+ };
2635
+ if (c === "{") d++;
2636
+ else if (c === "}") {
2637
+ d--;
2638
+ if (d === 0) m = "tpl";
2639
+ }
2640
+ i++;
2641
+ continue;
2642
+ }
2643
+ if (c === "\\") {
2644
+ i += 2;
2645
+ continue;
2646
+ }
2647
+ if (c === "`")
2648
+ return {
2649
+ mode: "tpl",
2650
+ quote: "",
2651
+ depth: 0,
2652
+ closed: true,
2653
+ bail: false,
2654
+ consumed: i - from + 1
2655
+ };
2656
+ if (c === "$" && line[i + 1] === "{") {
2657
+ m = "expr";
2658
+ d = 1;
2659
+ i += 2;
2660
+ continue;
2661
+ }
2662
+ i++;
2663
+ }
2664
+ return {
2665
+ mode: m,
2666
+ quote: q,
2667
+ depth: d,
2668
+ closed: false,
2669
+ bail: false,
2670
+ consumed: 0
2671
+ };
2672
+ }
2673
+ function regexAllowedAfter(prev) {
2674
+ if (prev === null) return true;
2675
+ if (/[A-Za-z0-9_]/.test(prev)) return false;
2676
+ return !".'\"`) ]".includes(prev);
2677
+ }
2678
+ function prevSigChar(line, i) {
2679
+ for (let j = i - 1; j >= 0; j--) {
2680
+ const c = line[j];
2681
+ if (c === " " || c === " ") continue;
2682
+ return c;
2683
+ }
2684
+ return null;
2685
+ }
2686
+ function scanRegexLiteral(line, i) {
2687
+ let j = i + 1;
2688
+ let inClass = false;
2689
+ while (j < line.length) {
2690
+ const c = line[j];
2691
+ if (c === "\\") {
2692
+ j += 2;
2693
+ continue;
2694
+ }
2695
+ if (inClass) {
2696
+ if (c === "]") inClass = false;
2697
+ } else if (c === "[") {
2698
+ inClass = true;
2699
+ } else if (c === "/") {
2700
+ let f = j + 1;
2701
+ while (f < line.length && /[a-z]/i.test(line[f])) f++;
2702
+ return f;
2703
+ }
2704
+ j++;
2705
+ }
2706
+ return null;
2707
+ }
2708
+ function scanJsCodeLine(line) {
2709
+ let i = 0;
2710
+ let inStr = null;
2711
+ while (i < line.length) {
2712
+ const c = line[i];
2713
+ if (inStr) {
2714
+ if (c === "\\") i += 2;
2715
+ else if (c === inStr) inStr = null;
2716
+ else i++;
2717
+ continue;
2718
+ }
2719
+ if (c === "'" || c === '"') {
2720
+ inStr = c;
2721
+ i++;
2722
+ continue;
2723
+ }
2724
+ if (c === "`") {
2725
+ const r = scanTemplateRest(line, i, { mode: "tpl", quote: "", depth: 0 });
2726
+ if (r.bail) return { kind: "code", bail: true };
2727
+ if (!r.closed)
2728
+ return {
2729
+ kind: "template-open",
2730
+ tmpl: { mode: r.mode, quote: r.quote, depth: r.depth }
2731
+ };
2732
+ i += r.consumed;
2733
+ continue;
2734
+ }
2735
+ if (c === "/") {
2736
+ const nxt = line[i + 1];
2737
+ if (nxt === "/") {
2738
+ return {
2739
+ kind: line.slice(0, i).trim().length === 0 ? "comment-full" : "code"
2740
+ };
2741
+ }
2742
+ if (nxt !== "*") {
2743
+ if (regexAllowedAfter(prevSigChar(line, i))) {
2744
+ const end = scanRegexLiteral(line, i);
2745
+ if (end !== null) {
2746
+ i = end;
2747
+ continue;
2748
+ }
2749
+ }
2750
+ i++;
2751
+ continue;
2752
+ }
2753
+ const close = line.indexOf("*/", i + 2);
2754
+ const before = line.slice(0, i).trim().length > 0;
2755
+ if (close === -1)
2756
+ return { kind: before ? "block-open-mid" : "block-open-only" };
2757
+ const rest = line.slice(close + 2).trim();
2758
+ if (rest.length === 0) return { kind: before ? "code" : "comment-full" };
2759
+ i = close + 2;
2760
+ continue;
2761
+ }
2762
+ i++;
2763
+ }
2764
+ return { kind: "code" };
2765
+ }
2766
+ function trimJsTs(text) {
2767
+ const lines = text.split(/\r?\n/);
2768
+ const emitted = [];
2769
+ let pending = 0;
2770
+ const flush = () => {
2771
+ if (pending > 0) {
2772
+ emitted.push(
2773
+ `// [acp-crush: elided ${pending} line${pending === 1 ? "" : "s"}]`
2774
+ );
2775
+ pending = 0;
2776
+ }
2777
+ };
2778
+ let state = "code";
2779
+ let tmpl = { mode: "tpl", quote: "", depth: 0 };
2780
+ let blankRun = 0;
2781
+ for (const line of lines) {
2782
+ if (state === "block") {
2783
+ const close = line.indexOf("*/");
2784
+ if (close === -1) {
2785
+ pending++;
2786
+ continue;
2787
+ }
2788
+ state = "code";
2789
+ if (line.slice(close + 2).trim().length === 0) pending++;
2790
+ else {
2791
+ flush();
2792
+ emitted.push(line);
2793
+ }
2794
+ blankRun = 0;
2795
+ continue;
2796
+ }
2797
+ if (state === "template") {
2798
+ const r = scanTemplateRest(line, 0, tmpl);
2799
+ if (r.bail) return null;
2800
+ tmpl = { mode: r.mode, quote: r.quote, depth: r.depth };
2801
+ if (r.closed) state = "code";
2802
+ emitted.push(line);
2803
+ blankRun = 0;
2804
+ continue;
2805
+ }
2806
+ if (line.trim().length === 0) {
2807
+ blankRun++;
2808
+ if (blankRun >= 3) {
2809
+ flush();
2810
+ emitted.push("");
2811
+ blankRun = 1;
2812
+ }
2813
+ continue;
2814
+ }
2815
+ const scan = scanJsCodeLine(line);
2816
+ if (scan.bail) return null;
2817
+ if (scan.kind === "comment-full") {
2818
+ pending++;
2819
+ continue;
2820
+ }
2821
+ if (scan.kind === "block-open-only") {
2822
+ pending++;
2823
+ state = "block";
2824
+ continue;
2825
+ }
2826
+ if (scan.kind === "block-open-mid") {
2827
+ flush();
2828
+ state = "block";
2829
+ emitted.push(line);
2830
+ blankRun = 0;
2831
+ continue;
2832
+ }
2833
+ if (scan.kind === "template-open") {
2834
+ flush();
2835
+ state = "template";
2836
+ tmpl = scan.tmpl ?? { mode: "tpl", quote: "", depth: 0 };
2837
+ emitted.push(line);
2838
+ blankRun = 0;
2839
+ continue;
2840
+ }
2841
+ flush();
2842
+ emitted.push(line);
2843
+ blankRun = 0;
2844
+ }
2845
+ if (state !== "code") return null;
2846
+ flush();
2847
+ return finalizeTrimmed(text, emitted);
2848
+ }
2849
+ function crushCode(text) {
2850
+ const lang = detectLanguage(text);
2851
+ if (lang === null) return null;
2852
+ if (lang === "python") return trimPython(text);
2853
+ return trimJsTs(text);
2854
+ }
2855
+ var LOG_MIN_LINES = 50;
2856
+ var LOG_MAX_TOTAL_LINES = 100;
2857
+ var LOG_MAX_ERRORS = 20;
2858
+ var LOG_ERROR_CONTEXT = 3;
2859
+ var LOG_MAX_WARNINGS = 5;
2860
+ var LOG_MAX_STACK_TRACES = 3;
2861
+ var LOG_STACK_MAX_LINES = 20;
2862
+ var LOG_TRACE_HEAD_FRAMES = 3;
2863
+ var LOG_TRACE_APP_FRAMES = 5;
2864
+ var LOG_CLASSIFIED_GATE = 5;
2865
+ var LOG_SUMMARY_GATE = 3;
2866
+ var LEVEL_PATTERNS = [
2867
+ ["error", /\b(ERROR|FATAL|CRITICAL)\b/i],
2868
+ ["fail", /\b(FAIL|FAILED)\b/i],
2869
+ ["warn", /\b(WARN|WARNING)\b/i],
2870
+ ["info", /\bINFO\b/i],
2871
+ ["debug", /\bDEBUG\b/i],
2872
+ ["trace", /\bTRACE\b/i]
2873
+ ];
2874
+ function classifyLevel(line) {
2875
+ for (const [level, re] of LEVEL_PATTERNS) {
2876
+ if (re.test(line)) return level;
2877
+ }
2878
+ return "unknown";
2879
+ }
2880
+ function isLogSummaryLine(line) {
2881
+ if (line.startsWith("===") || line.startsWith("---")) return true;
2882
+ let d = 0;
2883
+ while (d < line.length && line.charCodeAt(d) >= 48 && line.charCodeAt(d) <= 57)
2884
+ d++;
2885
+ if (d > 0 && line[d] === " ") {
2886
+ const rest = line.slice(d + 1);
2887
+ if (rest.startsWith("passed") || rest.startsWith("failed") || rest.startsWith("skipped") || rest.startsWith("error") || rest.startsWith("warning"))
2888
+ return true;
2889
+ }
2890
+ for (const prefix of [
2891
+ "Test ",
2892
+ "Tests ",
2893
+ "Tests:",
2894
+ "Test:",
2895
+ "Suite ",
2896
+ "Suites ",
2897
+ "Suites:",
2898
+ "Suite:"
2899
+ ]) {
2900
+ if (line.startsWith(prefix)) {
2901
+ const m = line.slice(prefix.length).match(/\S/);
2902
+ if (m !== null && m[0].charCodeAt(0) >= 48 && m[0].charCodeAt(0) <= 57)
2903
+ return true;
2904
+ }
2905
+ }
2906
+ if (line.startsWith("TOTAL") || line.startsWith("Total") || line.startsWith("Summary"))
2907
+ return true;
2908
+ for (const prefix of ["Build", "Compile", "Test"]) {
2909
+ if (line.startsWith(prefix) && (line.includes("succeeded") || line.includes("failed") || line.includes("complete")))
2910
+ return true;
2911
+ }
2912
+ return false;
2913
+ }
2914
+ function isDigitChar(c) {
2915
+ return c >= "0" && c <= "9";
2916
+ }
2917
+ function isAsciiAlnum(c) {
2918
+ const n = c.charCodeAt(0);
2919
+ return n >= 97 && n <= 122 || n >= 65 && n <= 90 || n >= 48 && n <= 57;
2920
+ }
2921
+ function hasLineColSuffix(s) {
2922
+ for (let i = 0; i + 1 < s.length; i++) {
2923
+ if (s[i] === ":" && isDigitChar(s[i + 1])) {
2924
+ let j = i + 1;
2925
+ while (j < s.length && isDigitChar(s[j])) j++;
2926
+ if (j + 1 < s.length && s[j] === ":" && isDigitChar(s[j + 1]))
2927
+ return true;
2928
+ }
2929
+ }
2930
+ return false;
2931
+ }
2932
+ function isPythonFileFrame(s) {
2933
+ return s.startsWith('File "') && s.includes('", line ') && s.length > 0 && isDigitChar(s[s.length - 1]);
2934
+ }
2935
+ function isJsAtFrame(s) {
2936
+ return s.startsWith("at ") && s.includes("(") && s.includes(")") && hasLineColSuffix(s);
2937
+ }
2938
+ function isJavaAtFrame(s) {
2939
+ if (!s.startsWith("at ") || !s.includes("(")) return false;
2940
+ const open = s.indexOf("(");
2941
+ const body = s.slice(3, open);
2942
+ if (body.length === 0) return false;
2943
+ for (const c of body) {
2944
+ if (!(isAsciiAlnum(c) || c === "." || c === "_" || c === "$" || c === "/"))
2945
+ return false;
2946
+ }
2947
+ return true;
2948
+ }
2949
+ function isRustPanicOpener(s) {
2950
+ return s.startsWith("thread '") && s.includes("panicked at");
2951
+ }
2952
+ function isGoroutineHeader(line) {
2953
+ if (!line.startsWith("goroutine ")) return false;
2954
+ const rest = line.slice(10);
2955
+ let d = 0;
2956
+ while (d < rest.length && isDigitChar(rest[d])) d++;
2957
+ return d > 0 && rest.slice(d).startsWith(" [");
2958
+ }
2959
+ function isGoPanicOpener(line) {
2960
+ return line.startsWith("panic: ") || line.startsWith("fatal error: ") || isGoroutineHeader(line);
2961
+ }
2962
+ function isGoFileFrame(line) {
2963
+ return line.startsWith(" ") && line.includes(".go:") && line.includes(" +0x");
2964
+ }
2965
+ function isGoCallFrame(line) {
2966
+ if (line.startsWith("created by ")) return true;
2967
+ if (line.startsWith(" ") || line.startsWith(" ") || !line.endsWith(")"))
2968
+ return false;
2969
+ const open = line.indexOf("(");
2970
+ if (open === -1) return false;
2971
+ const symbol = line.slice(0, open);
2972
+ if (symbol.length === 0 || !symbol.includes(".")) return false;
2973
+ for (const c of symbol) {
2974
+ if (!(isAsciiAlnum(c) || c === "." || c === "_" || c === "/" || c === "*"))
2975
+ return false;
2976
+ }
2977
+ return true;
2978
+ }
2979
+ function isDotnetFrame(s) {
2980
+ return s.startsWith("at ") && s.includes(") in ") && s.includes(":line ");
2981
+ }
2982
+ function isDotnetExceptionHead(trimmed) {
2983
+ const colon = trimmed.indexOf(":");
2984
+ if (colon === -1) return false;
2985
+ const head = trimmed.slice(0, colon);
2986
+ if (!head.endsWith("Exception") || !head.includes(".")) return false;
2987
+ for (const c of head) {
2988
+ if (!(isAsciiAlnum(c) || c === "." || c === "_" || c === "`" || c === "+"))
2989
+ return false;
2990
+ }
2991
+ return true;
2992
+ }
2993
+ function isRustBacktraceFrame(s) {
2994
+ const t = s.trimStart();
2995
+ let i = 0;
2996
+ while (i < t.length && isDigitChar(t[i])) i++;
2997
+ if (i === 0 || t[i] !== ":") return false;
2998
+ i++;
2999
+ while (i < t.length && t[i] === " ") i++;
3000
+ const rest = t.slice(i);
3001
+ if (!rest.startsWith("0x")) return false;
3002
+ let h = 0;
3003
+ for (const c of rest.slice(2)) {
3004
+ if (c >= "0" && c <= "9" || c >= "a" && c <= "f" || c >= "A" && c <= "F")
3005
+ h++;
3006
+ else break;
3007
+ }
3008
+ return h > 0;
3009
+ }
3010
+ function isJavaMoreSummary(trimmed) {
3011
+ if (!trimmed.startsWith("... ")) return false;
3012
+ const rest = trimmed.slice(4);
3013
+ let d = 0;
3014
+ while (d < rest.length && isDigitChar(rest[d])) d++;
3015
+ return d > 0 && rest.slice(d).trim() === "more";
3016
+ }
3017
+ function traceFlavorFor(line) {
3018
+ const t = line.trimStart();
3019
+ if (t.startsWith("Traceback (most recent call last)") || isPythonFileFrame(t))
3020
+ return "py";
3021
+ if (t.startsWith("Unhandled exception.") || isDotnetFrame(t)) return "dotnet";
3022
+ if (isJsAtFrame(t)) return "js";
3023
+ if (isJavaAtFrame(t)) return "java";
3024
+ if (t.startsWith("--> ") && hasLineColSuffix(t)) return "rust-error";
3025
+ if (isRustPanicOpener(t) || t.startsWith("stack backtrace:") || isRustBacktraceFrame(line))
3026
+ return "rust-backtrace";
3027
+ if (isGoPanicOpener(line)) return "go-panic";
3028
+ return null;
3029
+ }
3030
+ function traceTerminates(flavor, line, linesSoFar) {
3031
+ const t = line.trimStart();
3032
+ switch (flavor) {
3033
+ case "py": {
3034
+ const indentedOrBlank = line.startsWith(" ") || line.startsWith(" ") || line.length === 0;
3035
+ const continuation = t.startsWith("Traceback") || t.startsWith("File ") || t.startsWith("During handling") || t.startsWith("The above exception");
3036
+ if (indentedOrBlank || continuation) return false;
3037
+ return !(t.length > 0 && t[0] >= "A" && t[0] <= "Z");
3038
+ }
3039
+ case "js":
3040
+ return !t.startsWith("at ") && line.length !== 0;
3041
+ case "java": {
3042
+ const chain = t.startsWith("Caused by:") || t.startsWith("Suppressed:") || isJavaMoreSummary(t);
3043
+ return !t.startsWith("at ") && !chain && line.length !== 0;
3044
+ }
3045
+ case "dotnet": {
3046
+ if (line.length === 0) return false;
3047
+ const continues = t.startsWith("at ") || t.startsWith("--->") || t.startsWith("--- End of") || isDotnetExceptionHead(t);
3048
+ return !continues;
3049
+ }
3050
+ case "rust-error":
3051
+ return !t.startsWith("--> ") && line.length !== 0;
3052
+ case "rust-backtrace": {
3053
+ if (line.length === 0 || linesSoFar === 1) return false;
3054
+ const isFrame = t.length > 0 && isDigitChar(t[0]);
3055
+ const continuation = line.startsWith(" ") || line.startsWith(" ") || t.startsWith("stack backtrace:") || t.startsWith("note: run with");
3056
+ return !isFrame && !continuation;
3057
+ }
3058
+ case "go-panic": {
3059
+ if (line.length === 0) return false;
3060
+ const continues = line.startsWith(" ") || isGoroutineHeader(line) || isGoCallFrame(line) || line.startsWith("panic: ") || line.startsWith("fatal error: ") || line.startsWith("[signal ");
3061
+ return !continues;
3062
+ }
3063
+ }
3064
+ }
3065
+ function scoreLogLine(l) {
3066
+ const base = l.level === "error" || l.level === "fail" ? 1 : l.level === "warn" ? 0.5 : l.level === "info" || l.level === "unknown" ? 0.1 : l.level === "debug" ? 0.05 : 0.02;
3067
+ return Math.min(base + (l.isStack ? 0.3 : 0) + (l.isSummary ? 0.4 : 0), 1);
3068
+ }
3069
+ function parseLogLines(lines) {
3070
+ const out = new Array(lines.length);
3071
+ let active = null;
3072
+ let traceLines = 0;
3073
+ for (let i = 0; i < lines.length; i++) {
3074
+ const line = lines[i];
3075
+ const entry = {
3076
+ i,
3077
+ content: line,
3078
+ level: classifyLevel(line),
3079
+ isStack: false,
3080
+ isSummary: isLogSummaryLine(line),
3081
+ score: 0
3082
+ };
3083
+ if (active !== null) {
3084
+ const flavor = active;
3085
+ if (traceLines >= LOG_STACK_MAX_LINES || traceTerminates(flavor, line, traceLines)) {
3086
+ const capHit = traceLines >= LOG_STACK_MAX_LINES;
3087
+ active = null;
3088
+ traceLines = 0;
3089
+ const nf = traceFlavorFor(line);
3090
+ if (nf !== null) {
3091
+ active = nf;
3092
+ traceLines = 1;
3093
+ entry.isStack = true;
3094
+ } else if (capHit && !traceTerminates(flavor, line, 2)) {
3095
+ active = flavor;
3096
+ traceLines = 1;
3097
+ entry.isStack = true;
3098
+ }
3099
+ } else {
3100
+ entry.isStack = true;
3101
+ traceLines++;
3102
+ }
3103
+ } else {
3104
+ const f = traceFlavorFor(line);
3105
+ if (f !== null) {
3106
+ active = f;
3107
+ traceLines = 1;
3108
+ entry.isStack = true;
3109
+ }
3110
+ }
3111
+ entry.score = scoreLogLine(entry);
3112
+ out[i] = entry;
3113
+ }
3114
+ return out;
3115
+ }
3116
+ function selectWithFirstLast(arr, maxCount) {
3117
+ if (arr.length <= maxCount) return arr.slice();
3118
+ const out = [];
3119
+ const seen = /* @__PURE__ */ new Set();
3120
+ const push = (l) => {
3121
+ if (seen.add(l.i)) out.push(l);
3122
+ };
3123
+ push(arr[0]);
3124
+ push(arr[arr.length - 1]);
3125
+ if (out.length < maxCount) {
3126
+ const byScore = arr.slice().sort((a, b) => b.score - a.score || a.i - b.i);
3127
+ for (const l of byScore) {
3128
+ if (!seen.has(l.i)) {
3129
+ push(l);
3130
+ if (out.length >= maxCount) break;
3131
+ }
3132
+ }
3133
+ }
3134
+ return out;
3135
+ }
3136
+ function normalizeForDedupe(content) {
3137
+ let splitAt = content.length;
3138
+ for (let i = 0; i < content.length; i++) {
3139
+ if (content[i] === ":" || content[i] === "=") {
3140
+ splitAt = i;
3141
+ break;
3142
+ }
3143
+ }
3144
+ const suffix = content.slice(splitAt).replace(/\d+/g, "N").replace(/0x[0-9a-fA-F]+/g, "ADDR").replace(/\/[\w/]+\//g, "/PATH/");
3145
+ return content.slice(0, splitAt) + suffix;
3146
+ }
3147
+ var RUNTIME_FRAME_PREFIXES = [
3148
+ "at java.",
3149
+ "at jdk.",
3150
+ "at sun.",
3151
+ "at javax.",
3152
+ "at scala.",
3153
+ "at System.",
3154
+ "at Microsoft.",
3155
+ "runtime.",
3156
+ "created by runtime."
3157
+ ];
3158
+ var RUNTIME_FRAME_MARKERS = [
3159
+ "site-packages/",
3160
+ "/usr/lib/python",
3161
+ "lib/python3.",
3162
+ "node:internal/",
3163
+ "node_modules/",
3164
+ "(internal/",
3165
+ "core::",
3166
+ "std::",
3167
+ "alloc::",
3168
+ "rust_begin_unwind",
3169
+ "__rust_",
3170
+ "/rustc/",
3171
+ "/usr/local/go/src/",
3172
+ "/libexec/src/runtime/"
3173
+ ];
3174
+ function isFrameLine(content) {
3175
+ const t = content.trimStart();
3176
+ return t.startsWith("at ") || t.startsWith('File "') && t.includes('", line ') || isRustBacktraceFrame(content) || isGoFileFrame(content) || isGoCallFrame(content);
3177
+ }
3178
+ function isChainHeadLine(content) {
3179
+ const t = content.trimStart();
3180
+ return t.startsWith("Caused by:") || t.startsWith("Suppressed:") || t.startsWith("... ") || t.startsWith("--->") || t.startsWith("--- End of") || t.startsWith("During handling") || t.startsWith("The above exception");
3181
+ }
3182
+ function isRuntimeFrame(content) {
3183
+ const t = content.trimStart();
3184
+ return RUNTIME_FRAME_PREFIXES.some((p) => t.startsWith(p)) || RUNTIME_FRAME_MARKERS.some((m) => content.includes(m));
3185
+ }
3186
+ function collapseTraceFrames(stack, headFrames, appFrames) {
3187
+ const kept = [];
3188
+ const dropped = /* @__PURE__ */ new Set();
3189
+ let framesSeen = 0;
3190
+ let appKept = 0;
3191
+ let runStart = -1;
3192
+ let runLen = 0;
3193
+ let prevDropped = false;
3194
+ const flushRun = () => {
3195
+ if (runStart !== -1) {
3196
+ kept.push({
3197
+ i: runStart,
3198
+ content: ` [... ${runLen} frames collapsed]`,
3199
+ level: "unknown",
3200
+ isStack: true,
3201
+ isSummary: false,
3202
+ score: 0.8
3203
+ });
3204
+ runStart = -1;
3205
+ runLen = 0;
3206
+ }
3207
+ };
3208
+ for (const line of stack) {
3209
+ if (isFrameLine(line.content) && !isChainHeadLine(line.content)) {
3210
+ framesSeen++;
3211
+ const runtime = isRuntimeFrame(line.content);
3212
+ const keep = framesSeen <= headFrames || !runtime && appKept < appFrames;
3213
+ if (keep) {
3214
+ if (!runtime) appKept++;
3215
+ flushRun();
3216
+ kept.push(line);
3217
+ prevDropped = false;
3218
+ } else {
3219
+ if (runStart === -1) runStart = line.i;
3220
+ runLen++;
3221
+ dropped.add(line.i);
3222
+ prevDropped = true;
3223
+ }
3224
+ } else if (prevDropped && (line.content.startsWith(" ") || line.content.startsWith(" ")) && !isChainHeadLine(line.content)) {
3225
+ runLen++;
3226
+ dropped.add(line.i);
3227
+ } else {
3228
+ flushRun();
3229
+ kept.push(line);
3230
+ prevDropped = false;
3231
+ }
3232
+ }
3233
+ flushRun();
3234
+ return { kept, dropped };
1674
3235
  }
1675
- function applyAbsorb(input) {
1676
- const countTokens = input.countTokens ?? ((text) => Math.ceil(text.length / 4));
1677
- const summary = input.summary?.trim() ?? "";
1678
- if (!summary) {
1679
- return {
1680
- state: input.state,
1681
- ok: false,
1682
- resultText: "absorb failed: summary is empty \u2014 provide the distilled key info of the tool result."
1683
- };
3236
+ function selectLogLines(all) {
3237
+ const errors = [];
3238
+ const fails = [];
3239
+ const warnings = [];
3240
+ const summaries = [];
3241
+ const stacks = [];
3242
+ let current = [];
3243
+ for (const l of all) {
3244
+ if (l.level === "error") errors.push(l);
3245
+ else if (l.level === "fail") fails.push(l);
3246
+ else if (l.level === "warn") warnings.push(l);
3247
+ if (l.isStack) current.push(l);
3248
+ else if (current.length > 0) {
3249
+ stacks.push(current);
3250
+ current = [];
3251
+ }
3252
+ if (l.isSummary) summaries.push(l);
3253
+ }
3254
+ if (current.length > 0) stacks.push(current);
3255
+ const selected = /* @__PURE__ */ new Map();
3256
+ for (const l of selectWithFirstLast(errors, LOG_MAX_ERRORS))
3257
+ selected.set(l.i, l);
3258
+ for (const l of selectWithFirstLast(fails, LOG_MAX_ERRORS))
3259
+ selected.set(l.i, l);
3260
+ const seenWarn = /* @__PURE__ */ new Set();
3261
+ const dedupedWarnings = [];
3262
+ for (const w of warnings) {
3263
+ const key = normalizeForDedupe(w.content);
3264
+ if (!seenWarn.has(key)) {
3265
+ seenWarn.add(key);
3266
+ dedupedWarnings.push(w);
3267
+ }
3268
+ }
3269
+ for (const w of dedupedWarnings.slice(0, LOG_MAX_WARNINGS))
3270
+ selected.set(w.i, w);
3271
+ for (const stack of stacks.slice(0, LOG_MAX_STACK_TRACES)) {
3272
+ if (stack.length > LOG_STACK_MAX_LINES) {
3273
+ const collapsed = collapseTraceFrames(
3274
+ stack,
3275
+ LOG_TRACE_HEAD_FRAMES,
3276
+ LOG_TRACE_APP_FRAMES
3277
+ );
3278
+ for (const i of collapsed.dropped) selected.delete(i);
3279
+ for (const l of collapsed.kept.slice(0, LOG_STACK_MAX_LINES))
3280
+ selected.set(l.i, l);
3281
+ } else {
3282
+ for (const l of stack) selected.set(l.i, l);
3283
+ }
1684
3284
  }
1685
- const cfg = resolveAbsorbConfig(input.config);
1686
- const rawId = rawForRef(input.state.messageRefs, input.ref.trim());
1687
- if (!rawId) {
1688
- return {
1689
- state: input.state,
1690
- ok: false,
1691
- resultText: `absorb failed: ref ${input.ref} does not exist in this session (it may be hidden, already compressed, or stale).`
1692
- };
3285
+ for (const s of summaries) selected.set(s.i, s);
3286
+ for (const idx of Array.from(selected.keys())) {
3287
+ const lo = Math.max(0, idx - LOG_ERROR_CONTEXT);
3288
+ const hi = Math.min(all.length, idx + LOG_ERROR_CONTEXT + 1);
3289
+ for (let i = lo; i < hi; i++) {
3290
+ if (i !== idx && !selected.has(i)) selected.set(i, all[i]);
3291
+ }
1693
3292
  }
1694
- const existing = (input.state.absorbed ?? []).find(
1695
- (record2) => record2.resultMessageId === rawId
3293
+ let ordered = Array.from(selected.values());
3294
+ if (ordered.length > LOG_MAX_TOTAL_LINES) {
3295
+ ordered.sort((a, b) => b.score - a.score || a.i - b.i);
3296
+ ordered = ordered.slice(0, LOG_MAX_TOTAL_LINES);
3297
+ ordered.sort((a, b) => a.i - b.i);
3298
+ }
3299
+ return ordered;
3300
+ }
3301
+ function formatLogOutput(selected, all) {
3302
+ const count = (lv) => all.reduce((n, l) => l.level === lv ? n + 1 : n, 0);
3303
+ const output = selected.map((l) => l.content);
3304
+ const omitted = all.length - selected.length;
3305
+ if (omitted > 0) {
3306
+ const parts = [];
3307
+ const e = count("error");
3308
+ const f = count("fail");
3309
+ const w = count("warn");
3310
+ const inf = count("info");
3311
+ if (e > 0) parts.push(`${e} ERROR`);
3312
+ if (f > 0) parts.push(`${f} FAIL`);
3313
+ if (w > 0) parts.push(`${w} WARN`);
3314
+ if (inf > 0) parts.push(`${inf} INFO`);
3315
+ if (parts.length > 0)
3316
+ output.push(`[${omitted} lines omitted: ${parts.join(", ")}]`);
3317
+ }
3318
+ return output.join("\n");
3319
+ }
3320
+ var LOG_FORMAT_TABLE = [
3321
+ [
3322
+ "pytest",
3323
+ [
3324
+ "=== FAILURES",
3325
+ "=== ERRORS",
3326
+ "=== test session",
3327
+ "=== short test summary",
3328
+ "PASSED [",
3329
+ "FAILED [",
3330
+ "ERROR [",
3331
+ "SKIPPED [",
3332
+ "collected "
3333
+ ]
3334
+ ],
3335
+ ["npm", ["npm ERR!", "npm WARN", "npm info", "npm http"]],
3336
+ ["cargo", ["Compiling ", "Finished ", "Running ", "warning: ", "error[E"]],
3337
+ ["jest", ["PASS ", "FAIL ", "Test Suites:"]],
3338
+ ["make", ["make[", "make:", "gcc ", "g++ ", "clang "]]
3339
+ ];
3340
+ function detectLogFormat(lines) {
3341
+ const sample = lines.slice(0, 100);
3342
+ let best = "generic";
3343
+ let bestScore = 0;
3344
+ for (const [name, pats] of LOG_FORMAT_TABLE) {
3345
+ let score = 0;
3346
+ for (const line of sample) {
3347
+ if (pats.some((p) => line.includes(p))) score++;
3348
+ }
3349
+ if (score > bestScore) {
3350
+ bestScore = score;
3351
+ best = name;
3352
+ }
3353
+ }
3354
+ return best;
3355
+ }
3356
+ function crushLog(text) {
3357
+ const lines = text.split(/\r?\n/);
3358
+ if (lines.length < LOG_MIN_LINES) return null;
3359
+ let classified = 0;
3360
+ let summaries = 0;
3361
+ let traces = 0;
3362
+ for (const line of lines) {
3363
+ const lv = classifyLevel(line);
3364
+ if (lv !== "unknown") classified++;
3365
+ if (isLogSummaryLine(line)) summaries++;
3366
+ if (traceFlavorFor(line) !== null) traces++;
3367
+ }
3368
+ if (!(classified >= LOG_CLASSIFIED_GATE || traces >= 1 || summaries >= LOG_SUMMARY_GATE || detectLogFormat(lines) !== "generic"))
3369
+ return null;
3370
+ const parsed = parseLogLines(lines);
3371
+ const selected = selectLogLines(parsed);
3372
+ if (selected.length >= lines.length) return null;
3373
+ const out = formatLogOutput(selected, parsed);
3374
+ if (out.length >= text.length) return null;
3375
+ return out;
3376
+ }
3377
+ function errorLinesSurvive(input, output) {
3378
+ const kept = new Set(
3379
+ output.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0)
1696
3380
  );
1697
- if (existing) {
1698
- return {
1699
- state: input.state,
1700
- ok: true,
1701
- resultText: `already absorbed (${input.ref}) \u2014 no change.`
1702
- };
3381
+ for (const line of input.split(/\r?\n/)) {
3382
+ const level = classifyLevel(line);
3383
+ if (level !== "error" && level !== "fail") continue;
3384
+ const t = line.trim();
3385
+ if (t.length > 0 && !kept.has(t)) return false;
1703
3386
  }
1704
- const target = input.messages.find((m) => m.id === rawId);
1705
- if (!target) {
1706
- return {
1707
- state: input.state,
1708
- ok: false,
1709
- resultText: `absorb failed: ref ${input.ref} is not visible in this session (hidden or compressed).`
1710
- };
3387
+ return true;
3388
+ }
3389
+ var jsonFoldPlugin = {
3390
+ id: "json-fold",
3391
+ kinds: ["json"],
3392
+ run(text) {
3393
+ const out = crushJson(text);
3394
+ return out ? { text: out, lossy: false } : null;
1711
3395
  }
1712
- if (target.contentType !== "tool-result") {
1713
- return {
1714
- state: input.state,
1715
- ok: false,
1716
- resultText: `absorb failed: ref ${input.ref} is a ${target.contentType}, not a tool result.`
1717
- };
3396
+ };
3397
+ var codeTrimPlugin = {
3398
+ id: "code-trim",
3399
+ kinds: ["code"],
3400
+ run(text) {
3401
+ const out = crushCode(text);
3402
+ return out ? { text: out, lossy: true } : null;
1718
3403
  }
1719
- if (isAcpOrConfiguredTool(target.toolName, cfg)) {
1720
- return {
1721
- state: input.state,
1722
- ok: false,
1723
- resultText: `absorb failed: ${target.toolName} is an ACP-managed tool result \u2014 it is not absorbable.`
1724
- };
3404
+ };
3405
+ var logSelectPlugin = {
3406
+ id: "log-select",
3407
+ kinds: ["log"],
3408
+ run(text) {
3409
+ const out = crushLog(text);
3410
+ return out ? { text: out, lossy: true } : null;
1725
3411
  }
1726
- if (isMessageProtected(target, input.config) || isMessageLatestProtected(
1727
- target,
1728
- collectLatestProtected(input.messages, input.config)
1729
- )) {
1730
- return {
1731
- state: input.state,
1732
- ok: false,
1733
- resultText: `absorb failed: ${target.toolName} is a protected tool \u2014 its results must stay visible.`
1734
- };
3412
+ };
3413
+ var builtInPlugins = [
3414
+ jsonFoldPlugin,
3415
+ codeTrimPlugin,
3416
+ logSelectPlugin
3417
+ ];
3418
+ var crushRegistry = /* @__PURE__ */ new Map();
3419
+ for (const p of builtInPlugins) crushRegistry.set(p.id, p);
3420
+ function registerCrushPlugin(def) {
3421
+ crushRegistry.set(def.id, def);
3422
+ }
3423
+ function unregisterCrushPlugin(id) {
3424
+ crushRegistry.delete(id);
3425
+ }
3426
+ function listCrushPlugins() {
3427
+ return Array.from(crushRegistry.values()).map((p) => ({
3428
+ id: p.id,
3429
+ kinds: p.kinds
3430
+ }));
3431
+ }
3432
+ function resetCrushPlugins() {
3433
+ crushRegistry.clear();
3434
+ for (const p of builtInPlugins) crushRegistry.set(p.id, p);
3435
+ }
3436
+ function registeredPlugins() {
3437
+ return Array.from(crushRegistry.values());
3438
+ }
3439
+ function resolveCrushConfig(config) {
3440
+ return { ...DEFAULT_CRUSH_CONFIG, ...config.crush ?? {} };
3441
+ }
3442
+ function effectivePlugins(crush, meta) {
3443
+ const toolName = meta.toolName;
3444
+ return registeredPlugins().filter((p) => {
3445
+ const ov = crush.strategies?.[p.id];
3446
+ if (ov?.enabled === false) return false;
3447
+ if (toolName && ov?.excludeTools?.some((pat) => matchToolPattern(toolName, pat)))
3448
+ return false;
3449
+ return true;
3450
+ });
3451
+ }
3452
+ function evaluateToolResult(input) {
3453
+ const { absorb, crush, tokenCount, modelContextLimit, meta = {} } = input;
3454
+ const countTokens = input.countTokens ?? defaultCountTokens;
3455
+ const text = input.text;
3456
+ const rawTokens = countTokens(text);
3457
+ if (absorb.contextThresholdPct > 0 && modelContextLimit > 0 && tokenCount < absorb.contextThresholdPct * modelContextLimit) {
3458
+ return { kind: "skip", text, rawTokens };
1735
3459
  }
1736
- if (!target.toolCallId) {
1737
- return {
1738
- state: input.state,
1739
- ok: false,
1740
- resultText: `absorb failed: ref ${input.ref} has no tool-call id \u2014 cannot pair it for hiding.`
1741
- };
3460
+ if (rawTokens < absorb.minToolTokens) {
3461
+ return { kind: "skip", text, rawTokens };
1742
3462
  }
1743
- const call = input.messages.find(
1744
- (m) => m.contentType === "tool-call" && m.toolCallId === target.toolCallId
1745
- );
1746
- const tokens = countTokens(target.text ?? "");
1747
- const summaryTokens = countTokens(summary);
1748
- const record = {
1749
- toolCallId: target.toolCallId,
1750
- callMessageId: call?.id ?? "",
1751
- resultMessageId: target.id,
1752
- ...input.absorbCallId ? { absorbCallId: input.absorbCallId } : {},
1753
- summary,
1754
- tokensReclaimed: tokens,
1755
- createdAt: Date.now()
1756
- };
1757
- const state = {
1758
- ...input.state,
1759
- absorbed: [...input.state.absorbed ?? [], record],
1760
- stats: {
1761
- ...input.state.stats,
1762
- absorbedTokens: (input.state.stats.absorbedTokens ?? 0) + tokens
1763
- }
1764
- };
1765
- const bloat = summaryTokens >= tokens && tokens > 0 ? ` WARNING: your summary (~${formatTokenCount(summaryTokens)} tokens) is not smaller than the original (~${formatTokenCount(tokens)} tokens) \u2014 distill harder next time.` : "";
3463
+ const out = crushText(text, {
3464
+ minReduction: crush.minReduction,
3465
+ countTokens,
3466
+ meta,
3467
+ plugins: effectivePlugins(crush, meta)
3468
+ });
3469
+ if (!out) return { kind: "distill", text, rawTokens };
3470
+ const newTokens = countTokens(out.text);
1766
3471
  return {
1767
- state,
1768
- ok: true,
1769
- resultText: `absorbed ${input.ref} (~${formatTokenCount(tokens)} tokens \u2192 summary ~${formatTokenCount(summaryTokens)}). The original tool output is now hidden; your summary is the durable record.${bloat}`
3472
+ kind: newTokens < absorb.minToolTokens ? "crushed" : "distill",
3473
+ text: out.text,
3474
+ rawTokens,
3475
+ newTokens,
3476
+ reduction: (rawTokens - newTokens) / rawTokens,
3477
+ strategy: out.strategy,
3478
+ lossy: out.lossy
1770
3479
  };
1771
3480
  }
3481
+ function applyCrushToMessages(messages, config, tokenCount, countTokens) {
3482
+ const absorb = resolveAbsorbConfig(config);
3483
+ const crush = resolveCrushConfig(config);
3484
+ let crushedCount = 0;
3485
+ let distilledCount = 0;
3486
+ let changed = false;
3487
+ const out = [];
3488
+ for (const msg of messages) {
3489
+ if (!isAbsorbCandidate(msg, config)) {
3490
+ out.push(msg);
3491
+ continue;
3492
+ }
3493
+ const text = msg.text ?? "";
3494
+ const markerAt = text.indexOf(ABSORB_PROMPT_MARKER);
3495
+ const payload = markerAt >= 0 ? text.slice(0, markerAt) : text;
3496
+ const ev = evaluateToolResult({
3497
+ text: payload,
3498
+ absorb,
3499
+ crush,
3500
+ tokenCount,
3501
+ modelContextLimit: config.modelContextLimit,
3502
+ meta: { toolName: msg.toolName },
3503
+ countTokens
3504
+ });
3505
+ if (ev.kind === "skip") {
3506
+ out.push(msg);
3507
+ continue;
3508
+ }
3509
+ changed = true;
3510
+ if (ev.kind === "crushed") crushedCount++;
3511
+ else distilledCount++;
3512
+ out.push({ ...msg, text: ev.text });
3513
+ }
3514
+ return { messages: changed ? out : messages, crushedCount, distilledCount };
3515
+ }
1772
3516
 
1773
3517
  // src/filter/registry.ts
1774
3518
  var registry = /* @__PURE__ */ new Map();
@@ -2684,10 +4428,12 @@ function createCore(ports = {}) {
2684
4428
  `[acp-kernel] Config validation warnings: ${configErrors.join("; ")}. Thresholds may not fire correctly.`
2685
4429
  );
2686
4430
  }
4431
+ const contentStore = input.contentStore ?? createContentStore();
2687
4432
  const ctx = {
2688
4433
  config: input.config,
2689
4434
  tokenCount: input.tokenCount,
2690
- countTokens
4435
+ countTokens,
4436
+ contentStore
2691
4437
  };
2692
4438
  const initial = {
2693
4439
  messages: input.messages,
@@ -2697,14 +4443,19 @@ function createCore(ports = {}) {
2697
4443
  const strategy = input.renderTags ?? "all";
2698
4444
  const nodes = buildNodes(strategy);
2699
4445
  const result = runPipeline(nodes, initial, ctx);
4446
+ const ccrEffect = result.effects.ccr;
2700
4447
  return {
2701
4448
  messages: result.messages,
2702
4449
  state: result.state,
2703
4450
  nudge: result.effects.nudge,
2704
4451
  terminalEscape: result.effects.terminalEscape,
2705
- truncationSkipped: result.effects.truncationSkipped
4452
+ truncationSkipped: result.effects.truncationSkipped,
4453
+ contentStore: ccrEffect?.store ?? contentStore
2706
4454
  };
2707
4455
  }
4456
+ function retrieve(store, ref) {
4457
+ return applyRetrieve({ store, ref });
4458
+ }
2708
4459
  function decompress(blockId, state) {
2709
4460
  return blockById(state, blockId);
2710
4461
  }
@@ -2724,7 +4475,12 @@ function createCore(ports = {}) {
2724
4475
  activeBlocks: active.length,
2725
4476
  totalBlocks: state.blocks.length,
2726
4477
  tokensCompressed: state.stats.tokensCompressed,
2727
- breakdown: { active: active.length, total: state.blocks.length }
4478
+ breakdown: {
4479
+ active: active.length,
4480
+ total: state.blocks.length,
4481
+ storedMessages: state.stats.storedCount ?? 0,
4482
+ retrievals: state.stats.retrievalCount ?? 0
4483
+ }
2728
4484
  };
2729
4485
  }
2730
4486
  function defaultNodes() {
@@ -2735,7 +4491,9 @@ function createCore(ports = {}) {
2735
4491
  assignRefsNode,
2736
4492
  syncBlocksNode,
2737
4493
  pruneNode,
4494
+ ccrStoreNode,
2738
4495
  absorbHideNode,
4496
+ crushNode,
2739
4497
  absorbPromptNode,
2740
4498
  filterNode,
2741
4499
  hideCompressCallsNode,
@@ -2748,6 +4506,7 @@ function createCore(ports = {}) {
2748
4506
  }
2749
4507
  return {
2750
4508
  processTurn,
4509
+ retrieve,
2751
4510
  applyCompression,
2752
4511
  defaultNodes,
2753
4512
  decompress,
@@ -2764,7 +4523,9 @@ var assignRefsNode = {
2764
4523
  const refResult = assignRefs(io.messages, {
2765
4524
  existing: io.state.messageRefs,
2766
4525
  nextIndex: highestUsedIndex(io.state.messageRefs) + 1,
2767
- isProtected: protectedFn
4526
+ isProtected: protectedFn,
4527
+ // Ephemeral retrieval injections never consume a ref slot.
4528
+ shouldSkip: (m) => m.id.startsWith(RETRIEVED_ID_PREFIX)
2768
4529
  });
2769
4530
  return { ...io, state: { ...io.state, messageRefs: refResult.map } };
2770
4531
  }
@@ -2808,6 +4569,27 @@ var absorbPromptNode = {
2808
4569
  };
2809
4570
  }
2810
4571
  };
4572
+ var crushNode = {
4573
+ name: "crush",
4574
+ enabled: (_io, ctx) => ctx.config.crush?.enabled === true && ctx.config.absorb?.enabled === true,
4575
+ run(io, ctx) {
4576
+ const applied = applyCrushToMessages(
4577
+ io.messages,
4578
+ ctx.config,
4579
+ ctx.tokenCount,
4580
+ ctx.countTokens
4581
+ );
4582
+ return {
4583
+ ...io,
4584
+ messages: applied.messages,
4585
+ effects: {
4586
+ ...io.effects,
4587
+ crushCount: applied.crushedCount,
4588
+ crushDistilledCount: applied.distilledCount
4589
+ }
4590
+ };
4591
+ }
4592
+ };
2811
4593
  var filterNode = {
2812
4594
  name: "filter",
2813
4595
  enabled: (_io, ctx) => !!ctx.config.messageFilters?.enabled && listMessageFilters().length > 0,
@@ -4744,6 +6526,294 @@ function formatRulesList(rules) {
4744
6526
  return rules.map((rule, i) => `${i + 1}. [${rule.id}] ${rule.text}`).join("\n");
4745
6527
  }
4746
6528
 
6529
+ // src/image-compress.ts
6530
+ var DEFAULT_IMAGE_COMPRESSION_CONFIG = {
6531
+ enabled: false,
6532
+ minTokens: 512,
6533
+ maxDimension: 1280,
6534
+ quality: 80,
6535
+ format: "webp"
6536
+ };
6537
+ function resolveImageCompressionConfig(config) {
6538
+ return { ...DEFAULT_IMAGE_COMPRESSION_CONFIG, ...config.imageCompression ?? {} };
6539
+ }
6540
+ var PIXEL_IMAGE_FALLBACK_TOKENS = 16384;
6541
+ var TILE_EDGE = 512;
6542
+ var TILE_SHORT_SIDE = 768;
6543
+ var TILE_MAX_EDGE = 2048;
6544
+ function byteAt(b, off) {
6545
+ return b[off] ?? 0;
6546
+ }
6547
+ function u16be(b, off) {
6548
+ return byteAt(b, off) << 8 | byteAt(b, off + 1);
6549
+ }
6550
+ function u16le(b, off) {
6551
+ return byteAt(b, off) | byteAt(b, off + 1) << 8;
6552
+ }
6553
+ function u32be(b, off) {
6554
+ return (byteAt(b, off) << 24 | byteAt(b, off + 1) << 16 | byteAt(b, off + 2) << 8 | byteAt(b, off + 3)) >>> 0;
6555
+ }
6556
+ function i32le(b, off) {
6557
+ return byteAt(b, off) | byteAt(b, off + 1) << 8 | byteAt(b, off + 2) << 16 | byteAt(b, off + 3) << 24;
6558
+ }
6559
+ function parseImageDimensions(bytes) {
6560
+ const b = bytes;
6561
+ if (b.length < 10) return void 0;
6562
+ if (b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) {
6563
+ if (b.length < 24) return void 0;
6564
+ const w = u32be(b, 16);
6565
+ const h = u32be(b, 20);
6566
+ return w > 0 && h > 0 ? { width: w, height: h } : void 0;
6567
+ }
6568
+ if (b[0] === 71 && b[1] === 73 && b[2] === 70) {
6569
+ const w = u16le(b, 6);
6570
+ const h = u16le(b, 8);
6571
+ return w > 0 && h > 0 ? { width: w, height: h } : void 0;
6572
+ }
6573
+ if (b.length >= 26 && b[0] === 66 && b[1] === 77) {
6574
+ const w = i32le(b, 18);
6575
+ const h = Math.abs(i32le(b, 22));
6576
+ return w > 0 && h > 0 ? { width: w, height: h } : void 0;
6577
+ }
6578
+ if (b.length >= 20 && b[0] === 82 && b[1] === 73 && b[2] === 70 && b[3] === 70 && b[8] === 87 && b[9] === 69 && b[10] === 66 && b[11] === 80) {
6579
+ if (b[12] === 86 && b[13] === 80 && b[14] === 56 && b[15] === 88) {
6580
+ if (b.length < 30) return void 0;
6581
+ const w = byteAt(b, 24) | byteAt(b, 25) << 8 | byteAt(b, 26) << 16;
6582
+ const h = byteAt(b, 27) | byteAt(b, 28) << 8 | byteAt(b, 29) << 16;
6583
+ return w > 0 && h > 0 ? { width: w + 1, height: h + 1 } : void 0;
6584
+ }
6585
+ if (b[12] === 86 && b[13] === 80 && b[14] === 56 && b[15] === 76) {
6586
+ if (b.length < 25) return void 0;
6587
+ if (b[20] !== 47) return void 0;
6588
+ const w = ((byteAt(b, 22) & 63) << 8 | byteAt(b, 21)) + 1;
6589
+ const h = (byteAt(b, 24) & 15) << 10 | byteAt(b, 23) << 2 | (byteAt(b, 22) & 192) >> 6;
6590
+ return w > 0 && h > 0 ? { width: w, height: h + 1 } : void 0;
6591
+ }
6592
+ if (b[12] === 86 && b[13] === 80 && b[14] === 56 && b[15] === 32) {
6593
+ if (b.length < 30) return void 0;
6594
+ if (b[23] !== 157 || b[24] !== 1 || b[25] !== 42) return void 0;
6595
+ const w = u16le(b, 26) & 16383;
6596
+ const h = u16le(b, 28) & 16383;
6597
+ return w > 0 && h > 0 ? { width: w, height: h } : void 0;
6598
+ }
6599
+ return void 0;
6600
+ }
6601
+ if (b[0] === 255 && b[1] === 216) {
6602
+ let off = 2;
6603
+ while (off + 4 <= b.length) {
6604
+ if (b[off] !== 255) {
6605
+ off += 1;
6606
+ continue;
6607
+ }
6608
+ const marker = byteAt(b, off + 1);
6609
+ if (marker === 216 || marker === 1 || marker >= 208 && marker <= 217) {
6610
+ off += 2;
6611
+ continue;
6612
+ }
6613
+ if (marker === 218) break;
6614
+ const segLen = u16be(b, off + 2);
6615
+ if (segLen < 2) break;
6616
+ if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
6617
+ if (off + 9 > b.length) break;
6618
+ const h = u16be(b, off + 5);
6619
+ const w = u16be(b, off + 7);
6620
+ if (w > 0 && h > 0) return { width: w, height: h };
6621
+ break;
6622
+ }
6623
+ off += 2 + segLen;
6624
+ }
6625
+ }
6626
+ return void 0;
6627
+ }
6628
+ var HEADER_SCAN_CHARS = 64;
6629
+ var JPEG_SCAN_CHARS = 35e4;
6630
+ function parseImageDimensionsFromBase64(b64) {
6631
+ if (typeof b64 !== "string" || b64.length === 0) return void 0;
6632
+ const head = parseImageDimensions(Buffer.from(b64.slice(0, HEADER_SCAN_CHARS), "base64"));
6633
+ if (head) return head;
6634
+ if (b64.startsWith("/9j/") && b64.length > HEADER_SCAN_CHARS) {
6635
+ return parseImageDimensions(Buffer.from(b64.slice(0, JPEG_SCAN_CHARS), "base64"));
6636
+ }
6637
+ return void 0;
6638
+ }
6639
+ function pixelTileEstimate(width, height) {
6640
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
6641
+ return PIXEL_IMAGE_FALLBACK_TOKENS;
6642
+ }
6643
+ let sw = width;
6644
+ let sh = height;
6645
+ const shortSide = Math.min(sw, sh);
6646
+ if (shortSide > 0 && shortSide < TILE_SHORT_SIDE) {
6647
+ const s = TILE_SHORT_SIDE / shortSide;
6648
+ sw *= s;
6649
+ sh *= s;
6650
+ }
6651
+ const longSide = Math.max(sw, sh);
6652
+ if (longSide > TILE_MAX_EDGE) {
6653
+ const s = TILE_MAX_EDGE / longSide;
6654
+ sw *= s;
6655
+ sh *= s;
6656
+ }
6657
+ const tiles = Math.ceil(sw / TILE_EDGE) * Math.ceil(sh / TILE_EDGE);
6658
+ return 85 + 170 * tiles;
6659
+ }
6660
+ function estimateImageTokens(meta) {
6661
+ const billing = meta.billing ?? "bytes";
6662
+ if (billing === "bytes") return Math.ceil(meta.base64Length / 4);
6663
+ const w = meta.width;
6664
+ const h = meta.height;
6665
+ if (w && h) return pixelTileEstimate(w, h);
6666
+ if (meta.base64) {
6667
+ const dims = parseImageDimensionsFromBase64(meta.base64);
6668
+ if (dims) return pixelTileEstimate(dims.width, dims.height);
6669
+ }
6670
+ return PIXEL_IMAGE_FALLBACK_TOKENS;
6671
+ }
6672
+ function createHeuristicClassifier(options = {}) {
6673
+ const aspectRatioMin = options.aspectRatioMin ?? 1.4;
6674
+ const aspectRatioMax = options.aspectRatioMax ?? 2.6;
6675
+ const minShortSide = options.minShortSide ?? 720;
6676
+ return {
6677
+ name: "heuristic-v1",
6678
+ isScreenshotLike(meta) {
6679
+ const w = meta.width;
6680
+ const h = meta.height;
6681
+ if (!w || !h || w <= 0 || h <= 0) return false;
6682
+ const shortSide = Math.min(w, h);
6683
+ if (shortSide < minShortSide) return false;
6684
+ const ratio = Math.max(w, h) / shortSide;
6685
+ return ratio >= aspectRatioMin && ratio <= aspectRatioMax;
6686
+ }
6687
+ };
6688
+ }
6689
+ var DEFAULT_SCREENSHOT_CLASSIFIER = createHeuristicClassifier();
6690
+ function decideImageRoute(meta, config, classifier = DEFAULT_SCREENSHOT_CLASSIFIER) {
6691
+ const cfg = resolveImageCompressionConfig(config);
6692
+ const estimatedTokens = estimateImageTokens(meta);
6693
+ if (!cfg.enabled) return { action: "pass", reason: "disabled", estimatedTokens };
6694
+ if (!meta.mediaType.startsWith("image/")) {
6695
+ return { action: "pass", reason: "not-an-image", estimatedTokens };
6696
+ }
6697
+ if (estimatedTokens < cfg.minTokens) {
6698
+ return { action: "pass", reason: "below-min-tokens", estimatedTokens };
6699
+ }
6700
+ if (!classifier.isScreenshotLike(meta)) {
6701
+ return { action: "pass", reason: "not-screenshot-like", estimatedTokens };
6702
+ }
6703
+ return {
6704
+ action: "downsample",
6705
+ reason: "downsample",
6706
+ estimatedTokens,
6707
+ recipe: { maxDimension: cfg.maxDimension, quality: cfg.quality, format: cfg.format }
6708
+ };
6709
+ }
6710
+ var IMAGE_FULL_FAILURE_MARKER = "[image_full FAILED:";
6711
+ function buildImageFullSystemNote(count) {
6712
+ return `[Downscaled screenshots: ${count} image(s) were reduced before entering context. If you cannot read details (text, colors, alignment) in a reduced image, call ${IMAGE_FULL_TOOL_NAME} with that message's ref ("mNNNNN") to restore the original resolution.]`;
6713
+ }
6714
+ function parseImageFullInput(input, callId, onWarn) {
6715
+ let obj = null;
6716
+ if (typeof input === "string") {
6717
+ try {
6718
+ const parsed = JSON.parse(input);
6719
+ if (parsed && typeof parsed === "object") obj = parsed;
6720
+ } catch {
6721
+ obj = null;
6722
+ }
6723
+ } else if (input && typeof input === "object") {
6724
+ obj = input;
6725
+ }
6726
+ if (!obj) {
6727
+ onWarn?.(`[acp-image-full-input] rejected: not an object (${typeof input})`);
6728
+ return null;
6729
+ }
6730
+ let ref;
6731
+ for (const key of ["ref", "messageId", "of"]) {
6732
+ const value = obj[key];
6733
+ if (typeof value === "string") {
6734
+ ref = value;
6735
+ break;
6736
+ }
6737
+ }
6738
+ if (typeof ref !== "string") {
6739
+ onWarn?.(
6740
+ `[acp-image-full-input] rejected: need ref (string); keys: ${Object.keys(obj).join(",")}`
6741
+ );
6742
+ return null;
6743
+ }
6744
+ const trimmed = ref.trim();
6745
+ if (!trimmed) {
6746
+ onWarn?.("[acp-image-full-input] rejected: ref is empty");
6747
+ return null;
6748
+ }
6749
+ return { ref: trimmed, ...callId ? { callId } : {} };
6750
+ }
6751
+ function recordImageShrink(state, record) {
6752
+ const bytesSaved = Math.max(0, record.originalBytes - record.shrunkBytes);
6753
+ const tokensSaved = Math.max(0, record.tokensBefore - record.tokensAfter);
6754
+ return {
6755
+ ...state,
6756
+ imageShrinks: [...state.imageShrinks ?? [], record],
6757
+ stats: {
6758
+ ...state.stats,
6759
+ imagesShrunk: (state.stats.imagesShrunk ?? 0) + 1,
6760
+ imageBytesSaved: (state.stats.imageBytesSaved ?? 0) + bytesSaved,
6761
+ imageTokensSaved: (state.stats.imageTokensSaved ?? 0) + tokensSaved
6762
+ }
6763
+ };
6764
+ }
6765
+ function isImageFullRestored(state, ref) {
6766
+ return (state.imageFullRestored ?? []).includes(ref);
6767
+ }
6768
+ function imageShrinksForRef(state, ref) {
6769
+ return (state.imageShrinks ?? []).filter((r) => r.ref === ref);
6770
+ }
6771
+ function applyImageFull(input) {
6772
+ const cfg = resolveImageCompressionConfig(input.config);
6773
+ const ref = input.ref.trim();
6774
+ if (!cfg.enabled) {
6775
+ return {
6776
+ state: input.state,
6777
+ ok: false,
6778
+ resultText: `${IMAGE_FULL_FAILURE_MARKER} image compression is disabled in this session \u2014 no image was ever downscaled]`
6779
+ };
6780
+ }
6781
+ const rawId = rawForRef(input.state.messageRefs, ref);
6782
+ if (!rawId) {
6783
+ return {
6784
+ state: input.state,
6785
+ ok: false,
6786
+ resultText: `${IMAGE_FULL_FAILURE_MARKER} ref ${ref} does not exist in this session]`
6787
+ };
6788
+ }
6789
+ if (isImageFullRestored(input.state, ref)) {
6790
+ return {
6791
+ state: input.state,
6792
+ ok: true,
6793
+ resultText: `already restored (${ref}) \u2014 full resolution stays in effect for the rest of this session.`
6794
+ };
6795
+ }
6796
+ const shrinks = imageShrinksForRef(input.state, ref);
6797
+ if (shrinks.length === 0) {
6798
+ return {
6799
+ state: input.state,
6800
+ ok: false,
6801
+ resultText: `${IMAGE_FULL_FAILURE_MARKER} no downscaled image is recorded for ${ref} \u2014 it was passed through untouched or never carried an image]`
6802
+ };
6803
+ }
6804
+ return {
6805
+ state: {
6806
+ ...input.state,
6807
+ imageFullRestored: [...input.state.imageFullRestored ?? [], ref]
6808
+ },
6809
+ ok: true,
6810
+ resultText: `restored original-resolution image(s) for ${ref} (${shrinks.length} image${shrinks.length === 1 ? "" : "s"}); full resolution applies for the rest of this session.`
6811
+ };
6812
+ }
6813
+ function resetImageFullState(state) {
6814
+ return { ...state, imageFullRestored: [], imageShrinks: [] };
6815
+ }
6816
+
4747
6817
  // src/parse-compress-input.ts
4748
6818
  function parseCompressArgs(input, opts) {
4749
6819
  const callId = opts?.callId;
@@ -5272,7 +7342,8 @@ function rebuildCompressionState(state, messages, config, ports = {}) {
5272
7342
  const core = createCore({ countTokens: ports.countTokens ?? defaultCountTokens });
5273
7343
  const refResult = assignRefs(messages, {
5274
7344
  existing: state.messageRefs,
5275
- nextIndex: highestUsedIndex(state.messageRefs) + 1
7345
+ nextIndex: highestUsedIndex(state.messageRefs) + 1,
7346
+ shouldSkip: (m) => m.id.startsWith(RETRIEVED_ID_PREFIX)
5276
7347
  });
5277
7348
  let working = { ...state, messageRefs: refResult.map };
5278
7349
  const invocations = collectCompressInvocations(messages);
@@ -5636,6 +7707,129 @@ function makePreview(text, query, len) {
5636
7707
  const suffix = end < text.length ? "\u2026" : "";
5637
7708
  return prefix + clampWindow(text, start, end).trim() + suffix;
5638
7709
  }
7710
+
7711
+ // src/output-steering.ts
7712
+ function classifyTurn(messages) {
7713
+ if (messages.length === 0) return "unknown";
7714
+ const last = messages[messages.length - 1];
7715
+ if (!last || last.role !== "user") return "unknown";
7716
+ if (typeof last.text === "string")
7717
+ return last.text.trim() ? "new_user_ask" : "unknown";
7718
+ const blocks = last.blocks;
7719
+ if (!Array.isArray(blocks) || blocks.length === 0) return "unknown";
7720
+ let sawToolResult = false;
7721
+ let sawError = false;
7722
+ for (const b of blocks) {
7723
+ if (!b) return "unknown";
7724
+ switch (b.kind) {
7725
+ case "tool_result":
7726
+ sawToolResult = true;
7727
+ if (b.isError === true) sawError = true;
7728
+ break;
7729
+ case "text":
7730
+ case "image":
7731
+ case "document":
7732
+ return "new_user_ask";
7733
+ default:
7734
+ return "unknown";
7735
+ }
7736
+ }
7737
+ if (sawError) return "error_continuation";
7738
+ if (sawToolResult) return "mechanical_continuation";
7739
+ return "unknown";
7740
+ }
7741
+ var MIN_VERBOSITY_LEVEL = 0;
7742
+ var MAX_VERBOSITY_LEVEL = 4;
7743
+ var DEFAULT_VERBOSITY_LEVEL = 2;
7744
+ var VERBOSITY_LEVELS = {
7745
+ 1: "Skip preamble and postamble. Do not announce what you are about to do or recap what you just did; start with the substance.",
7746
+ 2: "Skip preamble and postamble; start with the substance. Never restate code, file contents, diffs, or tool output that already appear in this conversation \u2014 reference them by path and line instead. After a tool call succeeds, continue without narrating the result.",
7747
+ 3: "Skip preamble and postamble. Never restate code, file contents, diffs, or tool output already in this conversation \u2014 cite the exact file path and line or symbol instead, always; a reference that omits the location is not a reference. Give conclusions only; omit rationale unless the user asks why. Prefer the smallest edit over rewriting whole files. Keep prose to the minimum needed to be unambiguous. Never drop anything the turn or task needs to be correct, including negations (not, never, no, only, except) \u2014 shorten how you say it, not what you say. Use full prose for destructive or irreversible actions, security warnings, and any multi-step sequence where brevity would create ambiguity.",
7748
+ 4: "Minimum tokens. Fragments fine. No preamble, no postamble, no restating context, no rationale. Answer, smallest-possible edits, nothing else. Never drop anything the turn or task needs to be correct, including negations (not, never, no, only, except). Use full prose for destructive or irreversible actions, security warnings, and any multi-step sequence where brevity would create ambiguity."
7749
+ };
7750
+ function verbosityDirective(level) {
7751
+ if (!Number.isInteger(level)) return null;
7752
+ return VERBOSITY_LEVELS[level] ?? null;
7753
+ }
7754
+ var DEFAULT_STEERING_SENTINEL = "<acp_output_steering>";
7755
+ function renderSteeringBlock(level, sentinel = DEFAULT_STEERING_SENTINEL) {
7756
+ const text = verbosityDirective(level);
7757
+ if (text === null) return null;
7758
+ return `${sentinel}
7759
+ ${text}
7760
+ </${sentinel.slice(1)}`;
7761
+ }
7762
+ function applySteeringToPrompt(existing, block, sentinel = DEFAULT_STEERING_SENTINEL) {
7763
+ const suffix = `</${sentinel.slice(1)}`;
7764
+ const start = existing.indexOf(sentinel);
7765
+ if (start >= 0) {
7766
+ const found = existing.indexOf(suffix, start);
7767
+ const end = found < 0 ? existing.length : found + suffix.length;
7768
+ const prefix = existing.slice(0, start).replace(/\s+$/, "");
7769
+ const tail = existing.slice(end).replace(/^\n+/, "");
7770
+ const parts = [prefix, block, tail].filter((p) => p.length > 0);
7771
+ const updated2 = parts.join("\n\n");
7772
+ return { updated: updated2, changed: updated2 !== existing };
7773
+ }
7774
+ const trimmed = existing.trim();
7775
+ const updated = trimmed.length > 0 ? `${existing.replace(/\s+$/, "")}
7776
+
7777
+ ${block}` : block;
7778
+ return { updated, changed: updated !== existing };
7779
+ }
7780
+ var EFFORT_LADDER = [
7781
+ "minimal",
7782
+ "low",
7783
+ "medium",
7784
+ "high",
7785
+ "xhigh"
7786
+ ];
7787
+ function clampEffortToFloor(current, floor = "low") {
7788
+ if (typeof current !== "string") return null;
7789
+ const ci = EFFORT_LADDER.indexOf(current);
7790
+ if (ci === -1) return null;
7791
+ const fi = EFFORT_LADDER.indexOf(floor);
7792
+ return ci > fi ? floor : null;
7793
+ }
7794
+ var DEFAULT_OUTPUT_STEERING_CONFIG = {
7795
+ enabled: false,
7796
+ verbosityLevel: DEFAULT_VERBOSITY_LEVEL,
7797
+ effortRouting: true
7798
+ };
7799
+ function resolveVerbosityLevel(v) {
7800
+ if (typeof v === "number" && Number.isInteger(v) && v >= MIN_VERBOSITY_LEVEL && v <= MAX_VERBOSITY_LEVEL) {
7801
+ return { level: v };
7802
+ }
7803
+ const level = DEFAULT_OUTPUT_STEERING_CONFIG.verbosityLevel;
7804
+ if (v === void 0) return { level };
7805
+ return {
7806
+ level,
7807
+ warning: `[config] outputSteering.verbosityLevel must be an integer ${MIN_VERBOSITY_LEVEL}-${MAX_VERBOSITY_LEVEL}; got ${JSON.stringify(v)} \u2014 falling back to ${level}`
7808
+ };
7809
+ }
7810
+ function resolveOutputSteeringConfig(v) {
7811
+ if (!v || typeof v !== "object" || Array.isArray(v)) {
7812
+ return { config: DEFAULT_OUTPUT_STEERING_CONFIG, warnings: [] };
7813
+ }
7814
+ const obj = v;
7815
+ const lvl = resolveVerbosityLevel(obj.verbosityLevel);
7816
+ return {
7817
+ config: {
7818
+ enabled: obj.enabled === true,
7819
+ verbosityLevel: lvl.level,
7820
+ effortRouting: obj.effortRouting !== false
7821
+ },
7822
+ warnings: lvl.warning ? [lvl.warning] : []
7823
+ };
7824
+ }
7825
+ function decideOutputSteering(messages, config = DEFAULT_OUTPUT_STEERING_CONFIG) {
7826
+ const turnKind = classifyTurn(messages);
7827
+ return {
7828
+ turnKind,
7829
+ verbosityLevel: config.enabled ? config.verbosityLevel : 0,
7830
+ lowerEffort: config.enabled && config.effortRouting && turnKind === "mechanical_continuation"
7831
+ };
7832
+ }
5639
7833
  export {
5640
7834
  ABSORB_PROMPT_MARKER,
5641
7835
  ABSORB_TOOL,
@@ -5684,10 +7878,33 @@ export {
5684
7878
  DECOMPRESS_TOOL_RESPONSES,
5685
7879
  DEFAULT_ABSORB_CONFIG,
5686
7880
  DEFAULT_ALGORITHM,
7881
+ DEFAULT_CCR_CONFIG,
7882
+ DEFAULT_CRUSH_CONFIG,
7883
+ DEFAULT_IMAGE_COMPRESSION_CONFIG,
7884
+ DEFAULT_OUTPUT_STEERING_CONFIG,
5687
7885
  DEFAULT_ROLE_WEIGHTS,
5688
7886
  DEFAULT_RULE_LIMITS,
7887
+ DEFAULT_SCREENSHOT_CLASSIFIER,
7888
+ DEFAULT_STEERING_SENTINEL,
7889
+ DEFAULT_VERBOSITY_LEVEL,
7890
+ EFFORT_LADDER,
5689
7891
  HOW_TO_COMPRESS_RULES,
7892
+ IMAGE_FULL_FAILURE_MARKER,
7893
+ IMAGE_FULL_TOOL,
7894
+ IMAGE_FULL_TOOL_DESCRIPTION,
7895
+ IMAGE_FULL_TOOL_NAME,
7896
+ IMAGE_FULL_TOOL_OPENAI,
7897
+ IMAGE_FULL_TOOL_RESPONSES,
5690
7898
  LEAN_HOW_TO_COMPRESS,
7899
+ MAX_VERBOSITY_LEVEL,
7900
+ MIN_VERBOSITY_LEVEL,
7901
+ PIXEL_IMAGE_FALLBACK_TOKENS,
7902
+ RETRIEVED_ID_PREFIX,
7903
+ RETRIEVE_TOOL,
7904
+ RETRIEVE_TOOL_DESCRIPTION,
7905
+ RETRIEVE_TOOL_NAME,
7906
+ RETRIEVE_TOOL_OPENAI,
7907
+ RETRIEVE_TOOL_RESPONSES,
5691
7908
  RULES_USAGE_PROMPT,
5692
7909
  RULE_TOOL_NAME,
5693
7910
  SEARCH_CONTEXT_TOOL,
@@ -5695,9 +7912,11 @@ export {
5695
7912
  SEARCH_CONTEXT_TOOL_NAME,
5696
7913
  SEARCH_CONTEXT_TOOL_OPENAI,
5697
7914
  SEARCH_CONTEXT_TOOL_RESPONSES,
7915
+ STORED_PLACEHOLDER_MARKER,
5698
7916
  SUMMARY_HEADER,
5699
7917
  TIER2_DISTILL_RULES,
5700
7918
  TIER3_CONDENSE_RULES,
7919
+ VERBOSITY_LEVELS,
5701
7920
  VIABLE_RANGE_MIN_TOKENS,
5702
7921
  activeBlockSpans,
5703
7922
  activeBlocks,
@@ -5709,8 +7928,12 @@ export {
5709
7928
  appendAbsorbPrompts,
5710
7929
  applyAbsorb,
5711
7930
  applyAcpToolOverrides,
7931
+ applyCrushToMessages,
7932
+ applyImageFull,
5712
7933
  applyMessageFilters,
7934
+ applyRetrieve,
5713
7935
  applySectionOverrides,
7936
+ applySteeringToPrompt,
5714
7937
  assignRefs,
5715
7938
  blockById,
5716
7939
  blockDocs,
@@ -5721,26 +7944,41 @@ export {
5721
7944
  buildCompressHybridSystemPrompt,
5722
7945
  buildCompressSystemPrompt,
5723
7946
  buildCompressTextSystemPrompt,
7947
+ buildImageFullSystemNote,
5724
7948
  buildRecap,
5725
7949
  buildRestoredContentPreview,
7950
+ buildRetrievalInjection,
5726
7951
  buildStatusReport,
7952
+ buildStoredPlaceholder,
5727
7953
  builtinSource,
7954
+ ccrStoreNode,
7955
+ clampEffortToFloor,
7956
+ classifyCrushText,
7957
+ classifyKind,
7958
+ classifyTurn,
5728
7959
  clearDocFeatures,
5729
7960
  clearMessageFilters,
5730
7961
  clearRules,
5731
7962
  cloneWithDescriptions,
7963
+ codeTrimPlugin,
5732
7964
  collectBlockContent,
5733
7965
  collectLatestProtected,
5734
7966
  computeFoldEconomics,
7967
+ contentStoreStats,
5735
7968
  countMessageTokens,
5736
7969
  coveredMessageIds,
5737
7970
  createBpeTokenizer,
7971
+ createContentStore,
5738
7972
  createCore,
5739
7973
  createDirPackSource,
7974
+ createHeuristicClassifier,
5740
7975
  createInitialState,
5741
7976
  createPackResolver,
5742
7977
  createRenderRefsNode,
7978
+ crushText,
5743
7979
  deactivateBlock,
7980
+ decideImageRoute,
7981
+ decideOutputSteering,
5744
7982
  decomposeSample,
5745
7983
  defaultConfig,
5746
7984
  defaultCountTokens,
@@ -5750,7 +7988,10 @@ export {
5750
7988
  docCacheInfo,
5751
7989
  docFeatures,
5752
7990
  emptyRefMap,
7991
+ estimateImageTokens,
5753
7992
  estimateTokensFast,
7993
+ evaluateToolResult,
7994
+ extractCommand,
5754
7995
  findActiveAncestor,
5755
7996
  findBlocksOverlappingMessages,
5756
7997
  formatCacheReport,
@@ -5760,35 +8001,52 @@ export {
5760
8001
  formatRulesList,
5761
8002
  getMessageFilter,
5762
8003
  getSearchAlgorithm,
8004
+ hasStoredRef,
8005
+ hashContent,
5763
8006
  hideAbsorbedMessages,
5764
8007
  hideConsumedCompressCalls,
5765
8008
  highestActiveTier,
5766
8009
  highestUsedIndex,
8010
+ imageShrinksForRef,
5767
8011
  indexToRef,
5768
8012
  isAbsorbCandidate,
8013
+ isImageFullRestored,
5769
8014
  isMessageLatestProtected,
5770
8015
  isMessageProtected,
5771
8016
  isRenderedSummaryMessage,
8017
+ isRetrievedMessage,
8018
+ isStoredPlaceholderText,
5772
8019
  isSummaryMessageId,
5773
8020
  isValidPackName,
8021
+ jsonFoldPlugin,
5774
8022
  leanPack,
8023
+ listCrushPlugins,
5775
8024
  listMessageFilters,
5776
8025
  listRules,
5777
8026
  listSearchAlgorithms,
8027
+ logSelectPlugin,
5778
8028
  makeIO,
5779
8029
  matchSession,
5780
8030
  matchToolPattern,
5781
8031
  messageDocs,
8032
+ normalizeHead,
8033
+ noteRetrieval,
5782
8034
  parseAbsorbInput,
5783
8035
  parseBlockIdArg,
5784
8036
  parseBoundary,
5785
8037
  parseCompressArgs,
5786
8038
  parseCompressInput,
8039
+ parseImageDimensions,
8040
+ parseImageDimensionsFromBase64,
8041
+ parseImageFullInput,
8042
+ pixelTileEstimate,
5787
8043
  prune,
5788
8044
  rawForRef,
5789
8045
  rebuildCompressionState,
8046
+ recordImageShrink,
5790
8047
  refForRaw,
5791
8048
  refToIndex,
8049
+ registerCrushPlugin,
5792
8050
  registerMessageFilter,
5793
8051
  registerSearchAlgorithm,
5794
8052
  removeRule,
@@ -5796,23 +8054,38 @@ export {
5796
8054
  renderMessage2 as renderMessage,
5797
8055
  renderNudgeText,
5798
8056
  renderRefsNode,
8057
+ renderSteeringBlock,
5799
8058
  renderVisibleRefs,
8059
+ resetCrushPlugins,
8060
+ resetImageFullState,
5800
8061
  resolveAbsorbConfig,
5801
8062
  resolveBlockSpan,
5802
8063
  resolveBoundaries,
8064
+ resolveCcrConfig,
8065
+ resolveCrushConfig,
8066
+ resolveImageCompressionConfig,
8067
+ resolveOutputSteeringConfig,
5803
8068
  resolvePrompts,
5804
8069
  resolveRuleLimits,
5805
8070
  resolveTransformChannel,
8071
+ resolveVerbosityLevel,
8072
+ retrieveByRef,
8073
+ retrievedMessageId,
5806
8074
  runPipeline,
5807
8075
  sanitizePackSurface,
5808
8076
  searchBlocks,
5809
8077
  searchBlocksAsync,
5810
8078
  setDocCacheCap,
8079
+ storeCoveredOriginals,
8080
+ storeLargeResults,
8081
+ storeOriginal,
5811
8082
  summarizeFoldEconomics,
5812
8083
  summaryMessageId,
5813
8084
  syncBlocks,
5814
8085
  truncateLargeToolOutputs,
8086
+ unregisterCrushPlugin,
5815
8087
  validateConfig,
8088
+ verbosityDirective,
5816
8089
  viableRanges,
5817
8090
  visibleBlockAnchor
5818
8091
  };