acp-kernel 0.0.83 → 0.0.84-pr.365.231

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 (45) 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 +13 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +2948 -640
  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/protected.d.ts +1 -0
  34. package/dist/protected.d.ts.map +1 -1
  35. package/dist/rebuild.d.ts.map +1 -1
  36. package/dist/recommend.d.ts.map +1 -1
  37. package/dist/state.d.ts.map +1 -1
  38. package/dist/types.d.ts +82 -0
  39. package/dist/types.d.ts.map +1 -1
  40. package/dist/wire/index.js +19 -2
  41. package/dist/wire/index.js.map +1 -1
  42. package/dist/wire/openai.d.ts.map +1 -1
  43. package/package.json +1 -1
  44. package/dist/chunk-5TQA7ZQM.js.map +0 -1
  45. /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"];
@@ -1551,224 +1250,2289 @@ function isMessageLatestProtected(msg, latest) {
1551
1250
  }
1552
1251
  return false;
1553
1252
  }
1554
-
1555
- // src/absorb.ts
1556
- var ABSORB_PROMPT_MARKER = "[ACP absorb]";
1557
- var DEFAULT_ABSORB_CONFIG = {
1558
- enabled: false,
1559
- toolName: ABSORB_TOOL_NAME,
1560
- minToolTokens: 1e3,
1561
- contextThresholdPct: 0,
1562
- excludeTools: []
1563
- };
1564
- function resolveAbsorbConfig(config) {
1565
- return { ...DEFAULT_ABSORB_CONFIG, ...config.absorb ?? {} };
1253
+ function hasMediaPayload(msg) {
1254
+ const m = msg;
1255
+ if (typeof m.imageBase64 === "string" && m.imageBase64.length > 0) return true;
1256
+ if (m.rawOpenaiContent != null) return true;
1257
+ if (Array.isArray(m.rawOpenaiContentParts) && m.rawOpenaiContentParts.length > 0)
1258
+ return true;
1259
+ if (isObjWith(m.rawAnthropicBlock, "type", "image")) return true;
1260
+ const item = m.rawResponsesItem;
1261
+ if (isObjWith(item, "type", "input_image")) return true;
1262
+ if (item && typeof item === "object") {
1263
+ const content = item.content;
1264
+ if (Array.isArray(content)) {
1265
+ return content.some((p) => isObjWith(p, "type", "input_image"));
1266
+ }
1267
+ }
1268
+ return false;
1566
1269
  }
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";
1270
+ function isObjWith(v, key, value) {
1271
+ return typeof v === "object" && v !== null && v[key] === value;
1571
1272
  }
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)".`;
1273
+
1274
+ // src/content-store.ts
1275
+ import { createHash } from "crypto";
1276
+ function createContentStore() {
1277
+ return { version: 1, byHash: {}, byRef: {} };
1278
+ }
1279
+ function hashContent(text) {
1280
+ return createHash("sha256").update(text, "utf8").digest("hex");
1281
+ }
1282
+ function storeOriginal(store, spec) {
1283
+ if (!spec.ref || store.byRef[spec.ref]) return store;
1284
+ const hash = hashContent(spec.text);
1285
+ const entry = {
1286
+ hash,
1287
+ rawId: spec.rawId,
1288
+ kind: spec.kind,
1289
+ tokens: spec.tokens,
1290
+ chars: spec.text.length,
1291
+ head: spec.head
1292
+ };
1293
+ if (spec.toolName !== void 0) entry.toolName = spec.toolName;
1294
+ const byHash = store.byHash[hash] === void 0 ? { ...store.byHash, [hash]: spec.text } : store.byHash;
1295
+ return { ...store, byHash, byRef: { ...store.byRef, [spec.ref]: entry } };
1296
+ }
1297
+ function retrieveByRef(store, ref) {
1298
+ const entry = store.byRef[ref];
1299
+ if (!entry) return { ok: false, reason: "not-found" };
1300
+ const text = store.byHash[entry.hash];
1301
+ if (text === void 0) return { ok: false, reason: "not-found" };
1302
+ return { ok: true, text, entry };
1303
+ }
1304
+ function hasStoredRef(store, ref) {
1305
+ return store.byRef[ref] !== void 0;
1306
+ }
1307
+ function contentStoreStats(store) {
1308
+ let totalChars = 0;
1309
+ for (const text of Object.values(store.byHash)) totalChars += text.length;
1310
+ return {
1311
+ entries: Object.keys(store.byRef).length,
1312
+ uniqueContents: Object.keys(store.byHash).length,
1313
+ totalChars
1314
+ };
1574
1315
  }
1575
- function buildAbsorbSystemPrompt(toolName = ABSORB_TOOL_NAME) {
1576
- return `INSTANT TOOL-RESULT ABSORPTION (${toolName})
1577
1316
 
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.`;
1317
+ // src/ccr.ts
1318
+ var RETRIEVE_TOOL_NAME2 = "acp_retrieve";
1319
+ var DEFAULT_CCR_CONFIG = {
1320
+ enabled: false,
1321
+ toolName: RETRIEVE_TOOL_NAME2,
1322
+ minToolTokens: 4e3,
1323
+ excludeTools: [],
1324
+ maxHeadChars: 96
1325
+ };
1326
+ function resolveCcrConfig(config) {
1327
+ return { ...DEFAULT_CCR_CONFIG, ...config.ccr };
1328
+ }
1329
+ var STORED_PLACEHOLDER_MARKER = "[acp-stored";
1330
+ var RETRIEVED_ID_PREFIX = "acp_retrieved_";
1331
+ var KIND_LABELS = {
1332
+ bash: "shell output",
1333
+ shell: "shell output",
1334
+ exec: "shell output",
1335
+ execute_command: "shell output",
1336
+ run: "shell output",
1337
+ terminal: "shell output",
1338
+ read: "file read",
1339
+ read_file: "file read",
1340
+ cat: "file read",
1341
+ open: "file read",
1342
+ grep: "search output",
1343
+ rg: "search output",
1344
+ search: "search output",
1345
+ glob: "search output",
1346
+ find: "search output",
1347
+ webfetch: "web fetch",
1348
+ web_fetch: "web fetch",
1349
+ fetch: "web fetch",
1350
+ curl: "web fetch"
1351
+ };
1352
+ function classifyKind(toolName) {
1353
+ if (!toolName) return "tool result";
1354
+ return KIND_LABELS[toolName.toLowerCase()] ?? "tool result";
1355
+ }
1356
+ function groupThousands(value) {
1357
+ return String(Math.max(0, Math.round(value))).replace(
1358
+ /\B(?=(\d{3})+(?!\d))/g,
1359
+ ","
1360
+ );
1579
1361
  }
1580
- function isAcpOrConfiguredTool(toolName, cfg) {
1581
- if (!toolName) return false;
1582
- if (toolName === cfg.toolName) return true;
1583
- return ACP_TOOL_NAMES.has(toolName);
1362
+ function normalizeHead(text, maxChars) {
1363
+ const singleLine = (text || "").replace(/\s+/g, " ").trim();
1364
+ if (singleLine.length <= maxChars) return singleLine;
1365
+ return singleLine.slice(0, maxChars) + "\u2026";
1584
1366
  }
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;
1367
+ var COMMAND_FIELDS = ["command", "cmd", "script", "query", "path", "url"];
1368
+ function extractCommand(toolCallText, maxChars) {
1369
+ if (!toolCallText) return void 0;
1370
+ let parsed;
1371
+ try {
1372
+ parsed = JSON.parse(toolCallText);
1373
+ } catch {
1374
+ return void 0;
1593
1375
  }
1594
- return true;
1595
- }
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);
1376
+ if (typeof parsed !== "object" || parsed === null) return void 0;
1377
+ const record = parsed;
1378
+ for (const field of COMMAND_FIELDS) {
1379
+ const value = record[field];
1380
+ if (typeof value === "string" && value.trim().length > 0) {
1381
+ const normalized = value.replace(/\s+/g, " ").trim();
1382
+ return normalized.length <= maxChars ? normalized : normalized.slice(0, maxChars) + "\u2026";
1383
+ }
1603
1384
  }
1604
- return messages.filter((msg) => !hidden.has(msg.id));
1385
+ return void 0;
1605
1386
  }
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 };
1387
+ function buildStoredPlaceholder(input) {
1388
+ const title = input.command ?? input.head;
1389
+ const titlePart = title ? ` \`${title}\`` : "";
1390
+ return `\u{1F4E6} ${STORED_PLACEHOLDER_MARKER} #${input.ref} \xB7 ${input.kind} \xB7 ${groupThousands(input.tokens)} tok]${titlePart}
1391
+ \u2192 ${input.retrieveToolName}("${input.ref}") returns the full text`;
1392
+ }
1393
+ var LEADING_TAG_RE = new RegExp("^\\x3cacp [^>]*>[^\\x3c]*\\x3c\\/acp>\\n?");
1394
+ function stripLeadingTag(text) {
1395
+ return text.replace(LEADING_TAG_RE, "");
1396
+ }
1397
+ function isStoredPlaceholderText(text) {
1398
+ return stripLeadingTag(text).includes(STORED_PLACEHOLDER_MARKER);
1399
+ }
1400
+ function retrievedMessageId(ref) {
1401
+ return RETRIEVED_ID_PREFIX + ref;
1402
+ }
1403
+ function isRetrievedMessage(message) {
1404
+ return message.id.startsWith(RETRIEVED_ID_PREFIX) && message.role === "system" && message.contentType === "text";
1405
+ }
1406
+ function buildRetrievalInjection(ref, entry, text) {
1407
+ const header = `[acp-retrieved #${ref} \xB7 ${entry.kind} \xB7 ${groupThousands(entry.tokens)} tok]`;
1408
+ return {
1409
+ ackText: `retrieved ${ref}: ${groupThousands(entry.tokens)} tok (${entry.chars} chars)`,
1410
+ injection: {
1411
+ id: retrievedMessageId(ref),
1412
+ role: "system",
1413
+ contentType: "text",
1414
+ text: `${header}
1415
+ ${text}`
1416
+ }
1417
+ };
1418
+ }
1419
+ function applyRetrieve(input) {
1420
+ const ref = input.ref.trim();
1421
+ const found = retrieveByRef(input.store, ref);
1422
+ if (!found.ok) {
1423
+ return {
1424
+ ok: false,
1425
+ reason: "not-found",
1426
+ ackText: `retrieve ${ref}: not found \u2014 no stored original for this ref`
1427
+ };
1612
1428
  }
1613
- const absorbedIds = /* @__PURE__ */ new Set();
1614
- for (const record of state.absorbed ?? []) {
1615
- if (record.resultMessageId) absorbedIds.add(record.resultMessageId);
1429
+ const built = buildRetrievalInjection(ref, found.entry, found.text);
1430
+ return {
1431
+ ok: true,
1432
+ text: found.text,
1433
+ ackText: built.ackText,
1434
+ injection: built.injection,
1435
+ entry: found.entry
1436
+ };
1437
+ }
1438
+ function storeLargeResults(input) {
1439
+ const cfg = resolveCcrConfig(input.config);
1440
+ if (!cfg.enabled)
1441
+ return { messages: input.messages, store: input.store, storedCount: 0 };
1442
+ let current = input.store;
1443
+ let storedCount = 0;
1444
+ const callById = /* @__PURE__ */ new Map();
1445
+ for (const message of input.messages) {
1446
+ if (message.contentType === "tool-call" && message.toolCallId) {
1447
+ callById.set(message.toolCallId, message);
1448
+ }
1616
1449
  }
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++;
1450
+ const updated = input.messages.map((message) => {
1451
+ if (message.contentType !== "tool-result") return message;
1452
+ const text = message.text ?? "";
1453
+ if (text.length === 0) return message;
1454
+ if (isStoredPlaceholderText(text)) return message;
1455
+ if (!message.toolCallId) return message;
1456
+ const toolName = message.toolName;
1457
+ if (toolName && (ACP_TOOL_NAMES.has(toolName) || toolName === cfg.toolName)) {
1458
+ return message;
1459
+ }
1460
+ if (toolName && cfg.excludeTools.some((pattern) => matchToolPattern(toolName, pattern))) {
1461
+ return message;
1462
+ }
1463
+ if (isMessageProtected(message, input.config)) return message;
1464
+ const tokens = input.countTokens(text);
1465
+ if (tokens < cfg.minToolTokens) return message;
1466
+ const ref = refForRaw(input.state.messageRefs, message.id);
1467
+ if (!ref || ref === BLOCKED_REF) return message;
1468
+ if (hasStoredRef(current, ref)) return message;
1469
+ const kind = classifyKind(toolName);
1470
+ const head = normalizeHead(text, cfg.maxHeadChars);
1471
+ const command = extractCommand(
1472
+ callById.get(message.toolCallId)?.text,
1473
+ cfg.maxHeadChars
1474
+ );
1475
+ current = storeOriginal(current, {
1476
+ ref,
1477
+ rawId: message.id,
1478
+ text,
1479
+ kind,
1480
+ toolName,
1481
+ tokens,
1482
+ head
1483
+ });
1484
+ storedCount += 1;
1629
1485
  return {
1630
- ...msg,
1631
- text: text + "\n\n" + buildAbsorbPrompt(ref, tokens, cfg.toolName)
1486
+ ...message,
1487
+ text: buildStoredPlaceholder({
1488
+ ref,
1489
+ kind,
1490
+ tokens,
1491
+ head,
1492
+ command,
1493
+ retrieveToolName: cfg.toolName
1494
+ })
1632
1495
  };
1633
1496
  });
1634
- return { messages: out, promptedCount };
1497
+ return { messages: updated, store: current, storedCount };
1635
1498
  }
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;
1646
- }
1647
- } else if (input && typeof input === "object") {
1648
- obj = input;
1499
+ var ccrStoreNode = {
1500
+ name: "ccr-store",
1501
+ enabled: (_io, ctx) => resolveCcrConfig(ctx.config).enabled,
1502
+ run(io, ctx) {
1503
+ const applied = storeLargeResults({
1504
+ messages: io.messages,
1505
+ state: io.state,
1506
+ store: ctx.contentStore,
1507
+ config: ctx.config,
1508
+ countTokens: ctx.countTokens
1509
+ });
1510
+ const effect = {
1511
+ store: applied.store,
1512
+ storedCount: applied.storedCount
1513
+ };
1514
+ const stats = applied.storedCount > 0 ? {
1515
+ ...io.state.stats,
1516
+ storedCount: (io.state.stats.storedCount ?? 0) + applied.storedCount
1517
+ } : io.state.stats;
1518
+ return {
1519
+ ...io,
1520
+ messages: applied.messages,
1521
+ state: stats === io.state.stats ? io.state : { ...io.state, stats },
1522
+ effects: { ...io.effects, ccr: effect }
1523
+ };
1649
1524
  }
1650
- if (!obj) {
1651
- onWarn?.(`[acp-absorb-input] rejected: not an object (${typeof input})`);
1652
- return null;
1525
+ };
1526
+ function storeCoveredOriginals(store, messages, state, blockIds, countTokens, maxHeadChars = DEFAULT_CCR_CONFIG.maxHeadChars) {
1527
+ const covered = /* @__PURE__ */ new Set();
1528
+ for (const block of state.blocks) {
1529
+ if (!block.active || !blockIds.includes(block.blockId)) continue;
1530
+ for (const id of block.effectiveMessageIds) covered.add(id);
1653
1531
  }
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(",")}`
1659
- );
1660
- return null;
1532
+ let current = store;
1533
+ for (const message of messages) {
1534
+ if (!covered.has(message.id)) continue;
1535
+ if (message.contentType === "reasoning") continue;
1536
+ const text = stripLeadingTag(message.text ?? "");
1537
+ if (text.length === 0) continue;
1538
+ const ref = refForRaw(state.messageRefs, message.id);
1539
+ if (!ref || ref === BLOCKED_REF) continue;
1540
+ if (isStoredPlaceholderText(text) && hasStoredRef(current, ref)) continue;
1541
+ current = storeOriginal(current, {
1542
+ ref,
1543
+ rawId: message.id,
1544
+ text,
1545
+ kind: classifyKind(message.toolName),
1546
+ toolName: message.toolName,
1547
+ tokens: countTokens(text),
1548
+ head: normalizeHead(text, maxHeadChars)
1549
+ });
1661
1550
  }
1551
+ return current;
1552
+ }
1553
+ function noteRetrieval(state) {
1662
1554
  return {
1663
- ref: ref.trim(),
1664
- summary,
1665
- ...callId ? { absorbCallId: callId } : {}
1555
+ ...state,
1556
+ stats: {
1557
+ ...state.stats,
1558
+ retrievalCount: (state.stats.retrievalCount ?? 0) + 1
1559
+ }
1666
1560
  };
1667
1561
  }
1668
- function pickString(obj, ...keys) {
1669
- for (const key of keys) {
1670
- const value = obj[key];
1671
- if (typeof value === "string") return value;
1562
+
1563
+ // src/boundaries.ts
1564
+ var MESSAGE_REF_PATTERN = /^m0*(\d{1,5})$/;
1565
+ var BLOCK_REF_PATTERN = /^b(\d{1,9})$/;
1566
+ function parseBoundary(ref) {
1567
+ const normalized = ref.trim().toLowerCase();
1568
+ const messageMatch = MESSAGE_REF_PATTERN.exec(normalized);
1569
+ if (messageMatch) {
1570
+ const numericId = Number(messageMatch[1]);
1571
+ if (numericId >= 1 && numericId <= 99999) {
1572
+ return { kind: "message", numericId, raw: normalized };
1573
+ }
1672
1574
  }
1673
- return void 0;
1575
+ const blockMatch = BLOCK_REF_PATTERN.exec(normalized);
1576
+ if (blockMatch) {
1577
+ const numericId = Number(blockMatch[1]);
1578
+ if (numericId >= 1) return { kind: "block", numericId, raw: normalized };
1579
+ }
1580
+ return null;
1674
1581
  }
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
- };
1582
+ var BoundaryNotFoundError = class extends Error {
1583
+ code = "BOUNDARY_NOT_FOUND";
1584
+ kind;
1585
+ endpoint;
1586
+ constructor(kind, endpoint, message) {
1587
+ super(message);
1588
+ this.name = "BoundaryNotFoundError";
1589
+ this.code = "BOUNDARY_NOT_FOUND";
1590
+ this.kind = kind;
1591
+ this.endpoint = endpoint;
1592
+ }
1593
+ };
1594
+ function resolveBoundaries(input) {
1595
+ const start = parseBoundary(input.startRef);
1596
+ const end = parseBoundary(input.endRef);
1597
+ if (!start || !end) {
1598
+ throw new Error(
1599
+ `Invalid boundary ref(s): startId="${input.startRef}", endId="${input.endRef}". Use mNNNNN or bN.`
1600
+ );
1601
+ }
1602
+ const indexByMessageId = /* @__PURE__ */ new Map();
1603
+ input.messages.forEach(
1604
+ (message, index) => indexByMessageId.set(message.id, index)
1605
+ );
1606
+ let snappedBoundaries = [];
1607
+ const startAnchor = resolveAnchorIndex(
1608
+ start,
1609
+ input.state,
1610
+ indexByMessageId,
1611
+ "start"
1612
+ );
1613
+ if (startAnchor.snapped) snappedBoundaries.push(startAnchor.snapped);
1614
+ const endAnchor = resolveAnchorIndex(
1615
+ end,
1616
+ input.state,
1617
+ indexByMessageId,
1618
+ "end"
1619
+ );
1620
+ if (endAnchor.snapped) snappedBoundaries.push(endAnchor.snapped);
1621
+ let startIndex = startAnchor.index;
1622
+ let endIndex = endAnchor.index;
1623
+ if (startIndex > endIndex) {
1624
+ [startIndex, endIndex] = [endIndex, startIndex];
1625
+ }
1626
+ const messageIds = [];
1627
+ for (let index = startIndex; index <= endIndex; index++) {
1628
+ const message = input.messages[index];
1629
+ if (message && !isRenderedSummaryMessage(message) && !isRetrievedMessage(message))
1630
+ messageIds.push(message.id);
1631
+ }
1632
+ const boundaryKind = start.kind === "block" || end.kind === "block" ? "block" : "message";
1633
+ const nestedBlockIds = [];
1634
+ const nestedSeen = /* @__PURE__ */ new Set();
1635
+ for (const block of activeBlocks(input.state)) {
1636
+ if (blockVisibleInRange(block, indexByMessageId, startIndex, endIndex)) {
1637
+ if (!nestedSeen.has(block.blockId)) {
1638
+ nestedSeen.add(block.blockId);
1639
+ nestedBlockIds.push(block.blockId);
1640
+ }
1641
+ }
1642
+ }
1643
+ const protectedGaps = [];
1644
+ return {
1645
+ startIndex,
1646
+ endIndex,
1647
+ messageIds,
1648
+ nestedBlockIds,
1649
+ boundaryKind,
1650
+ protectedGaps,
1651
+ snappedBoundaries
1652
+ };
1653
+ }
1654
+ function resolveAnchorIndex(boundary, state, indexByMessageId, endpoint) {
1655
+ const label = endpoint === "start" ? "startId" : "endId";
1656
+ if (boundary.kind === "message") {
1657
+ const rawId = state.messageRefs.byRef[boundary.raw] ?? state.messageRefs.byRef[formatPaddedRef(boundary.numericId)];
1658
+ if (!rawId) {
1659
+ throw new BoundaryNotFoundError(
1660
+ "unknown",
1661
+ endpoint,
1662
+ `${label}="${boundary.raw}" does not exist in this session (typo or wrong session) \u2014 run acp_status for current refs.`
1663
+ );
1664
+ }
1665
+ const index = indexByMessageId.get(rawId);
1666
+ if (index !== void 0) {
1667
+ return { index, snapped: null };
1668
+ }
1669
+ const owner2 = activeOwnerAnchor(state, [rawId], indexByMessageId);
1670
+ if (owner2 !== null) {
1671
+ return {
1672
+ index: owner2,
1673
+ snapped: `${label}="${boundary.raw}" refers to a message already compressed into an active block \u2014 anchored to the active block covering it instead.`
1674
+ };
1675
+ }
1676
+ throw new BoundaryNotFoundError(
1677
+ "consumed",
1678
+ endpoint,
1679
+ `${label}="${boundary.raw}" not found in visible context (likely consumed by an existing block).`
1680
+ );
1681
+ }
1682
+ const block = blockById(state, `b${boundary.numericId}`);
1683
+ if (!block) {
1684
+ throw new BoundaryNotFoundError(
1685
+ "unknown",
1686
+ endpoint,
1687
+ `${label}="b${boundary.numericId}" does not exist in this session (typo or wrong session) \u2014 run acp_status for current refs.`
1688
+ );
1689
+ }
1690
+ if (block.active) {
1691
+ const anchor = visibleBlockAnchor(block, indexByMessageId);
1692
+ if (anchor !== null) {
1693
+ return { index: anchor, snapped: null };
1694
+ }
1695
+ }
1696
+ const owner = activeOwnerAnchor(
1697
+ state,
1698
+ block.effectiveMessageIds,
1699
+ indexByMessageId
1700
+ );
1701
+ if (owner !== null) {
1702
+ return {
1703
+ index: owner,
1704
+ snapped: `${label}="b${boundary.numericId}" was consumed by a higher-tier block \u2014 anchored to the active block covering its content instead.`
1705
+ };
1706
+ }
1707
+ if (!block.active) {
1708
+ throw new BoundaryNotFoundError(
1709
+ "consumed",
1710
+ endpoint,
1711
+ `${label}="b${boundary.numericId}" not found in visible context (block distilled/consumed by a higher-tier block).`
1712
+ );
1713
+ }
1714
+ throw new BoundaryNotFoundError(
1715
+ "consumed",
1716
+ endpoint,
1717
+ `${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.`
1718
+ );
1719
+ }
1720
+ function activeOwnerAnchor(state, ownedIds, indexByMessageId) {
1721
+ if (ownedIds.length === 0) return null;
1722
+ const owned = new Set(ownedIds);
1723
+ let best = null;
1724
+ for (const block of state.blocks) {
1725
+ if (!block.active) continue;
1726
+ const inherited = inheritedContentIds(state, block);
1727
+ let ownsInherited = false;
1728
+ for (const id of owned) {
1729
+ if (inherited.has(id)) {
1730
+ ownsInherited = true;
1731
+ break;
1732
+ }
1733
+ }
1734
+ if (!ownsInherited) continue;
1735
+ const anchor = visibleBlockAnchor(block, indexByMessageId);
1736
+ if (anchor === null) continue;
1737
+ if (best === null || anchor < best) {
1738
+ best = anchor;
1739
+ }
1740
+ }
1741
+ return best;
1742
+ }
1743
+ function inheritedContentIds(state, block) {
1744
+ const ids = /* @__PURE__ */ new Set();
1745
+ for (const childId of block.directBlockIds) {
1746
+ const child = blockById(state, childId);
1747
+ if (!child) continue;
1748
+ for (const id of child.effectiveMessageIds) ids.add(id);
1749
+ }
1750
+ return ids;
1751
+ }
1752
+ function formatPaddedRef(index) {
1753
+ return `m${String(index).padStart(5, "0")}`;
1754
+ }
1755
+ function visibleBlockAnchor(block, indexByMessageId) {
1756
+ const summaryIndex = indexByMessageId.get(summaryMessageId(block.blockId));
1757
+ if (summaryIndex !== void 0) return summaryIndex;
1758
+ return earliestIndexOfIds(block.effectiveMessageIds, indexByMessageId);
1759
+ }
1760
+ function blockVisibleInRange(block, indexByMessageId, startIndex, endIndex) {
1761
+ const summaryIndex = indexByMessageId.get(summaryMessageId(block.blockId));
1762
+ if (summaryIndex !== void 0 && summaryIndex >= startIndex && summaryIndex <= endIndex) {
1763
+ return true;
1764
+ }
1765
+ const rawIndex = earliestIndexOfIds(
1766
+ block.effectiveMessageIds,
1767
+ indexByMessageId
1768
+ );
1769
+ return rawIndex !== null && rawIndex >= startIndex && rawIndex <= endIndex;
1770
+ }
1771
+ function earliestIndexOfIds(ids, indexByMessageId) {
1772
+ let earliest = null;
1773
+ for (const id of ids) {
1774
+ const index = indexByMessageId.get(id);
1775
+ if (index !== void 0 && (earliest === null || index < earliest)) {
1776
+ earliest = index;
1777
+ }
1778
+ }
1779
+ return earliest;
1780
+ }
1781
+
1782
+ // src/truncate-tools.ts
1783
+ var TRUNCATION_MARKER = "[truncated for context space]";
1784
+ var DEFAULTS = {
1785
+ minOutputTokens: 1e3,
1786
+ keepPrefixChars: 2e3,
1787
+ keepSuffixChars: 2e3,
1788
+ protectRecentMessages: 3
1789
+ };
1790
+ function truncateLargeToolOutputs(messages, tokenCount, config, countTokens, options = {}) {
1791
+ const opts = { ...DEFAULTS, ...options };
1792
+ const limit = config.modelContextLimit;
1793
+ if (limit <= 0 || tokenCount < config.truncate.threshold * limit) {
1794
+ return { messages, truncatedCount: 0, savedTokens: 0, candidatesFound: 0 };
1795
+ }
1796
+ const protectedIndex = messages.length - opts.protectRecentMessages;
1797
+ const findCandidates = (predicate) => {
1798
+ const found = [];
1799
+ for (let i = 0; i < protectedIndex; i++) {
1800
+ const message = messages[i];
1801
+ if (!predicate(message)) continue;
1802
+ const text = message.text ?? "";
1803
+ if (text.length === 0 || text.includes(TRUNCATION_MARKER)) continue;
1804
+ if (countTokens(text) < opts.minOutputTokens) continue;
1805
+ found.push(message);
1806
+ }
1807
+ return found.sort(
1808
+ (a, b) => countTokens(b.text ?? "") - countTokens(a.text ?? "")
1809
+ );
1810
+ };
1811
+ const toolResults = findCandidates((m) => m.contentType === "tool-result");
1812
+ const textMessages = options.includeTextMessages ? findCandidates(
1813
+ (m) => m.contentType === "text" && (m.role === "user" || m.role === "assistant") && !isRenderedSummaryMessage(m) && !m.text?.startsWith(SUMMARY_HEADER)
1814
+ ) : [];
1815
+ const candidatesFound = toolResults.length + textMessages.length;
1816
+ let truncatedCount = 0;
1817
+ let savedTokens = 0;
1818
+ let remaining = tokenCount;
1819
+ const targetTokens = config.truncate.threshold * limit * 0.9;
1820
+ const replacements = /* @__PURE__ */ new Map();
1821
+ const applyTo = (candidates) => {
1822
+ for (const candidate of candidates) {
1823
+ if (remaining <= targetTokens) break;
1824
+ const original = candidate.text ?? "";
1825
+ const tokens = countTokens(original);
1826
+ if (original.length <= opts.keepPrefixChars + opts.keepSuffixChars) {
1827
+ continue;
1828
+ }
1829
+ const prefix = clampPrefix(original, opts.keepPrefixChars);
1830
+ const suffix = clampWindow(original, original.length - opts.keepSuffixChars, original.length);
1831
+ const replacement = `${prefix}
1832
+
1833
+ ...${TRUNCATION_MARKER} \u2014 original ~${tokens} tokens]...
1834
+
1835
+ ${suffix}`;
1836
+ replacements.set(candidate.id, replacement);
1837
+ truncatedCount++;
1838
+ remaining -= tokens - countTokens(replacement);
1839
+ savedTokens += tokens - countTokens(replacement);
1840
+ }
1841
+ };
1842
+ applyTo(toolResults);
1843
+ applyTo(textMessages);
1844
+ if (replacements.size === 0) {
1845
+ return { messages, truncatedCount: 0, savedTokens: 0, candidatesFound };
1846
+ }
1847
+ return {
1848
+ messages: messages.map(
1849
+ (m) => replacements.has(m.id) ? { ...m, text: replacements.get(m.id) } : m
1850
+ ),
1851
+ truncatedCount,
1852
+ savedTokens,
1853
+ candidatesFound
1854
+ };
1855
+ }
1856
+
1857
+ // src/hide-consumed.ts
1858
+ var KEEP_LAST_ORPHANED = 2;
1859
+ function rangeKey(startRef, endRef) {
1860
+ return `${startRef}::${endRef}`;
1861
+ }
1862
+ function parseCallText(text) {
1863
+ const raw = text ?? "";
1864
+ const start = raw.indexOf("{");
1865
+ if (start < 0) return null;
1866
+ let parsed;
1867
+ try {
1868
+ parsed = JSON.parse(raw.slice(start));
1869
+ } catch {
1870
+ return null;
1871
+ }
1872
+ if (!parsed || typeof parsed !== "object") return null;
1873
+ const obj = parsed;
1874
+ let content = null;
1875
+ let contentWasString = false;
1876
+ if (Array.isArray(obj.content)) {
1877
+ content = obj.content;
1878
+ } else if (typeof obj.content === "string") {
1879
+ contentWasString = true;
1880
+ try {
1881
+ const inner = JSON.parse(obj.content);
1882
+ if (Array.isArray(inner)) content = inner;
1883
+ } catch {
1884
+ content = null;
1885
+ }
1886
+ }
1887
+ if (!content || content.length === 0) return null;
1888
+ return { prefix: raw.slice(0, start), obj, content, contentWasString };
1889
+ }
1890
+ function rewriteCompressText(text, liveKeys) {
1891
+ const parsed = parseCallText(text);
1892
+ if (!parsed) return null;
1893
+ const { prefix, obj, content, contentWasString } = parsed;
1894
+ const kept = content.filter((entry) => {
1895
+ if (!entry || typeof entry !== "object") return false;
1896
+ const e = entry;
1897
+ const s = typeof e.startId === "string" ? e.startId : typeof e.messageId === "string" ? e.messageId : "";
1898
+ const end = typeof e.endId === "string" ? e.endId : typeof e.messageId === "string" ? e.messageId : "";
1899
+ return liveKeys.has(rangeKey(s, end));
1900
+ });
1901
+ if (kept.length === 0) return null;
1902
+ return prefix + serializeCompacted(obj, kept, contentWasString).text;
1903
+ }
1904
+ var SUMMARY_STUB_CHARS = 200;
1905
+ function compactEntry(entry) {
1906
+ if (!entry || typeof entry !== "object") return entry;
1907
+ const e = entry;
1908
+ if (typeof e.summary !== "string" || e.summary.length <= SUMMARY_STUB_CHARS) return entry;
1909
+ return { ...e, summary: `${clampPrefix(e.summary, SUMMARY_STUB_CHARS - 1)}\u2026` };
1910
+ }
1911
+ function serializeCompacted(obj, content, contentWasString) {
1912
+ let changed = false;
1913
+ const compacted = content.map((entry) => {
1914
+ const out = compactEntry(entry);
1915
+ if (out !== entry) changed = true;
1916
+ return out;
1917
+ });
1918
+ const outContent = contentWasString ? JSON.stringify(compacted) : compacted;
1919
+ return { text: JSON.stringify({ ...obj, content: outContent }), changed };
1920
+ }
1921
+ function compactCompressText(text) {
1922
+ const parsed = parseCallText(text);
1923
+ if (!parsed) return null;
1924
+ const { prefix, obj, content, contentWasString } = parsed;
1925
+ const { text: out, changed } = serializeCompacted(obj, content, contentWasString);
1926
+ return changed ? prefix + out : null;
1927
+ }
1928
+ function hideConsumedCompressCalls(state, messages) {
1929
+ const allBlockCallIds = /* @__PURE__ */ new Set();
1930
+ const activeCallIds = /* @__PURE__ */ new Set();
1931
+ const liveRangeKeysByCallId = /* @__PURE__ */ new Map();
1932
+ const legacyLiveByCallId = /* @__PURE__ */ new Set();
1933
+ for (const block of state.blocks) {
1934
+ if (!block.compressCallId) continue;
1935
+ allBlockCallIds.add(block.compressCallId);
1936
+ if (!block.active) continue;
1937
+ activeCallIds.add(block.compressCallId);
1938
+ if (block.startRef === void 0 || block.endRef === void 0) {
1939
+ legacyLiveByCallId.add(block.compressCallId);
1940
+ continue;
1941
+ }
1942
+ let keys = liveRangeKeysByCallId.get(block.compressCallId);
1943
+ if (!keys) {
1944
+ keys = /* @__PURE__ */ new Set();
1945
+ liveRangeKeysByCallId.set(block.compressCallId, keys);
1946
+ }
1947
+ keys.add(rangeKey(block.startRef, block.endRef));
1948
+ }
1949
+ const lastOrphanedCallIds = [];
1950
+ for (let i = messages.length - 1; i >= 0 && lastOrphanedCallIds.length < KEEP_LAST_ORPHANED; i--) {
1951
+ const message = messages[i];
1952
+ if (message.toolName !== "compress" || message.contentType !== "tool-call") continue;
1953
+ const callId = message.toolCallId;
1954
+ if (callId && !allBlockCallIds.has(callId)) {
1955
+ lastOrphanedCallIds.push(callId);
1956
+ }
1957
+ }
1958
+ const keepCallIds = /* @__PURE__ */ new Set([...activeCallIds, ...lastOrphanedCallIds]);
1959
+ const hiddenCallIds = /* @__PURE__ */ new Set();
1960
+ for (const message of messages) {
1961
+ if (message.toolName === "compress" && message.contentType === "tool-call" && (!message.toolCallId || !keepCallIds.has(message.toolCallId))) {
1962
+ if (message.toolCallId) hiddenCallIds.add(message.toolCallId);
1963
+ }
1964
+ }
1965
+ let hidden = 0;
1966
+ const result = [];
1967
+ for (const message of messages) {
1968
+ if (message.toolName === "compress" && message.contentType === "tool-call" && (!message.toolCallId || !keepCallIds.has(message.toolCallId))) {
1969
+ hidden++;
1970
+ continue;
1971
+ }
1972
+ if (message.contentType === "tool-result" && message.toolCallId && hiddenCallIds.has(message.toolCallId)) {
1973
+ hidden++;
1974
+ continue;
1975
+ }
1976
+ if (message.toolName === "compress" && message.contentType === "tool-call" && message.toolCallId && keepCallIds.has(message.toolCallId)) {
1977
+ const liveKeys = liveRangeKeysByCallId.get(message.toolCallId);
1978
+ if (liveKeys && liveKeys.size > 0 && !legacyLiveByCallId.has(message.toolCallId)) {
1979
+ const rewritten = rewriteCompressText(message.text, liveKeys);
1980
+ if (rewritten !== null) {
1981
+ result.push({ ...message, text: rewritten });
1982
+ continue;
1983
+ }
1984
+ }
1985
+ const compacted = compactCompressText(message.text);
1986
+ if (compacted !== null) {
1987
+ result.push({ ...message, text: compacted });
1988
+ continue;
1989
+ }
1990
+ }
1991
+ result.push(message);
1992
+ }
1993
+ return { messages: result, hidden };
1994
+ }
1995
+
1996
+ // src/absorb.ts
1997
+ var ABSORB_PROMPT_MARKER = "[ACP absorb]";
1998
+ var DEFAULT_ABSORB_CONFIG = {
1999
+ enabled: false,
2000
+ toolName: ABSORB_TOOL_NAME,
2001
+ // Raised 1000 → 4000 (issue #352): lossless CCR takes over large-result
2002
+ // handling; absorb's forced distillation only fires above the new bar.
2003
+ minToolTokens: 4e3,
2004
+ contextThresholdPct: 0,
2005
+ excludeTools: []
2006
+ };
2007
+ function resolveAbsorbConfig(config) {
2008
+ return { ...DEFAULT_ABSORB_CONFIG, ...config.absorb ?? {} };
2009
+ }
2010
+ function formatTokenCount(tokens) {
2011
+ if (tokens < 1e3) return String(tokens);
2012
+ if (tokens < 1e4) return (tokens / 1e3).toFixed(1) + "K";
2013
+ return Math.round(tokens / 1e3) + "K";
2014
+ }
2015
+ function buildAbsorbPrompt(ref, tokens, toolName = ABSORB_TOOL_NAME) {
2016
+ 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)".`;
2017
+ }
2018
+ function buildAbsorbSystemPrompt(toolName = ABSORB_TOOL_NAME) {
2019
+ return `INSTANT TOOL-RESULT ABSORPTION (${toolName})
2020
+
2021
+ 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.`;
2022
+ }
2023
+ function isAcpOrConfiguredTool(toolName, cfg) {
2024
+ if (!toolName) return false;
2025
+ if (toolName === cfg.toolName) return true;
2026
+ return ACP_TOOL_NAMES.has(toolName);
2027
+ }
2028
+ function isAbsorbCandidate(msg, config, latest) {
2029
+ if (msg.contentType !== "tool-result" || !msg.toolCallId) return false;
2030
+ if (isStoredPlaceholderText(msg.text ?? "")) return false;
2031
+ const cfg = resolveAbsorbConfig(config);
2032
+ if (isAcpOrConfiguredTool(msg.toolName, cfg)) return false;
2033
+ if (isMessageProtected(msg, config)) return false;
2034
+ if (latest && isMessageLatestProtected(msg, latest)) return false;
2035
+ for (const pattern of cfg.excludeTools) {
2036
+ if (msg.toolName && matchToolPattern(msg.toolName, pattern)) return false;
2037
+ }
2038
+ return true;
2039
+ }
2040
+ function hideAbsorbedMessages(messages, state) {
2041
+ const records = state.absorbed ?? [];
2042
+ if (records.length === 0) return messages;
2043
+ const hidden = /* @__PURE__ */ new Set();
2044
+ for (const record of records) {
2045
+ if (record.callMessageId) hidden.add(record.callMessageId);
2046
+ if (record.resultMessageId) hidden.add(record.resultMessageId);
2047
+ }
2048
+ return messages.filter((msg) => !hidden.has(msg.id));
2049
+ }
2050
+ function appendAbsorbPrompts(messages, state, config, tokenCount, countTokens) {
2051
+ const cfg = resolveAbsorbConfig(config);
2052
+ if (!cfg.enabled) return { messages, promptedCount: 0 };
2053
+ const limit = config.modelContextLimit;
2054
+ if (cfg.contextThresholdPct > 0 && limit > 0 && tokenCount < cfg.contextThresholdPct * limit) {
2055
+ return { messages, promptedCount: 0 };
2056
+ }
2057
+ const absorbedIds = /* @__PURE__ */ new Set();
2058
+ for (const record of state.absorbed ?? []) {
2059
+ if (record.resultMessageId) absorbedIds.add(record.resultMessageId);
2060
+ }
2061
+ let promptedCount = 0;
2062
+ const latest = collectLatestProtected(messages, config);
2063
+ const out = messages.map((msg) => {
2064
+ if (!isAbsorbCandidate(msg, config, latest)) return msg;
2065
+ if (absorbedIds.has(msg.id)) return msg;
2066
+ const text = msg.text ?? "";
2067
+ if (text.includes(ABSORB_PROMPT_MARKER)) return msg;
2068
+ const tokens = countTokens(text);
2069
+ if (tokens < cfg.minToolTokens) return msg;
2070
+ const ref = refForRaw(state.messageRefs, msg.id);
2071
+ if (!ref || ref === BLOCKED_REF) return msg;
2072
+ promptedCount++;
2073
+ return {
2074
+ ...msg,
2075
+ text: text + "\n\n" + buildAbsorbPrompt(ref, tokens, cfg.toolName)
2076
+ };
2077
+ });
2078
+ return { messages: out, promptedCount };
2079
+ }
2080
+ function parseAbsorbInput(input, callId, onWarn) {
2081
+ let obj = null;
2082
+ if (typeof input === "string") {
2083
+ try {
2084
+ const parsed = JSON.parse(input);
2085
+ if (parsed && typeof parsed === "object") {
2086
+ obj = parsed;
2087
+ }
2088
+ } catch {
2089
+ obj = null;
2090
+ }
2091
+ } else if (input && typeof input === "object") {
2092
+ obj = input;
2093
+ }
2094
+ if (!obj) {
2095
+ onWarn?.(`[acp-absorb-input] rejected: not an object (${typeof input})`);
2096
+ return null;
2097
+ }
2098
+ const ref = pickString(obj, "ref", "messageId", "of");
2099
+ const summary = pickString(obj, "summary", "content");
2100
+ if (typeof ref !== "string" || typeof summary !== "string") {
2101
+ onWarn?.(
2102
+ `[acp-absorb-input] rejected: need ref (string) + summary (string); keys: ${Object.keys(obj).join(",")}`
2103
+ );
2104
+ return null;
2105
+ }
2106
+ return {
2107
+ ref: ref.trim(),
2108
+ summary,
2109
+ ...callId ? { absorbCallId: callId } : {}
2110
+ };
2111
+ }
2112
+ function pickString(obj, ...keys) {
2113
+ for (const key of keys) {
2114
+ const value = obj[key];
2115
+ if (typeof value === "string") return value;
2116
+ }
2117
+ return void 0;
2118
+ }
2119
+ function applyAbsorb(input) {
2120
+ const countTokens = input.countTokens ?? ((text) => Math.ceil(text.length / 4));
2121
+ const summary = input.summary?.trim() ?? "";
2122
+ if (!summary) {
2123
+ return {
2124
+ state: input.state,
2125
+ ok: false,
2126
+ resultText: "absorb failed: summary is empty \u2014 provide the distilled key info of the tool result."
2127
+ };
2128
+ }
2129
+ const cfg = resolveAbsorbConfig(input.config);
2130
+ const rawId = rawForRef(input.state.messageRefs, input.ref.trim());
2131
+ if (!rawId) {
2132
+ return {
2133
+ state: input.state,
2134
+ ok: false,
2135
+ resultText: `absorb failed: ref ${input.ref} does not exist in this session (it may be hidden, already compressed, or stale).`
2136
+ };
2137
+ }
2138
+ const existing = (input.state.absorbed ?? []).find(
2139
+ (record2) => record2.resultMessageId === rawId
2140
+ );
2141
+ if (existing) {
2142
+ return {
2143
+ state: input.state,
2144
+ ok: true,
2145
+ resultText: `already absorbed (${input.ref}) \u2014 no change.`
2146
+ };
2147
+ }
2148
+ const target = input.messages.find((m) => m.id === rawId);
2149
+ if (!target) {
2150
+ return {
2151
+ state: input.state,
2152
+ ok: false,
2153
+ resultText: `absorb failed: ref ${input.ref} is not visible in this session (hidden or compressed).`
2154
+ };
2155
+ }
2156
+ if (target.contentType !== "tool-result") {
2157
+ return {
2158
+ state: input.state,
2159
+ ok: false,
2160
+ resultText: `absorb failed: ref ${input.ref} is a ${target.contentType}, not a tool result.`
2161
+ };
2162
+ }
2163
+ if (isAcpOrConfiguredTool(target.toolName, cfg)) {
2164
+ return {
2165
+ state: input.state,
2166
+ ok: false,
2167
+ resultText: `absorb failed: ${target.toolName} is an ACP-managed tool result \u2014 it is not absorbable.`
2168
+ };
2169
+ }
2170
+ if (isMessageProtected(target, input.config) || isMessageLatestProtected(
2171
+ target,
2172
+ collectLatestProtected(input.messages, input.config)
2173
+ )) {
2174
+ return {
2175
+ state: input.state,
2176
+ ok: false,
2177
+ resultText: `absorb failed: ${target.toolName} is a protected tool \u2014 its results must stay visible.`
2178
+ };
2179
+ }
2180
+ if (!target.toolCallId) {
2181
+ return {
2182
+ state: input.state,
2183
+ ok: false,
2184
+ resultText: `absorb failed: ref ${input.ref} has no tool-call id \u2014 cannot pair it for hiding.`
2185
+ };
2186
+ }
2187
+ const call = input.messages.find(
2188
+ (m) => m.contentType === "tool-call" && m.toolCallId === target.toolCallId
2189
+ );
2190
+ const tokens = countTokens(target.text ?? "");
2191
+ const summaryTokens = countTokens(summary);
2192
+ const record = {
2193
+ toolCallId: target.toolCallId,
2194
+ callMessageId: call?.id ?? "",
2195
+ resultMessageId: target.id,
2196
+ ...input.absorbCallId ? { absorbCallId: input.absorbCallId } : {},
2197
+ summary,
2198
+ tokensReclaimed: tokens,
2199
+ createdAt: Date.now()
2200
+ };
2201
+ const state = {
2202
+ ...input.state,
2203
+ absorbed: [...input.state.absorbed ?? [], record],
2204
+ stats: {
2205
+ ...input.state.stats,
2206
+ absorbedTokens: (input.state.stats.absorbedTokens ?? 0) + tokens
2207
+ }
2208
+ };
2209
+ 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.` : "";
2210
+ return {
2211
+ state,
2212
+ ok: true,
2213
+ 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}`
2214
+ };
2215
+ }
2216
+
2217
+ // src/crush.ts
2218
+ var DEFAULT_CRUSH_CONFIG = {
2219
+ enabled: false,
2220
+ minReduction: 0.1
2221
+ };
2222
+ var MAX_INPUT_CHARS = 2e6;
2223
+ var MAX_DEPTH = 12;
2224
+ var MAX_FOLDS = 1e3;
2225
+ var RUN_MIN = 4;
2226
+ var HOIST_MIN = 4;
2227
+ var CODE_KEEP_RATIO = 0.97;
2228
+ var CRUSH_KEY = "__acp_crush";
2229
+ function isPlainObject(v) {
2230
+ if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
2231
+ const p = Object.getPrototypeOf(v);
2232
+ return p === null || p === Object.prototype;
2233
+ }
2234
+ function classifyCrushText(text) {
2235
+ const trimmed = text.trim();
2236
+ if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]"))
2237
+ return "json";
2238
+ if (detectLanguage(text) !== null) return "code";
2239
+ return "log";
2240
+ }
2241
+ function crushText(text, options = {}) {
2242
+ const countTokens = options.countTokens ?? defaultCountTokens;
2243
+ const minReduction = options.minReduction ?? DEFAULT_CRUSH_CONFIG.minReduction;
2244
+ const meta = options.meta ?? {};
2245
+ if (text.length === 0 || text.length > MAX_INPUT_CHARS) return null;
2246
+ const rawTok = countTokens(text);
2247
+ if (rawTok <= 0) return null;
2248
+ const kind = classifyCrushText(text);
2249
+ const wanted = options.plugins ?? registeredPlugins();
2250
+ for (const plugin of wanted) {
2251
+ let res;
2252
+ try {
2253
+ if (!Array.isArray(plugin.kinds) || !plugin.kinds.includes(kind))
2254
+ continue;
2255
+ res = plugin.run(text, meta);
2256
+ } catch {
2257
+ continue;
2258
+ }
2259
+ if (!res || typeof res.text !== "string" || res.text === "" || res.text === text)
2260
+ continue;
2261
+ const newTok = countTokens(res.text);
2262
+ const reduction = (rawTok - newTok) / rawTok;
2263
+ if (reduction < minReduction) continue;
2264
+ if (kind === "log" && res.lossy && !errorLinesSurvive(text, res.text))
2265
+ continue;
2266
+ const out = {
2267
+ text: res.text,
2268
+ strategy: plugin.id,
2269
+ lossy: res.lossy
2270
+ };
2271
+ if (res.stats) out.stats = res.stats;
2272
+ return out;
2273
+ }
2274
+ return null;
2275
+ }
2276
+ function canonOf(v, cache2) {
2277
+ if (!isPlainObject(v) && !Array.isArray(v))
2278
+ return JSON.stringify(v) ?? "null";
2279
+ const key = v;
2280
+ const hit = cache2.get(key);
2281
+ if (hit !== void 0) return hit;
2282
+ let s;
2283
+ if (Array.isArray(v)) {
2284
+ s = `[${v.map((e) => canonOf(e, cache2)).join(",")}]`;
2285
+ } else {
2286
+ const entries = Object.entries(v).map(([k, val]) => `${JSON.stringify(k)}:${canonOf(val, cache2)}`).sort();
2287
+ s = `{${entries.join(",")}}`;
2288
+ }
2289
+ cache2.set(key, s);
2290
+ return s;
2291
+ }
2292
+ function strLen(v) {
2293
+ return JSON.stringify(v).length;
2294
+ }
2295
+ function compactValue(v, depth, stats, cache2) {
2296
+ if (depth > MAX_DEPTH || stats.folds >= MAX_FOLDS) return v;
2297
+ if (Array.isArray(v)) return compactArray(v, depth, stats, cache2);
2298
+ if (isPlainObject(v)) {
2299
+ let changed = false;
2300
+ const out = {};
2301
+ for (const [k, val] of Object.entries(v)) {
2302
+ const nv = compactValue(val, depth + 1, stats, cache2);
2303
+ out[k] = nv;
2304
+ if (nv !== val) changed = true;
2305
+ }
2306
+ return changed ? out : v;
2307
+ }
2308
+ return v;
2309
+ }
2310
+ function compactArray(arr, depth, stats, cache2) {
2311
+ const n = arr.length;
2312
+ if (n < RUN_MIN && n < HOIST_MIN)
2313
+ return arr.map((e) => compactValue(e, depth + 1, stats, cache2));
2314
+ if (stats.folds >= MAX_FOLDS) return arr;
2315
+ const plainStats = { folds: 0 };
2316
+ const plain = arr.map((e) => compactValue(e, depth + 1, plainStats, cache2));
2317
+ let winner = plain;
2318
+ let winnerLen = strLen(plain);
2319
+ let winnerFolds = plainStats.folds;
2320
+ if (n >= RUN_MIN) {
2321
+ const rf = buildRunFold(arr, depth, cache2);
2322
+ if (rf && rf.len < winnerLen) {
2323
+ winner = rf.value;
2324
+ winnerLen = rf.len;
2325
+ winnerFolds = rf.folds;
2326
+ }
2327
+ }
2328
+ if (n >= HOIST_MIN && arr.every((e) => isPlainObject(e))) {
2329
+ const hc = buildConstHoist(arr, depth, cache2);
2330
+ if (hc && hc.len < winnerLen) {
2331
+ winner = hc.value;
2332
+ winnerLen = hc.len;
2333
+ winnerFolds = hc.folds;
2334
+ }
2335
+ }
2336
+ stats.folds += winnerFolds;
2337
+ return winner;
2338
+ }
2339
+ function isRunMarker(v) {
2340
+ return isPlainObject(v) && v[CRUSH_KEY] === "identical-run";
2341
+ }
2342
+ function buildRunFold(arr, depth, cache2) {
2343
+ const n = arr.length;
2344
+ const canon = arr.map((e) => canonOf(e, cache2));
2345
+ const segs = [];
2346
+ let runs = 0;
2347
+ let i = 0;
2348
+ while (i < n) {
2349
+ let j = i;
2350
+ while (j + 1 < n && canon[j + 1] === canon[i]) j++;
2351
+ const runLen = j - i + 1;
2352
+ if (runLen >= RUN_MIN) {
2353
+ segs.push({ [CRUSH_KEY]: "identical-run", count: runLen, item: arr[i] });
2354
+ runs++;
2355
+ } else {
2356
+ for (let k = i; k <= j; k++) segs.push(arr[k]);
2357
+ }
2358
+ i = j + 1;
2359
+ }
2360
+ if (runs === 0) return null;
2361
+ const inner = { folds: 0 };
2362
+ const final = segs.map(
2363
+ (s) => isRunMarker(s) ? { ...s, item: compactValue(s.item, depth + 1, inner, cache2) } : compactValue(s, depth + 1, inner, cache2)
2364
+ );
2365
+ return { value: final, len: strLen(final), folds: runs + inner.folds };
2366
+ }
2367
+ function buildConstHoist(arr, depth, cache2) {
2368
+ const n = arr.length;
2369
+ const keys = [];
2370
+ const seen = /* @__PURE__ */ new Set();
2371
+ for (const e of arr) {
2372
+ for (const k of Object.keys(e)) {
2373
+ if (!seen.has(k)) {
2374
+ seen.add(k);
2375
+ keys.push(k);
2376
+ }
2377
+ }
2378
+ }
2379
+ const first = arr[0];
2380
+ if (!first) return null;
2381
+ const constKeys = [];
2382
+ for (const k of keys) {
2383
+ if (!(k in first)) continue;
2384
+ const c0 = canonOf(first[k], cache2);
2385
+ let constant = true;
2386
+ for (let i = 1; i < n; i++) {
2387
+ const row = arr[i];
2388
+ if (!(k in row) || canonOf(row[k], cache2) !== c0) {
2389
+ constant = false;
2390
+ break;
2391
+ }
2392
+ }
2393
+ if (constant) constKeys.push(k);
2394
+ }
2395
+ if (constKeys.length === 0) return null;
2396
+ const constSet = new Set(constKeys);
2397
+ const constObj = {};
2398
+ for (const k of constKeys) constObj[k] = first[k];
2399
+ const items = arr.map((e) => {
2400
+ const o = {};
2401
+ for (const [k, v] of Object.entries(e)) {
2402
+ if (!constSet.has(k)) o[k] = v;
2403
+ }
2404
+ return o;
2405
+ });
2406
+ const hasVarying = items.some((o) => Object.keys(o).length > 0);
2407
+ const inner = { folds: 0 };
2408
+ const env = {
2409
+ [CRUSH_KEY]: "rows",
2410
+ rows: n,
2411
+ const: compactValue(constObj, depth + 1, inner, cache2)
2412
+ };
2413
+ if (hasVarying)
2414
+ env.items = items.map((it) => compactValue(it, depth + 1, inner, cache2));
2415
+ const len = strLen(env);
2416
+ if (len >= strLen(arr)) return null;
2417
+ return { value: env, len, folds: 1 + inner.folds };
2418
+ }
2419
+ function crushJson(text) {
2420
+ const parsed = JSON.parse(text);
2421
+ const cache2 = /* @__PURE__ */ new Map();
2422
+ const stats = { folds: 0 };
2423
+ const out = compactValue(parsed, 0, stats, cache2);
2424
+ if (stats.folds === 0) return null;
2425
+ const s = JSON.stringify(out);
2426
+ return s.length < text.length ? s : null;
2427
+ }
2428
+ function detectLanguage(text) {
2429
+ const sample = text.split(/\r?\n/).slice(0, 300);
2430
+ let py = 0;
2431
+ let js = 0;
2432
+ for (const line of sample) {
2433
+ if (/^\s*#!.*python/.test(line)) py += 4;
2434
+ if (/^\s*(async\s+)?def\s+\w/.test(line) || /^\s*class\s+\w/.test(line))
2435
+ py += 2;
2436
+ if (/\bfrom\s+['"]/.test(line) || /^\s*import\s+['"]/.test(line)) js += 2;
2437
+ else if (/^\s*(import|from)\s+[A-Za-z_]\w*/.test(line)) py += 1;
2438
+ if (/^\s*(function\b|const\s|let\s|var\s)/.test(line)) js += 2;
2439
+ if (/=>/.test(line)) js += 1;
2440
+ if (/^\s*(public|private|protected|static|final|void|return)\b/.test(line))
2441
+ js += 1;
2442
+ if (/;\s*$/.test(line) && line.trim().length > 0) js += 1;
2443
+ if (/\bself\./.test(line) || /^\s*(elif|except|yield)\b/.test(line))
2444
+ py += 1;
2445
+ if (/\bconsole\.log\b|\brequire\s*\(|module\.exports|\bawait\s/.test(line))
2446
+ js += 1;
2447
+ }
2448
+ const MARGIN = 1.5;
2449
+ if (py >= 3 && py > js * MARGIN) return "python";
2450
+ if (js >= 4 && js > py * MARGIN) return "js";
2451
+ return null;
2452
+ }
2453
+ function finalizeTrimmed(original, emitted) {
2454
+ if (emitted.length >= original.split(/\r?\n/).length) return null;
2455
+ const out = emitted.join("\n") + (original.endsWith("\n") ? "\n" : "");
2456
+ return out.length < original.length * CODE_KEEP_RATIO ? out : null;
2457
+ }
2458
+ function trimPython(text) {
2459
+ const lines = text.split(/\r?\n/);
2460
+ const emitted = [];
2461
+ let pending = 0;
2462
+ const flush = () => {
2463
+ if (pending > 0) {
2464
+ emitted.push(
2465
+ `# [acp-crush: elided ${pending} line${pending === 1 ? "" : "s"}]`
2466
+ );
2467
+ pending = 0;
2468
+ }
2469
+ };
2470
+ let state = "code";
2471
+ let quote = "";
2472
+ let isDocstring = false;
2473
+ let blankRun = 0;
2474
+ let atModuleStart = true;
2475
+ let pendingDefClass = false;
2476
+ let docstringSlot = false;
2477
+ const classifySig = (codePart) => {
2478
+ const t = codePart.trim();
2479
+ if (/^(async\s+)?(def|class)\b/.test(t)) pendingDefClass = true;
2480
+ if (t.endsWith(":")) {
2481
+ docstringSlot = pendingDefClass;
2482
+ pendingDefClass = false;
2483
+ } else if (t.length > 0 && !t.startsWith("@")) {
2484
+ docstringSlot = false;
2485
+ }
2486
+ atModuleStart = false;
2487
+ };
2488
+ for (let li = 0; li < lines.length; li++) {
2489
+ const line = lines[li];
2490
+ if (state === "triple") {
2491
+ const close = findTripleClose(line, quote);
2492
+ if (close === -1) {
2493
+ if (isDocstring) pending++;
2494
+ else emitted.push(line);
2495
+ continue;
2496
+ }
2497
+ state = "code";
2498
+ const rest = line.slice(close + 3).trim();
2499
+ if (rest.length > 0) {
2500
+ flush();
2501
+ emitted.push(line);
2502
+ classifySig(rest);
2503
+ } else {
2504
+ if (isDocstring) pending++;
2505
+ else emitted.push(line);
2506
+ }
2507
+ isDocstring = false;
2508
+ docstringSlot = false;
2509
+ pendingDefClass = false;
2510
+ atModuleStart = false;
2511
+ blankRun = 0;
2512
+ continue;
2513
+ }
2514
+ if (line.trim().length === 0) {
2515
+ blankRun++;
2516
+ if (blankRun >= 3) {
2517
+ flush();
2518
+ emitted.push("");
2519
+ blankRun = 1;
2520
+ }
2521
+ continue;
2522
+ }
2523
+ if (li === 0 && line.startsWith("#!")) {
2524
+ flush();
2525
+ emitted.push(line);
2526
+ blankRun = 0;
2527
+ continue;
2528
+ }
2529
+ const m = line.match(/^\s*("""|''')/);
2530
+ if (m) {
2531
+ flush();
2532
+ const q = m[1];
2533
+ const opensHere = line.indexOf(q);
2534
+ const closeOnSameLine = findTripleClose(line.slice(opensHere + 3), q);
2535
+ if (closeOnSameLine !== -1) {
2536
+ const restAfter = line.slice(opensHere + 3 + closeOnSameLine + 3).trim();
2537
+ if (restAfter.length > 0) {
2538
+ emitted.push(line);
2539
+ classifySig(restAfter);
2540
+ } else if (atModuleStart || docstringSlot) {
2541
+ pending++;
2542
+ atModuleStart = false;
2543
+ docstringSlot = false;
2544
+ pendingDefClass = false;
2545
+ } else {
2546
+ emitted.push(line);
2547
+ }
2548
+ blankRun = 0;
2549
+ continue;
2550
+ }
2551
+ state = "triple";
2552
+ quote = q;
2553
+ isDocstring = atModuleStart || docstringSlot;
2554
+ atModuleStart = false;
2555
+ docstringSlot = false;
2556
+ if (!isDocstring) emitted.push(line);
2557
+ blankRun = 0;
2558
+ continue;
2559
+ }
2560
+ const probe = findCommentOrTriple(line);
2561
+ if (probe.kind === "comment-full") {
2562
+ pending++;
2563
+ continue;
2564
+ }
2565
+ if (probe.kind === "triple-mid") {
2566
+ flush();
2567
+ state = "triple";
2568
+ quote = probe.quote;
2569
+ isDocstring = false;
2570
+ emitted.push(line);
2571
+ classifySig(line.slice(0, probe.idx));
2572
+ blankRun = 0;
2573
+ continue;
2574
+ }
2575
+ flush();
2576
+ emitted.push(line);
2577
+ classifySig(line);
2578
+ blankRun = 0;
2579
+ }
2580
+ if (state === "triple") return null;
2581
+ flush();
2582
+ return finalizeTrimmed(text, emitted);
2583
+ }
2584
+ function findTripleClose(line, quote) {
2585
+ let i = 0;
2586
+ while (i < line.length) {
2587
+ if (line[i] === "\\") {
2588
+ i += 2;
2589
+ continue;
2590
+ }
2591
+ if (line.startsWith(quote, i)) return i;
2592
+ i++;
2593
+ }
2594
+ return -1;
2595
+ }
2596
+ function findCommentOrTriple(line) {
2597
+ let i = 0;
2598
+ let inStr = null;
2599
+ while (i < line.length) {
2600
+ const c = line[i];
2601
+ if (inStr) {
2602
+ if (c === "\\") i += 2;
2603
+ else if (c === inStr) inStr = null;
2604
+ else i++;
2605
+ continue;
2606
+ }
2607
+ if (c === "'" || c === '"') {
2608
+ inStr = c;
2609
+ i++;
2610
+ continue;
2611
+ }
2612
+ if (c === "#") {
2613
+ return {
2614
+ kind: line.slice(0, i).trim().length === 0 ? "comment-full" : "none",
2615
+ idx: i,
2616
+ quote: ""
2617
+ };
2618
+ }
2619
+ if (line.startsWith('"""', i) || line.startsWith("'''", i)) {
2620
+ return { kind: "triple-mid", idx: i, quote: line.slice(i, i + 3) };
2621
+ }
2622
+ i++;
2623
+ }
2624
+ return { kind: "none", idx: -1, quote: "" };
2625
+ }
2626
+ function scanTemplateRest(line, from, st) {
2627
+ let m = st.mode;
2628
+ let q = st.quote;
2629
+ let d = st.depth;
2630
+ let i = from;
2631
+ while (i < line.length) {
2632
+ const c = line[i];
2633
+ if (m === "str") {
2634
+ if (c === "\\") i += 2;
2635
+ else if (c === q) m = "expr";
2636
+ else i++;
2637
+ continue;
2638
+ }
2639
+ if (m === "expr") {
2640
+ if (c === "'" || c === '"') {
2641
+ q = c;
2642
+ m = "str";
2643
+ i++;
2644
+ continue;
2645
+ }
2646
+ if (c === "`")
2647
+ return {
2648
+ mode: m,
2649
+ quote: q,
2650
+ depth: d,
2651
+ closed: false,
2652
+ bail: true,
2653
+ consumed: 0
2654
+ };
2655
+ if (c === "{") d++;
2656
+ else if (c === "}") {
2657
+ d--;
2658
+ if (d === 0) m = "tpl";
2659
+ }
2660
+ i++;
2661
+ continue;
2662
+ }
2663
+ if (c === "\\") {
2664
+ i += 2;
2665
+ continue;
2666
+ }
2667
+ if (c === "`")
2668
+ return {
2669
+ mode: "tpl",
2670
+ quote: "",
2671
+ depth: 0,
2672
+ closed: true,
2673
+ bail: false,
2674
+ consumed: i - from + 1
2675
+ };
2676
+ if (c === "$" && line[i + 1] === "{") {
2677
+ m = "expr";
2678
+ d = 1;
2679
+ i += 2;
2680
+ continue;
2681
+ }
2682
+ i++;
2683
+ }
2684
+ return {
2685
+ mode: m,
2686
+ quote: q,
2687
+ depth: d,
2688
+ closed: false,
2689
+ bail: false,
2690
+ consumed: 0
2691
+ };
2692
+ }
2693
+ function regexAllowedAfter(prev) {
2694
+ if (prev === null) return true;
2695
+ if (/[A-Za-z0-9_]/.test(prev)) return false;
2696
+ return !".'\"`) ]".includes(prev);
2697
+ }
2698
+ function prevSigChar(line, i) {
2699
+ for (let j = i - 1; j >= 0; j--) {
2700
+ const c = line[j];
2701
+ if (c === " " || c === " ") continue;
2702
+ return c;
2703
+ }
2704
+ return null;
2705
+ }
2706
+ function scanRegexLiteral(line, i) {
2707
+ let j = i + 1;
2708
+ let inClass = false;
2709
+ while (j < line.length) {
2710
+ const c = line[j];
2711
+ if (c === "\\") {
2712
+ j += 2;
2713
+ continue;
2714
+ }
2715
+ if (inClass) {
2716
+ if (c === "]") inClass = false;
2717
+ } else if (c === "[") {
2718
+ inClass = true;
2719
+ } else if (c === "/") {
2720
+ let f = j + 1;
2721
+ while (f < line.length && /[a-z]/i.test(line[f])) f++;
2722
+ return f;
2723
+ }
2724
+ j++;
2725
+ }
2726
+ return null;
2727
+ }
2728
+ function scanJsCodeLine(line) {
2729
+ let i = 0;
2730
+ let inStr = null;
2731
+ while (i < line.length) {
2732
+ const c = line[i];
2733
+ if (inStr) {
2734
+ if (c === "\\") i += 2;
2735
+ else if (c === inStr) inStr = null;
2736
+ else i++;
2737
+ continue;
2738
+ }
2739
+ if (c === "'" || c === '"') {
2740
+ inStr = c;
2741
+ i++;
2742
+ continue;
2743
+ }
2744
+ if (c === "`") {
2745
+ const r = scanTemplateRest(line, i, { mode: "tpl", quote: "", depth: 0 });
2746
+ if (r.bail) return { kind: "code", bail: true };
2747
+ if (!r.closed)
2748
+ return {
2749
+ kind: "template-open",
2750
+ tmpl: { mode: r.mode, quote: r.quote, depth: r.depth }
2751
+ };
2752
+ i += r.consumed;
2753
+ continue;
2754
+ }
2755
+ if (c === "/") {
2756
+ const nxt = line[i + 1];
2757
+ if (nxt === "/") {
2758
+ return {
2759
+ kind: line.slice(0, i).trim().length === 0 ? "comment-full" : "code"
2760
+ };
2761
+ }
2762
+ if (nxt !== "*") {
2763
+ if (regexAllowedAfter(prevSigChar(line, i))) {
2764
+ const end = scanRegexLiteral(line, i);
2765
+ if (end !== null) {
2766
+ i = end;
2767
+ continue;
2768
+ }
2769
+ }
2770
+ i++;
2771
+ continue;
2772
+ }
2773
+ const close = line.indexOf("*/", i + 2);
2774
+ const before = line.slice(0, i).trim().length > 0;
2775
+ if (close === -1)
2776
+ return { kind: before ? "block-open-mid" : "block-open-only" };
2777
+ const rest = line.slice(close + 2).trim();
2778
+ if (rest.length === 0) return { kind: before ? "code" : "comment-full" };
2779
+ i = close + 2;
2780
+ continue;
2781
+ }
2782
+ i++;
2783
+ }
2784
+ return { kind: "code" };
2785
+ }
2786
+ function trimJsTs(text) {
2787
+ const lines = text.split(/\r?\n/);
2788
+ const emitted = [];
2789
+ let pending = 0;
2790
+ const flush = () => {
2791
+ if (pending > 0) {
2792
+ emitted.push(
2793
+ `// [acp-crush: elided ${pending} line${pending === 1 ? "" : "s"}]`
2794
+ );
2795
+ pending = 0;
2796
+ }
2797
+ };
2798
+ let state = "code";
2799
+ let tmpl = { mode: "tpl", quote: "", depth: 0 };
2800
+ let blankRun = 0;
2801
+ for (const line of lines) {
2802
+ if (state === "block") {
2803
+ const close = line.indexOf("*/");
2804
+ if (close === -1) {
2805
+ pending++;
2806
+ continue;
2807
+ }
2808
+ state = "code";
2809
+ if (line.slice(close + 2).trim().length === 0) pending++;
2810
+ else {
2811
+ flush();
2812
+ emitted.push(line);
2813
+ }
2814
+ blankRun = 0;
2815
+ continue;
2816
+ }
2817
+ if (state === "template") {
2818
+ const r = scanTemplateRest(line, 0, tmpl);
2819
+ if (r.bail) return null;
2820
+ tmpl = { mode: r.mode, quote: r.quote, depth: r.depth };
2821
+ if (r.closed) state = "code";
2822
+ emitted.push(line);
2823
+ blankRun = 0;
2824
+ continue;
2825
+ }
2826
+ if (line.trim().length === 0) {
2827
+ blankRun++;
2828
+ if (blankRun >= 3) {
2829
+ flush();
2830
+ emitted.push("");
2831
+ blankRun = 1;
2832
+ }
2833
+ continue;
2834
+ }
2835
+ const scan = scanJsCodeLine(line);
2836
+ if (scan.bail) return null;
2837
+ if (scan.kind === "comment-full") {
2838
+ pending++;
2839
+ continue;
2840
+ }
2841
+ if (scan.kind === "block-open-only") {
2842
+ pending++;
2843
+ state = "block";
2844
+ continue;
2845
+ }
2846
+ if (scan.kind === "block-open-mid") {
2847
+ flush();
2848
+ state = "block";
2849
+ emitted.push(line);
2850
+ blankRun = 0;
2851
+ continue;
2852
+ }
2853
+ if (scan.kind === "template-open") {
2854
+ flush();
2855
+ state = "template";
2856
+ tmpl = scan.tmpl ?? { mode: "tpl", quote: "", depth: 0 };
2857
+ emitted.push(line);
2858
+ blankRun = 0;
2859
+ continue;
2860
+ }
2861
+ flush();
2862
+ emitted.push(line);
2863
+ blankRun = 0;
2864
+ }
2865
+ if (state !== "code") return null;
2866
+ flush();
2867
+ return finalizeTrimmed(text, emitted);
2868
+ }
2869
+ function crushCode(text) {
2870
+ const lang = detectLanguage(text);
2871
+ if (lang === null) return null;
2872
+ if (lang === "python") return trimPython(text);
2873
+ return trimJsTs(text);
2874
+ }
2875
+ var LOG_MIN_LINES = 50;
2876
+ var LOG_MAX_TOTAL_LINES = 100;
2877
+ var LOG_MAX_ERRORS = 20;
2878
+ var LOG_ERROR_CONTEXT = 3;
2879
+ var LOG_MAX_WARNINGS = 5;
2880
+ var LOG_MAX_STACK_TRACES = 3;
2881
+ var LOG_STACK_MAX_LINES = 20;
2882
+ var LOG_TRACE_HEAD_FRAMES = 3;
2883
+ var LOG_TRACE_APP_FRAMES = 5;
2884
+ var LOG_CLASSIFIED_GATE = 5;
2885
+ var LOG_SUMMARY_GATE = 3;
2886
+ var LEVEL_PATTERNS = [
2887
+ ["error", /\b(ERROR|FATAL|CRITICAL)\b/i],
2888
+ ["fail", /\b(FAIL|FAILED)\b/i],
2889
+ ["warn", /\b(WARN|WARNING)\b/i],
2890
+ ["info", /\bINFO\b/i],
2891
+ ["debug", /\bDEBUG\b/i],
2892
+ ["trace", /\bTRACE\b/i]
2893
+ ];
2894
+ function classifyLevel(line) {
2895
+ for (const [level, re] of LEVEL_PATTERNS) {
2896
+ if (re.test(line)) return level;
2897
+ }
2898
+ return "unknown";
2899
+ }
2900
+ function isLogSummaryLine(line) {
2901
+ if (line.startsWith("===") || line.startsWith("---")) return true;
2902
+ let d = 0;
2903
+ while (d < line.length && line.charCodeAt(d) >= 48 && line.charCodeAt(d) <= 57)
2904
+ d++;
2905
+ if (d > 0 && line[d] === " ") {
2906
+ const rest = line.slice(d + 1);
2907
+ if (rest.startsWith("passed") || rest.startsWith("failed") || rest.startsWith("skipped") || rest.startsWith("error") || rest.startsWith("warning"))
2908
+ return true;
2909
+ }
2910
+ for (const prefix of [
2911
+ "Test ",
2912
+ "Tests ",
2913
+ "Tests:",
2914
+ "Test:",
2915
+ "Suite ",
2916
+ "Suites ",
2917
+ "Suites:",
2918
+ "Suite:"
2919
+ ]) {
2920
+ if (line.startsWith(prefix)) {
2921
+ const m = line.slice(prefix.length).match(/\S/);
2922
+ if (m !== null && m[0].charCodeAt(0) >= 48 && m[0].charCodeAt(0) <= 57)
2923
+ return true;
2924
+ }
2925
+ }
2926
+ if (line.startsWith("TOTAL") || line.startsWith("Total") || line.startsWith("Summary"))
2927
+ return true;
2928
+ for (const prefix of ["Build", "Compile", "Test"]) {
2929
+ if (line.startsWith(prefix) && (line.includes("succeeded") || line.includes("failed") || line.includes("complete")))
2930
+ return true;
2931
+ }
2932
+ return false;
2933
+ }
2934
+ function isDigitChar(c) {
2935
+ return c >= "0" && c <= "9";
2936
+ }
2937
+ function isAsciiAlnum(c) {
2938
+ const n = c.charCodeAt(0);
2939
+ return n >= 97 && n <= 122 || n >= 65 && n <= 90 || n >= 48 && n <= 57;
2940
+ }
2941
+ function hasLineColSuffix(s) {
2942
+ for (let i = 0; i + 1 < s.length; i++) {
2943
+ if (s[i] === ":" && isDigitChar(s[i + 1])) {
2944
+ let j = i + 1;
2945
+ while (j < s.length && isDigitChar(s[j])) j++;
2946
+ if (j + 1 < s.length && s[j] === ":" && isDigitChar(s[j + 1]))
2947
+ return true;
2948
+ }
2949
+ }
2950
+ return false;
2951
+ }
2952
+ function isPythonFileFrame(s) {
2953
+ return s.startsWith('File "') && s.includes('", line ') && s.length > 0 && isDigitChar(s[s.length - 1]);
2954
+ }
2955
+ function isJsAtFrame(s) {
2956
+ return s.startsWith("at ") && s.includes("(") && s.includes(")") && hasLineColSuffix(s);
2957
+ }
2958
+ function isJavaAtFrame(s) {
2959
+ if (!s.startsWith("at ") || !s.includes("(")) return false;
2960
+ const open = s.indexOf("(");
2961
+ const body = s.slice(3, open);
2962
+ if (body.length === 0) return false;
2963
+ for (const c of body) {
2964
+ if (!(isAsciiAlnum(c) || c === "." || c === "_" || c === "$" || c === "/"))
2965
+ return false;
2966
+ }
2967
+ return true;
2968
+ }
2969
+ function isRustPanicOpener(s) {
2970
+ return s.startsWith("thread '") && s.includes("panicked at");
2971
+ }
2972
+ function isGoroutineHeader(line) {
2973
+ if (!line.startsWith("goroutine ")) return false;
2974
+ const rest = line.slice(10);
2975
+ let d = 0;
2976
+ while (d < rest.length && isDigitChar(rest[d])) d++;
2977
+ return d > 0 && rest.slice(d).startsWith(" [");
2978
+ }
2979
+ function isGoPanicOpener(line) {
2980
+ return line.startsWith("panic: ") || line.startsWith("fatal error: ") || isGoroutineHeader(line);
2981
+ }
2982
+ function isGoFileFrame(line) {
2983
+ return line.startsWith(" ") && line.includes(".go:") && line.includes(" +0x");
2984
+ }
2985
+ function isGoCallFrame(line) {
2986
+ if (line.startsWith("created by ")) return true;
2987
+ if (line.startsWith(" ") || line.startsWith(" ") || !line.endsWith(")"))
2988
+ return false;
2989
+ const open = line.indexOf("(");
2990
+ if (open === -1) return false;
2991
+ const symbol = line.slice(0, open);
2992
+ if (symbol.length === 0 || !symbol.includes(".")) return false;
2993
+ for (const c of symbol) {
2994
+ if (!(isAsciiAlnum(c) || c === "." || c === "_" || c === "/" || c === "*"))
2995
+ return false;
2996
+ }
2997
+ return true;
2998
+ }
2999
+ function isDotnetFrame(s) {
3000
+ return s.startsWith("at ") && s.includes(") in ") && s.includes(":line ");
3001
+ }
3002
+ function isDotnetExceptionHead(trimmed) {
3003
+ const colon = trimmed.indexOf(":");
3004
+ if (colon === -1) return false;
3005
+ const head = trimmed.slice(0, colon);
3006
+ if (!head.endsWith("Exception") || !head.includes(".")) return false;
3007
+ for (const c of head) {
3008
+ if (!(isAsciiAlnum(c) || c === "." || c === "_" || c === "`" || c === "+"))
3009
+ return false;
3010
+ }
3011
+ return true;
3012
+ }
3013
+ function isRustBacktraceFrame(s) {
3014
+ const t = s.trimStart();
3015
+ let i = 0;
3016
+ while (i < t.length && isDigitChar(t[i])) i++;
3017
+ if (i === 0 || t[i] !== ":") return false;
3018
+ i++;
3019
+ while (i < t.length && t[i] === " ") i++;
3020
+ const rest = t.slice(i);
3021
+ if (!rest.startsWith("0x")) return false;
3022
+ let h = 0;
3023
+ for (const c of rest.slice(2)) {
3024
+ if (c >= "0" && c <= "9" || c >= "a" && c <= "f" || c >= "A" && c <= "F")
3025
+ h++;
3026
+ else break;
3027
+ }
3028
+ return h > 0;
3029
+ }
3030
+ function isJavaMoreSummary(trimmed) {
3031
+ if (!trimmed.startsWith("... ")) return false;
3032
+ const rest = trimmed.slice(4);
3033
+ let d = 0;
3034
+ while (d < rest.length && isDigitChar(rest[d])) d++;
3035
+ return d > 0 && rest.slice(d).trim() === "more";
3036
+ }
3037
+ function traceFlavorFor(line) {
3038
+ const t = line.trimStart();
3039
+ if (t.startsWith("Traceback (most recent call last)") || isPythonFileFrame(t))
3040
+ return "py";
3041
+ if (t.startsWith("Unhandled exception.") || isDotnetFrame(t)) return "dotnet";
3042
+ if (isJsAtFrame(t)) return "js";
3043
+ if (isJavaAtFrame(t)) return "java";
3044
+ if (t.startsWith("--> ") && hasLineColSuffix(t)) return "rust-error";
3045
+ if (isRustPanicOpener(t) || t.startsWith("stack backtrace:") || isRustBacktraceFrame(line))
3046
+ return "rust-backtrace";
3047
+ if (isGoPanicOpener(line)) return "go-panic";
3048
+ return null;
3049
+ }
3050
+ function traceTerminates(flavor, line, linesSoFar) {
3051
+ const t = line.trimStart();
3052
+ switch (flavor) {
3053
+ case "py": {
3054
+ const indentedOrBlank = line.startsWith(" ") || line.startsWith(" ") || line.length === 0;
3055
+ const continuation = t.startsWith("Traceback") || t.startsWith("File ") || t.startsWith("During handling") || t.startsWith("The above exception");
3056
+ if (indentedOrBlank || continuation) return false;
3057
+ return !(t.length > 0 && t[0] >= "A" && t[0] <= "Z");
3058
+ }
3059
+ case "js":
3060
+ return !t.startsWith("at ") && line.length !== 0;
3061
+ case "java": {
3062
+ const chain = t.startsWith("Caused by:") || t.startsWith("Suppressed:") || isJavaMoreSummary(t);
3063
+ return !t.startsWith("at ") && !chain && line.length !== 0;
3064
+ }
3065
+ case "dotnet": {
3066
+ if (line.length === 0) return false;
3067
+ const continues = t.startsWith("at ") || t.startsWith("--->") || t.startsWith("--- End of") || isDotnetExceptionHead(t);
3068
+ return !continues;
3069
+ }
3070
+ case "rust-error":
3071
+ return !t.startsWith("--> ") && line.length !== 0;
3072
+ case "rust-backtrace": {
3073
+ if (line.length === 0 || linesSoFar === 1) return false;
3074
+ const isFrame = t.length > 0 && isDigitChar(t[0]);
3075
+ const continuation = line.startsWith(" ") || line.startsWith(" ") || t.startsWith("stack backtrace:") || t.startsWith("note: run with");
3076
+ return !isFrame && !continuation;
3077
+ }
3078
+ case "go-panic": {
3079
+ if (line.length === 0) return false;
3080
+ const continues = line.startsWith(" ") || isGoroutineHeader(line) || isGoCallFrame(line) || line.startsWith("panic: ") || line.startsWith("fatal error: ") || line.startsWith("[signal ");
3081
+ return !continues;
3082
+ }
3083
+ }
3084
+ }
3085
+ function scoreLogLine(l) {
3086
+ 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;
3087
+ return Math.min(base + (l.isStack ? 0.3 : 0) + (l.isSummary ? 0.4 : 0), 1);
3088
+ }
3089
+ function parseLogLines(lines) {
3090
+ const out = new Array(lines.length);
3091
+ let active = null;
3092
+ let traceLines = 0;
3093
+ for (let i = 0; i < lines.length; i++) {
3094
+ const line = lines[i];
3095
+ const entry = {
3096
+ i,
3097
+ content: line,
3098
+ level: classifyLevel(line),
3099
+ isStack: false,
3100
+ isSummary: isLogSummaryLine(line),
3101
+ score: 0
3102
+ };
3103
+ if (active !== null) {
3104
+ const flavor = active;
3105
+ if (traceLines >= LOG_STACK_MAX_LINES || traceTerminates(flavor, line, traceLines)) {
3106
+ const capHit = traceLines >= LOG_STACK_MAX_LINES;
3107
+ active = null;
3108
+ traceLines = 0;
3109
+ const nf = traceFlavorFor(line);
3110
+ if (nf !== null) {
3111
+ active = nf;
3112
+ traceLines = 1;
3113
+ entry.isStack = true;
3114
+ } else if (capHit && !traceTerminates(flavor, line, 2)) {
3115
+ active = flavor;
3116
+ traceLines = 1;
3117
+ entry.isStack = true;
3118
+ }
3119
+ } else {
3120
+ entry.isStack = true;
3121
+ traceLines++;
3122
+ }
3123
+ } else {
3124
+ const f = traceFlavorFor(line);
3125
+ if (f !== null) {
3126
+ active = f;
3127
+ traceLines = 1;
3128
+ entry.isStack = true;
3129
+ }
3130
+ }
3131
+ entry.score = scoreLogLine(entry);
3132
+ out[i] = entry;
3133
+ }
3134
+ return out;
3135
+ }
3136
+ function selectWithFirstLast(arr, maxCount) {
3137
+ if (arr.length <= maxCount) return arr.slice();
3138
+ const out = [];
3139
+ const seen = /* @__PURE__ */ new Set();
3140
+ const push = (l) => {
3141
+ if (seen.add(l.i)) out.push(l);
3142
+ };
3143
+ push(arr[0]);
3144
+ push(arr[arr.length - 1]);
3145
+ if (out.length < maxCount) {
3146
+ const byScore = arr.slice().sort((a, b) => b.score - a.score || a.i - b.i);
3147
+ for (const l of byScore) {
3148
+ if (!seen.has(l.i)) {
3149
+ push(l);
3150
+ if (out.length >= maxCount) break;
3151
+ }
3152
+ }
3153
+ }
3154
+ return out;
3155
+ }
3156
+ function normalizeForDedupe(content) {
3157
+ let splitAt = content.length;
3158
+ for (let i = 0; i < content.length; i++) {
3159
+ if (content[i] === ":" || content[i] === "=") {
3160
+ splitAt = i;
3161
+ break;
3162
+ }
3163
+ }
3164
+ const suffix = content.slice(splitAt).replace(/\d+/g, "N").replace(/0x[0-9a-fA-F]+/g, "ADDR").replace(/\/[\w/]+\//g, "/PATH/");
3165
+ return content.slice(0, splitAt) + suffix;
3166
+ }
3167
+ var RUNTIME_FRAME_PREFIXES = [
3168
+ "at java.",
3169
+ "at jdk.",
3170
+ "at sun.",
3171
+ "at javax.",
3172
+ "at scala.",
3173
+ "at System.",
3174
+ "at Microsoft.",
3175
+ "runtime.",
3176
+ "created by runtime."
3177
+ ];
3178
+ var RUNTIME_FRAME_MARKERS = [
3179
+ "site-packages/",
3180
+ "/usr/lib/python",
3181
+ "lib/python3.",
3182
+ "node:internal/",
3183
+ "node_modules/",
3184
+ "(internal/",
3185
+ "core::",
3186
+ "std::",
3187
+ "alloc::",
3188
+ "rust_begin_unwind",
3189
+ "__rust_",
3190
+ "/rustc/",
3191
+ "/usr/local/go/src/",
3192
+ "/libexec/src/runtime/"
3193
+ ];
3194
+ function isFrameLine(content) {
3195
+ const t = content.trimStart();
3196
+ return t.startsWith("at ") || t.startsWith('File "') && t.includes('", line ') || isRustBacktraceFrame(content) || isGoFileFrame(content) || isGoCallFrame(content);
3197
+ }
3198
+ function isChainHeadLine(content) {
3199
+ const t = content.trimStart();
3200
+ 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");
3201
+ }
3202
+ function isRuntimeFrame(content) {
3203
+ const t = content.trimStart();
3204
+ return RUNTIME_FRAME_PREFIXES.some((p) => t.startsWith(p)) || RUNTIME_FRAME_MARKERS.some((m) => content.includes(m));
3205
+ }
3206
+ function collapseTraceFrames(stack, headFrames, appFrames) {
3207
+ const kept = [];
3208
+ const dropped = /* @__PURE__ */ new Set();
3209
+ let framesSeen = 0;
3210
+ let appKept = 0;
3211
+ let runStart = -1;
3212
+ let runLen = 0;
3213
+ let prevDropped = false;
3214
+ const flushRun = () => {
3215
+ if (runStart !== -1) {
3216
+ kept.push({
3217
+ i: runStart,
3218
+ content: ` [... ${runLen} frames collapsed]`,
3219
+ level: "unknown",
3220
+ isStack: true,
3221
+ isSummary: false,
3222
+ score: 0.8
3223
+ });
3224
+ runStart = -1;
3225
+ runLen = 0;
3226
+ }
3227
+ };
3228
+ for (const line of stack) {
3229
+ if (isFrameLine(line.content) && !isChainHeadLine(line.content)) {
3230
+ framesSeen++;
3231
+ const runtime = isRuntimeFrame(line.content);
3232
+ const keep = framesSeen <= headFrames || !runtime && appKept < appFrames;
3233
+ if (keep) {
3234
+ if (!runtime) appKept++;
3235
+ flushRun();
3236
+ kept.push(line);
3237
+ prevDropped = false;
3238
+ } else {
3239
+ if (runStart === -1) runStart = line.i;
3240
+ runLen++;
3241
+ dropped.add(line.i);
3242
+ prevDropped = true;
3243
+ }
3244
+ } else if (prevDropped && (line.content.startsWith(" ") || line.content.startsWith(" ")) && !isChainHeadLine(line.content)) {
3245
+ runLen++;
3246
+ dropped.add(line.i);
3247
+ } else {
3248
+ flushRun();
3249
+ kept.push(line);
3250
+ prevDropped = false;
3251
+ }
3252
+ }
3253
+ flushRun();
3254
+ return { kept, dropped };
3255
+ }
3256
+ function selectLogLines(all) {
3257
+ const errors = [];
3258
+ const fails = [];
3259
+ const warnings = [];
3260
+ const summaries = [];
3261
+ const stacks = [];
3262
+ let current = [];
3263
+ for (const l of all) {
3264
+ if (l.level === "error") errors.push(l);
3265
+ else if (l.level === "fail") fails.push(l);
3266
+ else if (l.level === "warn") warnings.push(l);
3267
+ if (l.isStack) current.push(l);
3268
+ else if (current.length > 0) {
3269
+ stacks.push(current);
3270
+ current = [];
3271
+ }
3272
+ if (l.isSummary) summaries.push(l);
3273
+ }
3274
+ if (current.length > 0) stacks.push(current);
3275
+ const selected = /* @__PURE__ */ new Map();
3276
+ for (const l of selectWithFirstLast(errors, LOG_MAX_ERRORS))
3277
+ selected.set(l.i, l);
3278
+ for (const l of selectWithFirstLast(fails, LOG_MAX_ERRORS))
3279
+ selected.set(l.i, l);
3280
+ const seenWarn = /* @__PURE__ */ new Set();
3281
+ const dedupedWarnings = [];
3282
+ for (const w of warnings) {
3283
+ const key = normalizeForDedupe(w.content);
3284
+ if (!seenWarn.has(key)) {
3285
+ seenWarn.add(key);
3286
+ dedupedWarnings.push(w);
3287
+ }
3288
+ }
3289
+ for (const w of dedupedWarnings.slice(0, LOG_MAX_WARNINGS))
3290
+ selected.set(w.i, w);
3291
+ for (const stack of stacks.slice(0, LOG_MAX_STACK_TRACES)) {
3292
+ if (stack.length > LOG_STACK_MAX_LINES) {
3293
+ const collapsed = collapseTraceFrames(
3294
+ stack,
3295
+ LOG_TRACE_HEAD_FRAMES,
3296
+ LOG_TRACE_APP_FRAMES
3297
+ );
3298
+ for (const i of collapsed.dropped) selected.delete(i);
3299
+ for (const l of collapsed.kept.slice(0, LOG_STACK_MAX_LINES))
3300
+ selected.set(l.i, l);
3301
+ } else {
3302
+ for (const l of stack) selected.set(l.i, l);
3303
+ }
1684
3304
  }
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
- };
3305
+ for (const s of summaries) selected.set(s.i, s);
3306
+ for (const idx of Array.from(selected.keys())) {
3307
+ const lo = Math.max(0, idx - LOG_ERROR_CONTEXT);
3308
+ const hi = Math.min(all.length, idx + LOG_ERROR_CONTEXT + 1);
3309
+ for (let i = lo; i < hi; i++) {
3310
+ if (i !== idx && !selected.has(i)) selected.set(i, all[i]);
3311
+ }
1693
3312
  }
1694
- const existing = (input.state.absorbed ?? []).find(
1695
- (record2) => record2.resultMessageId === rawId
3313
+ let ordered = Array.from(selected.values());
3314
+ if (ordered.length > LOG_MAX_TOTAL_LINES) {
3315
+ ordered.sort((a, b) => b.score - a.score || a.i - b.i);
3316
+ ordered = ordered.slice(0, LOG_MAX_TOTAL_LINES);
3317
+ ordered.sort((a, b) => a.i - b.i);
3318
+ }
3319
+ return ordered;
3320
+ }
3321
+ function formatLogOutput(selected, all) {
3322
+ const count = (lv) => all.reduce((n, l) => l.level === lv ? n + 1 : n, 0);
3323
+ const output = selected.map((l) => l.content);
3324
+ const omitted = all.length - selected.length;
3325
+ if (omitted > 0) {
3326
+ const parts = [];
3327
+ const e = count("error");
3328
+ const f = count("fail");
3329
+ const w = count("warn");
3330
+ const inf = count("info");
3331
+ if (e > 0) parts.push(`${e} ERROR`);
3332
+ if (f > 0) parts.push(`${f} FAIL`);
3333
+ if (w > 0) parts.push(`${w} WARN`);
3334
+ if (inf > 0) parts.push(`${inf} INFO`);
3335
+ if (parts.length > 0)
3336
+ output.push(`[${omitted} lines omitted: ${parts.join(", ")}]`);
3337
+ }
3338
+ return output.join("\n");
3339
+ }
3340
+ var LOG_FORMAT_TABLE = [
3341
+ [
3342
+ "pytest",
3343
+ [
3344
+ "=== FAILURES",
3345
+ "=== ERRORS",
3346
+ "=== test session",
3347
+ "=== short test summary",
3348
+ "PASSED [",
3349
+ "FAILED [",
3350
+ "ERROR [",
3351
+ "SKIPPED [",
3352
+ "collected "
3353
+ ]
3354
+ ],
3355
+ ["npm", ["npm ERR!", "npm WARN", "npm info", "npm http"]],
3356
+ ["cargo", ["Compiling ", "Finished ", "Running ", "warning: ", "error[E"]],
3357
+ ["jest", ["PASS ", "FAIL ", "Test Suites:"]],
3358
+ ["make", ["make[", "make:", "gcc ", "g++ ", "clang "]]
3359
+ ];
3360
+ function detectLogFormat(lines) {
3361
+ const sample = lines.slice(0, 100);
3362
+ let best = "generic";
3363
+ let bestScore = 0;
3364
+ for (const [name, pats] of LOG_FORMAT_TABLE) {
3365
+ let score = 0;
3366
+ for (const line of sample) {
3367
+ if (pats.some((p) => line.includes(p))) score++;
3368
+ }
3369
+ if (score > bestScore) {
3370
+ bestScore = score;
3371
+ best = name;
3372
+ }
3373
+ }
3374
+ return best;
3375
+ }
3376
+ function crushLog(text) {
3377
+ const lines = text.split(/\r?\n/);
3378
+ if (lines.length < LOG_MIN_LINES) return null;
3379
+ let classified = 0;
3380
+ let summaries = 0;
3381
+ let traces = 0;
3382
+ for (const line of lines) {
3383
+ const lv = classifyLevel(line);
3384
+ if (lv !== "unknown") classified++;
3385
+ if (isLogSummaryLine(line)) summaries++;
3386
+ if (traceFlavorFor(line) !== null) traces++;
3387
+ }
3388
+ if (!(classified >= LOG_CLASSIFIED_GATE || traces >= 1 || summaries >= LOG_SUMMARY_GATE || detectLogFormat(lines) !== "generic"))
3389
+ return null;
3390
+ const parsed = parseLogLines(lines);
3391
+ const selected = selectLogLines(parsed);
3392
+ if (selected.length >= lines.length) return null;
3393
+ const out = formatLogOutput(selected, parsed);
3394
+ if (out.length >= text.length) return null;
3395
+ return out;
3396
+ }
3397
+ function errorLinesSurvive(input, output) {
3398
+ const kept = new Set(
3399
+ output.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0)
1696
3400
  );
1697
- if (existing) {
1698
- return {
1699
- state: input.state,
1700
- ok: true,
1701
- resultText: `already absorbed (${input.ref}) \u2014 no change.`
1702
- };
3401
+ for (const line of input.split(/\r?\n/)) {
3402
+ const level = classifyLevel(line);
3403
+ if (level !== "error" && level !== "fail") continue;
3404
+ const t = line.trim();
3405
+ if (t.length > 0 && !kept.has(t)) return false;
1703
3406
  }
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
- };
3407
+ return true;
3408
+ }
3409
+ var jsonFoldPlugin = {
3410
+ id: "json-fold",
3411
+ kinds: ["json"],
3412
+ run(text) {
3413
+ const out = crushJson(text);
3414
+ return out ? { text: out, lossy: false } : null;
1711
3415
  }
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
- };
3416
+ };
3417
+ var codeTrimPlugin = {
3418
+ id: "code-trim",
3419
+ kinds: ["code"],
3420
+ run(text) {
3421
+ const out = crushCode(text);
3422
+ return out ? { text: out, lossy: true } : null;
1718
3423
  }
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
- };
3424
+ };
3425
+ var logSelectPlugin = {
3426
+ id: "log-select",
3427
+ kinds: ["log"],
3428
+ run(text) {
3429
+ const out = crushLog(text);
3430
+ return out ? { text: out, lossy: true } : null;
1725
3431
  }
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
- };
3432
+ };
3433
+ var builtInPlugins = [
3434
+ jsonFoldPlugin,
3435
+ codeTrimPlugin,
3436
+ logSelectPlugin
3437
+ ];
3438
+ var crushRegistry = /* @__PURE__ */ new Map();
3439
+ for (const p of builtInPlugins) crushRegistry.set(p.id, p);
3440
+ function registerCrushPlugin(def) {
3441
+ crushRegistry.set(def.id, def);
3442
+ }
3443
+ function unregisterCrushPlugin(id) {
3444
+ crushRegistry.delete(id);
3445
+ }
3446
+ function listCrushPlugins() {
3447
+ return Array.from(crushRegistry.values()).map((p) => ({
3448
+ id: p.id,
3449
+ kinds: p.kinds
3450
+ }));
3451
+ }
3452
+ function resetCrushPlugins() {
3453
+ crushRegistry.clear();
3454
+ for (const p of builtInPlugins) crushRegistry.set(p.id, p);
3455
+ }
3456
+ function registeredPlugins() {
3457
+ return Array.from(crushRegistry.values());
3458
+ }
3459
+ function resolveCrushConfig(config) {
3460
+ return { ...DEFAULT_CRUSH_CONFIG, ...config.crush ?? {} };
3461
+ }
3462
+ function effectivePlugins(crush, meta) {
3463
+ const toolName = meta.toolName;
3464
+ return registeredPlugins().filter((p) => {
3465
+ const ov = crush.strategies?.[p.id];
3466
+ if (ov?.enabled === false) return false;
3467
+ if (toolName && ov?.excludeTools?.some((pat) => matchToolPattern(toolName, pat)))
3468
+ return false;
3469
+ return true;
3470
+ });
3471
+ }
3472
+ function evaluateToolResult(input) {
3473
+ const { absorb, crush, tokenCount, modelContextLimit, meta = {} } = input;
3474
+ const countTokens = input.countTokens ?? defaultCountTokens;
3475
+ const text = input.text;
3476
+ const rawTokens = countTokens(text);
3477
+ if (absorb.contextThresholdPct > 0 && modelContextLimit > 0 && tokenCount < absorb.contextThresholdPct * modelContextLimit) {
3478
+ return { kind: "skip", text, rawTokens };
1735
3479
  }
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
- };
3480
+ if (rawTokens < absorb.minToolTokens) {
3481
+ return { kind: "skip", text, rawTokens };
1742
3482
  }
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.` : "";
3483
+ const out = crushText(text, {
3484
+ minReduction: crush.minReduction,
3485
+ countTokens,
3486
+ meta,
3487
+ plugins: effectivePlugins(crush, meta)
3488
+ });
3489
+ if (!out) return { kind: "distill", text, rawTokens };
3490
+ const newTokens = countTokens(out.text);
1766
3491
  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}`
3492
+ kind: newTokens < absorb.minToolTokens ? "crushed" : "distill",
3493
+ text: out.text,
3494
+ rawTokens,
3495
+ newTokens,
3496
+ reduction: (rawTokens - newTokens) / rawTokens,
3497
+ strategy: out.strategy,
3498
+ lossy: out.lossy
1770
3499
  };
1771
3500
  }
3501
+ function applyCrushToMessages(messages, config, tokenCount, countTokens) {
3502
+ const absorb = resolveAbsorbConfig(config);
3503
+ const crush = resolveCrushConfig(config);
3504
+ let crushedCount = 0;
3505
+ let distilledCount = 0;
3506
+ let changed = false;
3507
+ const out = [];
3508
+ for (const msg of messages) {
3509
+ if (!isAbsorbCandidate(msg, config)) {
3510
+ out.push(msg);
3511
+ continue;
3512
+ }
3513
+ const text = msg.text ?? "";
3514
+ const markerAt = text.indexOf(ABSORB_PROMPT_MARKER);
3515
+ const payload = markerAt >= 0 ? text.slice(0, markerAt) : text;
3516
+ const ev = evaluateToolResult({
3517
+ text: payload,
3518
+ absorb,
3519
+ crush,
3520
+ tokenCount,
3521
+ modelContextLimit: config.modelContextLimit,
3522
+ meta: { toolName: msg.toolName },
3523
+ countTokens
3524
+ });
3525
+ if (ev.kind === "skip") {
3526
+ out.push(msg);
3527
+ continue;
3528
+ }
3529
+ changed = true;
3530
+ if (ev.kind === "crushed") crushedCount++;
3531
+ else distilledCount++;
3532
+ out.push({ ...msg, text: ev.text });
3533
+ }
3534
+ return { messages: changed ? out : messages, crushedCount, distilledCount };
3535
+ }
1772
3536
 
1773
3537
  // src/filter/registry.ts
1774
3538
  var registry = /* @__PURE__ */ new Map();
@@ -2251,6 +4015,11 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs, cou
2251
4015
  skipSinceProtected = true;
2252
4016
  continue;
2253
4017
  }
4018
+ if (hasMediaPayload(msg)) {
4019
+ skipSinceCompressible = true;
4020
+ skipSinceProtected = true;
4021
+ continue;
4022
+ }
2254
4023
  if (isMessageProtectedWithPairing(msg, config, protectedCallIds) || isMessageLatestProtected(msg, latest)) {
2255
4024
  protectedMsgs.push({
2256
4025
  ref,
@@ -2684,10 +4453,12 @@ function createCore(ports = {}) {
2684
4453
  `[acp-kernel] Config validation warnings: ${configErrors.join("; ")}. Thresholds may not fire correctly.`
2685
4454
  );
2686
4455
  }
4456
+ const contentStore = input.contentStore ?? createContentStore();
2687
4457
  const ctx = {
2688
4458
  config: input.config,
2689
4459
  tokenCount: input.tokenCount,
2690
- countTokens
4460
+ countTokens,
4461
+ contentStore
2691
4462
  };
2692
4463
  const initial = {
2693
4464
  messages: input.messages,
@@ -2697,14 +4468,19 @@ function createCore(ports = {}) {
2697
4468
  const strategy = input.renderTags ?? "all";
2698
4469
  const nodes = buildNodes(strategy);
2699
4470
  const result = runPipeline(nodes, initial, ctx);
4471
+ const ccrEffect = result.effects.ccr;
2700
4472
  return {
2701
4473
  messages: result.messages,
2702
4474
  state: result.state,
2703
4475
  nudge: result.effects.nudge,
2704
4476
  terminalEscape: result.effects.terminalEscape,
2705
- truncationSkipped: result.effects.truncationSkipped
4477
+ truncationSkipped: result.effects.truncationSkipped,
4478
+ contentStore: ccrEffect?.store ?? contentStore
2706
4479
  };
2707
4480
  }
4481
+ function retrieve(store, ref) {
4482
+ return applyRetrieve({ store, ref });
4483
+ }
2708
4484
  function decompress(blockId, state) {
2709
4485
  return blockById(state, blockId);
2710
4486
  }
@@ -2724,7 +4500,12 @@ function createCore(ports = {}) {
2724
4500
  activeBlocks: active.length,
2725
4501
  totalBlocks: state.blocks.length,
2726
4502
  tokensCompressed: state.stats.tokensCompressed,
2727
- breakdown: { active: active.length, total: state.blocks.length }
4503
+ breakdown: {
4504
+ active: active.length,
4505
+ total: state.blocks.length,
4506
+ storedMessages: state.stats.storedCount ?? 0,
4507
+ retrievals: state.stats.retrievalCount ?? 0
4508
+ }
2728
4509
  };
2729
4510
  }
2730
4511
  function defaultNodes() {
@@ -2735,7 +4516,9 @@ function createCore(ports = {}) {
2735
4516
  assignRefsNode,
2736
4517
  syncBlocksNode,
2737
4518
  pruneNode,
4519
+ ccrStoreNode,
2738
4520
  absorbHideNode,
4521
+ crushNode,
2739
4522
  absorbPromptNode,
2740
4523
  filterNode,
2741
4524
  hideCompressCallsNode,
@@ -2748,6 +4531,7 @@ function createCore(ports = {}) {
2748
4531
  }
2749
4532
  return {
2750
4533
  processTurn,
4534
+ retrieve,
2751
4535
  applyCompression,
2752
4536
  defaultNodes,
2753
4537
  decompress,
@@ -2760,11 +4544,13 @@ var assignRefsNode = {
2760
4544
  run(io, ctx) {
2761
4545
  const hasProtection = ctx.config.protectedTools.length > 0 || !!ctx.config.isToolProtected || (ctx.config.protectedLatestTools?.length ?? 0) > 0;
2762
4546
  const latest = hasProtection ? collectLatestProtected(io.messages, ctx.config) : void 0;
2763
- const protectedFn = hasProtection ? (m) => isMessageProtected(m, ctx.config) || (latest ? isMessageLatestProtected(m, latest) : false) : void 0;
4547
+ const protectedFn = (m) => hasMediaPayload(m) || (hasProtection ? isMessageProtected(m, ctx.config) || (latest ? isMessageLatestProtected(m, latest) : false) : false);
2764
4548
  const refResult = assignRefs(io.messages, {
2765
4549
  existing: io.state.messageRefs,
2766
4550
  nextIndex: highestUsedIndex(io.state.messageRefs) + 1,
2767
- isProtected: protectedFn
4551
+ isProtected: protectedFn,
4552
+ // Ephemeral retrieval injections never consume a ref slot.
4553
+ shouldSkip: (m) => m.id.startsWith(RETRIEVED_ID_PREFIX)
2768
4554
  });
2769
4555
  return { ...io, state: { ...io.state, messageRefs: refResult.map } };
2770
4556
  }
@@ -2808,6 +4594,27 @@ var absorbPromptNode = {
2808
4594
  };
2809
4595
  }
2810
4596
  };
4597
+ var crushNode = {
4598
+ name: "crush",
4599
+ enabled: (_io, ctx) => ctx.config.crush?.enabled === true && ctx.config.absorb?.enabled === true,
4600
+ run(io, ctx) {
4601
+ const applied = applyCrushToMessages(
4602
+ io.messages,
4603
+ ctx.config,
4604
+ ctx.tokenCount,
4605
+ ctx.countTokens
4606
+ );
4607
+ return {
4608
+ ...io,
4609
+ messages: applied.messages,
4610
+ effects: {
4611
+ ...io.effects,
4612
+ crushCount: applied.crushedCount,
4613
+ crushDistilledCount: applied.distilledCount
4614
+ }
4615
+ };
4616
+ }
4617
+ };
2811
4618
  var filterNode = {
2812
4619
  name: "filter",
2813
4620
  enabled: (_io, ctx) => !!ctx.config.messageFilters?.enabled && listMessageFilters().length > 0,
@@ -3020,6 +4827,15 @@ function applySingleRange(input) {
3020
4827
  if (!kept.has(id)) effectiveMessageIds.delete(id);
3021
4828
  }
3022
4829
  }
4830
+ const mediaExcluded = directMessageIds.filter((id) => {
4831
+ const msg = input.messages.find((m) => m.id === id);
4832
+ return !!msg && hasMediaPayload(msg);
4833
+ });
4834
+ if (mediaExcluded.length > 0) {
4835
+ warnings.push(
4836
+ `Excluded ${mediaExcluded.length} message(s) carrying image/attachment payload(s) from compression range \u2014 their bytes are unrecoverable once folded (billion-context#1188); enable stripImages to release old ones.`
4837
+ );
4838
+ }
3023
4839
  const protectedRefs = input.protectedMessageIds;
3024
4840
  const hitProtectedRaw = protectedRefs ? filteredIds.filter((id) => {
3025
4841
  const ref = input.state.messageRefs.byRaw[id];
@@ -3185,7 +5001,7 @@ function filterProtectedToolMessages(directMessageIds, messages, config) {
3185
5001
  for (const id of directMessageIds) {
3186
5002
  const msg = messages.find((m) => m.id === id);
3187
5003
  if (!msg) continue;
3188
- if (isMessageProtected(msg, config) || isMessageLatestProtected(msg, latest)) {
5004
+ if (hasMediaPayload(msg) || isMessageProtected(msg, config) || isMessageLatestProtected(msg, latest)) {
3189
5005
  removedIds.add(id);
3190
5006
  if (msg.toolCallId) protectedCallIds.add(msg.toolCallId);
3191
5007
  }
@@ -4744,6 +6560,294 @@ function formatRulesList(rules) {
4744
6560
  return rules.map((rule, i) => `${i + 1}. [${rule.id}] ${rule.text}`).join("\n");
4745
6561
  }
4746
6562
 
6563
+ // src/image-compress.ts
6564
+ var DEFAULT_IMAGE_COMPRESSION_CONFIG = {
6565
+ enabled: false,
6566
+ minTokens: 512,
6567
+ maxDimension: 1280,
6568
+ quality: 80,
6569
+ format: "webp"
6570
+ };
6571
+ function resolveImageCompressionConfig(config) {
6572
+ return { ...DEFAULT_IMAGE_COMPRESSION_CONFIG, ...config.imageCompression ?? {} };
6573
+ }
6574
+ var PIXEL_IMAGE_FALLBACK_TOKENS = 16384;
6575
+ var TILE_EDGE = 512;
6576
+ var TILE_SHORT_SIDE = 768;
6577
+ var TILE_MAX_EDGE = 2048;
6578
+ function byteAt(b, off) {
6579
+ return b[off] ?? 0;
6580
+ }
6581
+ function u16be(b, off) {
6582
+ return byteAt(b, off) << 8 | byteAt(b, off + 1);
6583
+ }
6584
+ function u16le(b, off) {
6585
+ return byteAt(b, off) | byteAt(b, off + 1) << 8;
6586
+ }
6587
+ function u32be(b, off) {
6588
+ return (byteAt(b, off) << 24 | byteAt(b, off + 1) << 16 | byteAt(b, off + 2) << 8 | byteAt(b, off + 3)) >>> 0;
6589
+ }
6590
+ function i32le(b, off) {
6591
+ return byteAt(b, off) | byteAt(b, off + 1) << 8 | byteAt(b, off + 2) << 16 | byteAt(b, off + 3) << 24;
6592
+ }
6593
+ function parseImageDimensions(bytes) {
6594
+ const b = bytes;
6595
+ if (b.length < 10) return void 0;
6596
+ if (b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) {
6597
+ if (b.length < 24) return void 0;
6598
+ const w = u32be(b, 16);
6599
+ const h = u32be(b, 20);
6600
+ return w > 0 && h > 0 ? { width: w, height: h } : void 0;
6601
+ }
6602
+ if (b[0] === 71 && b[1] === 73 && b[2] === 70) {
6603
+ const w = u16le(b, 6);
6604
+ const h = u16le(b, 8);
6605
+ return w > 0 && h > 0 ? { width: w, height: h } : void 0;
6606
+ }
6607
+ if (b.length >= 26 && b[0] === 66 && b[1] === 77) {
6608
+ const w = i32le(b, 18);
6609
+ const h = Math.abs(i32le(b, 22));
6610
+ return w > 0 && h > 0 ? { width: w, height: h } : void 0;
6611
+ }
6612
+ 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) {
6613
+ if (b[12] === 86 && b[13] === 80 && b[14] === 56 && b[15] === 88) {
6614
+ if (b.length < 30) return void 0;
6615
+ const w = byteAt(b, 24) | byteAt(b, 25) << 8 | byteAt(b, 26) << 16;
6616
+ const h = byteAt(b, 27) | byteAt(b, 28) << 8 | byteAt(b, 29) << 16;
6617
+ return w > 0 && h > 0 ? { width: w + 1, height: h + 1 } : void 0;
6618
+ }
6619
+ if (b[12] === 86 && b[13] === 80 && b[14] === 56 && b[15] === 76) {
6620
+ if (b.length < 25) return void 0;
6621
+ if (b[20] !== 47) return void 0;
6622
+ const w = ((byteAt(b, 22) & 63) << 8 | byteAt(b, 21)) + 1;
6623
+ const h = (byteAt(b, 24) & 15) << 10 | byteAt(b, 23) << 2 | (byteAt(b, 22) & 192) >> 6;
6624
+ return w > 0 && h > 0 ? { width: w, height: h + 1 } : void 0;
6625
+ }
6626
+ if (b[12] === 86 && b[13] === 80 && b[14] === 56 && b[15] === 32) {
6627
+ if (b.length < 30) return void 0;
6628
+ if (b[23] !== 157 || b[24] !== 1 || b[25] !== 42) return void 0;
6629
+ const w = u16le(b, 26) & 16383;
6630
+ const h = u16le(b, 28) & 16383;
6631
+ return w > 0 && h > 0 ? { width: w, height: h } : void 0;
6632
+ }
6633
+ return void 0;
6634
+ }
6635
+ if (b[0] === 255 && b[1] === 216) {
6636
+ let off = 2;
6637
+ while (off + 4 <= b.length) {
6638
+ if (b[off] !== 255) {
6639
+ off += 1;
6640
+ continue;
6641
+ }
6642
+ const marker = byteAt(b, off + 1);
6643
+ if (marker === 216 || marker === 1 || marker >= 208 && marker <= 217) {
6644
+ off += 2;
6645
+ continue;
6646
+ }
6647
+ if (marker === 218) break;
6648
+ const segLen = u16be(b, off + 2);
6649
+ if (segLen < 2) break;
6650
+ if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
6651
+ if (off + 9 > b.length) break;
6652
+ const h = u16be(b, off + 5);
6653
+ const w = u16be(b, off + 7);
6654
+ if (w > 0 && h > 0) return { width: w, height: h };
6655
+ break;
6656
+ }
6657
+ off += 2 + segLen;
6658
+ }
6659
+ }
6660
+ return void 0;
6661
+ }
6662
+ var HEADER_SCAN_CHARS = 64;
6663
+ var JPEG_SCAN_CHARS = 35e4;
6664
+ function parseImageDimensionsFromBase64(b64) {
6665
+ if (typeof b64 !== "string" || b64.length === 0) return void 0;
6666
+ const head = parseImageDimensions(Buffer.from(b64.slice(0, HEADER_SCAN_CHARS), "base64"));
6667
+ if (head) return head;
6668
+ if (b64.startsWith("/9j/") && b64.length > HEADER_SCAN_CHARS) {
6669
+ return parseImageDimensions(Buffer.from(b64.slice(0, JPEG_SCAN_CHARS), "base64"));
6670
+ }
6671
+ return void 0;
6672
+ }
6673
+ function pixelTileEstimate(width, height) {
6674
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
6675
+ return PIXEL_IMAGE_FALLBACK_TOKENS;
6676
+ }
6677
+ let sw = width;
6678
+ let sh = height;
6679
+ const shortSide = Math.min(sw, sh);
6680
+ if (shortSide > 0 && shortSide < TILE_SHORT_SIDE) {
6681
+ const s = TILE_SHORT_SIDE / shortSide;
6682
+ sw *= s;
6683
+ sh *= s;
6684
+ }
6685
+ const longSide = Math.max(sw, sh);
6686
+ if (longSide > TILE_MAX_EDGE) {
6687
+ const s = TILE_MAX_EDGE / longSide;
6688
+ sw *= s;
6689
+ sh *= s;
6690
+ }
6691
+ const tiles = Math.ceil(sw / TILE_EDGE) * Math.ceil(sh / TILE_EDGE);
6692
+ return 85 + 170 * tiles;
6693
+ }
6694
+ function estimateImageTokens(meta) {
6695
+ const billing = meta.billing ?? "bytes";
6696
+ if (billing === "bytes") return Math.ceil(meta.base64Length / 4);
6697
+ const w = meta.width;
6698
+ const h = meta.height;
6699
+ if (w && h) return pixelTileEstimate(w, h);
6700
+ if (meta.base64) {
6701
+ const dims = parseImageDimensionsFromBase64(meta.base64);
6702
+ if (dims) return pixelTileEstimate(dims.width, dims.height);
6703
+ }
6704
+ return PIXEL_IMAGE_FALLBACK_TOKENS;
6705
+ }
6706
+ function createHeuristicClassifier(options = {}) {
6707
+ const aspectRatioMin = options.aspectRatioMin ?? 1.4;
6708
+ const aspectRatioMax = options.aspectRatioMax ?? 2.6;
6709
+ const minShortSide = options.minShortSide ?? 720;
6710
+ return {
6711
+ name: "heuristic-v1",
6712
+ isScreenshotLike(meta) {
6713
+ const w = meta.width;
6714
+ const h = meta.height;
6715
+ if (!w || !h || w <= 0 || h <= 0) return false;
6716
+ const shortSide = Math.min(w, h);
6717
+ if (shortSide < minShortSide) return false;
6718
+ const ratio = Math.max(w, h) / shortSide;
6719
+ return ratio >= aspectRatioMin && ratio <= aspectRatioMax;
6720
+ }
6721
+ };
6722
+ }
6723
+ var DEFAULT_SCREENSHOT_CLASSIFIER = createHeuristicClassifier();
6724
+ function decideImageRoute(meta, config, classifier = DEFAULT_SCREENSHOT_CLASSIFIER) {
6725
+ const cfg = resolveImageCompressionConfig(config);
6726
+ const estimatedTokens = estimateImageTokens(meta);
6727
+ if (!cfg.enabled) return { action: "pass", reason: "disabled", estimatedTokens };
6728
+ if (!meta.mediaType.startsWith("image/")) {
6729
+ return { action: "pass", reason: "not-an-image", estimatedTokens };
6730
+ }
6731
+ if (estimatedTokens < cfg.minTokens) {
6732
+ return { action: "pass", reason: "below-min-tokens", estimatedTokens };
6733
+ }
6734
+ if (!classifier.isScreenshotLike(meta)) {
6735
+ return { action: "pass", reason: "not-screenshot-like", estimatedTokens };
6736
+ }
6737
+ return {
6738
+ action: "downsample",
6739
+ reason: "downsample",
6740
+ estimatedTokens,
6741
+ recipe: { maxDimension: cfg.maxDimension, quality: cfg.quality, format: cfg.format }
6742
+ };
6743
+ }
6744
+ var IMAGE_FULL_FAILURE_MARKER = "[image_full FAILED:";
6745
+ function buildImageFullSystemNote(count) {
6746
+ 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.]`;
6747
+ }
6748
+ function parseImageFullInput(input, callId, onWarn) {
6749
+ let obj = null;
6750
+ if (typeof input === "string") {
6751
+ try {
6752
+ const parsed = JSON.parse(input);
6753
+ if (parsed && typeof parsed === "object") obj = parsed;
6754
+ } catch {
6755
+ obj = null;
6756
+ }
6757
+ } else if (input && typeof input === "object") {
6758
+ obj = input;
6759
+ }
6760
+ if (!obj) {
6761
+ onWarn?.(`[acp-image-full-input] rejected: not an object (${typeof input})`);
6762
+ return null;
6763
+ }
6764
+ let ref;
6765
+ for (const key of ["ref", "messageId", "of"]) {
6766
+ const value = obj[key];
6767
+ if (typeof value === "string") {
6768
+ ref = value;
6769
+ break;
6770
+ }
6771
+ }
6772
+ if (typeof ref !== "string") {
6773
+ onWarn?.(
6774
+ `[acp-image-full-input] rejected: need ref (string); keys: ${Object.keys(obj).join(",")}`
6775
+ );
6776
+ return null;
6777
+ }
6778
+ const trimmed = ref.trim();
6779
+ if (!trimmed) {
6780
+ onWarn?.("[acp-image-full-input] rejected: ref is empty");
6781
+ return null;
6782
+ }
6783
+ return { ref: trimmed, ...callId ? { callId } : {} };
6784
+ }
6785
+ function recordImageShrink(state, record) {
6786
+ const bytesSaved = Math.max(0, record.originalBytes - record.shrunkBytes);
6787
+ const tokensSaved = Math.max(0, record.tokensBefore - record.tokensAfter);
6788
+ return {
6789
+ ...state,
6790
+ imageShrinks: [...state.imageShrinks ?? [], record],
6791
+ stats: {
6792
+ ...state.stats,
6793
+ imagesShrunk: (state.stats.imagesShrunk ?? 0) + 1,
6794
+ imageBytesSaved: (state.stats.imageBytesSaved ?? 0) + bytesSaved,
6795
+ imageTokensSaved: (state.stats.imageTokensSaved ?? 0) + tokensSaved
6796
+ }
6797
+ };
6798
+ }
6799
+ function isImageFullRestored(state, ref) {
6800
+ return (state.imageFullRestored ?? []).includes(ref);
6801
+ }
6802
+ function imageShrinksForRef(state, ref) {
6803
+ return (state.imageShrinks ?? []).filter((r) => r.ref === ref);
6804
+ }
6805
+ function applyImageFull(input) {
6806
+ const cfg = resolveImageCompressionConfig(input.config);
6807
+ const ref = input.ref.trim();
6808
+ if (!cfg.enabled) {
6809
+ return {
6810
+ state: input.state,
6811
+ ok: false,
6812
+ resultText: `${IMAGE_FULL_FAILURE_MARKER} image compression is disabled in this session \u2014 no image was ever downscaled]`
6813
+ };
6814
+ }
6815
+ const rawId = rawForRef(input.state.messageRefs, ref);
6816
+ if (!rawId) {
6817
+ return {
6818
+ state: input.state,
6819
+ ok: false,
6820
+ resultText: `${IMAGE_FULL_FAILURE_MARKER} ref ${ref} does not exist in this session]`
6821
+ };
6822
+ }
6823
+ if (isImageFullRestored(input.state, ref)) {
6824
+ return {
6825
+ state: input.state,
6826
+ ok: true,
6827
+ resultText: `already restored (${ref}) \u2014 full resolution stays in effect for the rest of this session.`
6828
+ };
6829
+ }
6830
+ const shrinks = imageShrinksForRef(input.state, ref);
6831
+ if (shrinks.length === 0) {
6832
+ return {
6833
+ state: input.state,
6834
+ ok: false,
6835
+ resultText: `${IMAGE_FULL_FAILURE_MARKER} no downscaled image is recorded for ${ref} \u2014 it was passed through untouched or never carried an image]`
6836
+ };
6837
+ }
6838
+ return {
6839
+ state: {
6840
+ ...input.state,
6841
+ imageFullRestored: [...input.state.imageFullRestored ?? [], ref]
6842
+ },
6843
+ ok: true,
6844
+ resultText: `restored original-resolution image(s) for ${ref} (${shrinks.length} image${shrinks.length === 1 ? "" : "s"}); full resolution applies for the rest of this session.`
6845
+ };
6846
+ }
6847
+ function resetImageFullState(state) {
6848
+ return { ...state, imageFullRestored: [], imageShrinks: [] };
6849
+ }
6850
+
4747
6851
  // src/parse-compress-input.ts
4748
6852
  function parseCompressArgs(input, opts) {
4749
6853
  const callId = opts?.callId;
@@ -5272,7 +7376,8 @@ function rebuildCompressionState(state, messages, config, ports = {}) {
5272
7376
  const core = createCore({ countTokens: ports.countTokens ?? defaultCountTokens });
5273
7377
  const refResult = assignRefs(messages, {
5274
7378
  existing: state.messageRefs,
5275
- nextIndex: highestUsedIndex(state.messageRefs) + 1
7379
+ nextIndex: highestUsedIndex(state.messageRefs) + 1,
7380
+ shouldSkip: (m) => m.id.startsWith(RETRIEVED_ID_PREFIX)
5276
7381
  });
5277
7382
  let working = { ...state, messageRefs: refResult.map };
5278
7383
  const invocations = collectCompressInvocations(messages);
@@ -5636,6 +7741,129 @@ function makePreview(text, query, len) {
5636
7741
  const suffix = end < text.length ? "\u2026" : "";
5637
7742
  return prefix + clampWindow(text, start, end).trim() + suffix;
5638
7743
  }
7744
+
7745
+ // src/output-steering.ts
7746
+ function classifyTurn(messages) {
7747
+ if (messages.length === 0) return "unknown";
7748
+ const last = messages[messages.length - 1];
7749
+ if (!last || last.role !== "user") return "unknown";
7750
+ if (typeof last.text === "string")
7751
+ return last.text.trim() ? "new_user_ask" : "unknown";
7752
+ const blocks = last.blocks;
7753
+ if (!Array.isArray(blocks) || blocks.length === 0) return "unknown";
7754
+ let sawToolResult = false;
7755
+ let sawError = false;
7756
+ for (const b of blocks) {
7757
+ if (!b) return "unknown";
7758
+ switch (b.kind) {
7759
+ case "tool_result":
7760
+ sawToolResult = true;
7761
+ if (b.isError === true) sawError = true;
7762
+ break;
7763
+ case "text":
7764
+ case "image":
7765
+ case "document":
7766
+ return "new_user_ask";
7767
+ default:
7768
+ return "unknown";
7769
+ }
7770
+ }
7771
+ if (sawError) return "error_continuation";
7772
+ if (sawToolResult) return "mechanical_continuation";
7773
+ return "unknown";
7774
+ }
7775
+ var MIN_VERBOSITY_LEVEL = 0;
7776
+ var MAX_VERBOSITY_LEVEL = 4;
7777
+ var DEFAULT_VERBOSITY_LEVEL = 2;
7778
+ var VERBOSITY_LEVELS = {
7779
+ 1: "Skip preamble and postamble. Do not announce what you are about to do or recap what you just did; start with the substance.",
7780
+ 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.",
7781
+ 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.",
7782
+ 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."
7783
+ };
7784
+ function verbosityDirective(level) {
7785
+ if (!Number.isInteger(level)) return null;
7786
+ return VERBOSITY_LEVELS[level] ?? null;
7787
+ }
7788
+ var DEFAULT_STEERING_SENTINEL = "<acp_output_steering>";
7789
+ function renderSteeringBlock(level, sentinel = DEFAULT_STEERING_SENTINEL) {
7790
+ const text = verbosityDirective(level);
7791
+ if (text === null) return null;
7792
+ return `${sentinel}
7793
+ ${text}
7794
+ </${sentinel.slice(1)}`;
7795
+ }
7796
+ function applySteeringToPrompt(existing, block, sentinel = DEFAULT_STEERING_SENTINEL) {
7797
+ const suffix = `</${sentinel.slice(1)}`;
7798
+ const start = existing.indexOf(sentinel);
7799
+ if (start >= 0) {
7800
+ const found = existing.indexOf(suffix, start);
7801
+ const end = found < 0 ? existing.length : found + suffix.length;
7802
+ const prefix = existing.slice(0, start).replace(/\s+$/, "");
7803
+ const tail = existing.slice(end).replace(/^\n+/, "");
7804
+ const parts = [prefix, block, tail].filter((p) => p.length > 0);
7805
+ const updated2 = parts.join("\n\n");
7806
+ return { updated: updated2, changed: updated2 !== existing };
7807
+ }
7808
+ const trimmed = existing.trim();
7809
+ const updated = trimmed.length > 0 ? `${existing.replace(/\s+$/, "")}
7810
+
7811
+ ${block}` : block;
7812
+ return { updated, changed: updated !== existing };
7813
+ }
7814
+ var EFFORT_LADDER = [
7815
+ "minimal",
7816
+ "low",
7817
+ "medium",
7818
+ "high",
7819
+ "xhigh"
7820
+ ];
7821
+ function clampEffortToFloor(current, floor = "low") {
7822
+ if (typeof current !== "string") return null;
7823
+ const ci = EFFORT_LADDER.indexOf(current);
7824
+ if (ci === -1) return null;
7825
+ const fi = EFFORT_LADDER.indexOf(floor);
7826
+ return ci > fi ? floor : null;
7827
+ }
7828
+ var DEFAULT_OUTPUT_STEERING_CONFIG = {
7829
+ enabled: false,
7830
+ verbosityLevel: DEFAULT_VERBOSITY_LEVEL,
7831
+ effortRouting: true
7832
+ };
7833
+ function resolveVerbosityLevel(v) {
7834
+ if (typeof v === "number" && Number.isInteger(v) && v >= MIN_VERBOSITY_LEVEL && v <= MAX_VERBOSITY_LEVEL) {
7835
+ return { level: v };
7836
+ }
7837
+ const level = DEFAULT_OUTPUT_STEERING_CONFIG.verbosityLevel;
7838
+ if (v === void 0) return { level };
7839
+ return {
7840
+ level,
7841
+ warning: `[config] outputSteering.verbosityLevel must be an integer ${MIN_VERBOSITY_LEVEL}-${MAX_VERBOSITY_LEVEL}; got ${JSON.stringify(v)} \u2014 falling back to ${level}`
7842
+ };
7843
+ }
7844
+ function resolveOutputSteeringConfig(v) {
7845
+ if (!v || typeof v !== "object" || Array.isArray(v)) {
7846
+ return { config: DEFAULT_OUTPUT_STEERING_CONFIG, warnings: [] };
7847
+ }
7848
+ const obj = v;
7849
+ const lvl = resolveVerbosityLevel(obj.verbosityLevel);
7850
+ return {
7851
+ config: {
7852
+ enabled: obj.enabled === true,
7853
+ verbosityLevel: lvl.level,
7854
+ effortRouting: obj.effortRouting !== false
7855
+ },
7856
+ warnings: lvl.warning ? [lvl.warning] : []
7857
+ };
7858
+ }
7859
+ function decideOutputSteering(messages, config = DEFAULT_OUTPUT_STEERING_CONFIG) {
7860
+ const turnKind = classifyTurn(messages);
7861
+ return {
7862
+ turnKind,
7863
+ verbosityLevel: config.enabled ? config.verbosityLevel : 0,
7864
+ lowerEffort: config.enabled && config.effortRouting && turnKind === "mechanical_continuation"
7865
+ };
7866
+ }
5639
7867
  export {
5640
7868
  ABSORB_PROMPT_MARKER,
5641
7869
  ABSORB_TOOL,
@@ -5684,10 +7912,33 @@ export {
5684
7912
  DECOMPRESS_TOOL_RESPONSES,
5685
7913
  DEFAULT_ABSORB_CONFIG,
5686
7914
  DEFAULT_ALGORITHM,
7915
+ DEFAULT_CCR_CONFIG,
7916
+ DEFAULT_CRUSH_CONFIG,
7917
+ DEFAULT_IMAGE_COMPRESSION_CONFIG,
7918
+ DEFAULT_OUTPUT_STEERING_CONFIG,
5687
7919
  DEFAULT_ROLE_WEIGHTS,
5688
7920
  DEFAULT_RULE_LIMITS,
7921
+ DEFAULT_SCREENSHOT_CLASSIFIER,
7922
+ DEFAULT_STEERING_SENTINEL,
7923
+ DEFAULT_VERBOSITY_LEVEL,
7924
+ EFFORT_LADDER,
5689
7925
  HOW_TO_COMPRESS_RULES,
7926
+ IMAGE_FULL_FAILURE_MARKER,
7927
+ IMAGE_FULL_TOOL,
7928
+ IMAGE_FULL_TOOL_DESCRIPTION,
7929
+ IMAGE_FULL_TOOL_NAME,
7930
+ IMAGE_FULL_TOOL_OPENAI,
7931
+ IMAGE_FULL_TOOL_RESPONSES,
5690
7932
  LEAN_HOW_TO_COMPRESS,
7933
+ MAX_VERBOSITY_LEVEL,
7934
+ MIN_VERBOSITY_LEVEL,
7935
+ PIXEL_IMAGE_FALLBACK_TOKENS,
7936
+ RETRIEVED_ID_PREFIX,
7937
+ RETRIEVE_TOOL,
7938
+ RETRIEVE_TOOL_DESCRIPTION,
7939
+ RETRIEVE_TOOL_NAME,
7940
+ RETRIEVE_TOOL_OPENAI,
7941
+ RETRIEVE_TOOL_RESPONSES,
5691
7942
  RULES_USAGE_PROMPT,
5692
7943
  RULE_TOOL_NAME,
5693
7944
  SEARCH_CONTEXT_TOOL,
@@ -5695,9 +7946,11 @@ export {
5695
7946
  SEARCH_CONTEXT_TOOL_NAME,
5696
7947
  SEARCH_CONTEXT_TOOL_OPENAI,
5697
7948
  SEARCH_CONTEXT_TOOL_RESPONSES,
7949
+ STORED_PLACEHOLDER_MARKER,
5698
7950
  SUMMARY_HEADER,
5699
7951
  TIER2_DISTILL_RULES,
5700
7952
  TIER3_CONDENSE_RULES,
7953
+ VERBOSITY_LEVELS,
5701
7954
  VIABLE_RANGE_MIN_TOKENS,
5702
7955
  activeBlockSpans,
5703
7956
  activeBlocks,
@@ -5709,8 +7962,12 @@ export {
5709
7962
  appendAbsorbPrompts,
5710
7963
  applyAbsorb,
5711
7964
  applyAcpToolOverrides,
7965
+ applyCrushToMessages,
7966
+ applyImageFull,
5712
7967
  applyMessageFilters,
7968
+ applyRetrieve,
5713
7969
  applySectionOverrides,
7970
+ applySteeringToPrompt,
5714
7971
  assignRefs,
5715
7972
  blockById,
5716
7973
  blockDocs,
@@ -5721,26 +7978,41 @@ export {
5721
7978
  buildCompressHybridSystemPrompt,
5722
7979
  buildCompressSystemPrompt,
5723
7980
  buildCompressTextSystemPrompt,
7981
+ buildImageFullSystemNote,
5724
7982
  buildRecap,
5725
7983
  buildRestoredContentPreview,
7984
+ buildRetrievalInjection,
5726
7985
  buildStatusReport,
7986
+ buildStoredPlaceholder,
5727
7987
  builtinSource,
7988
+ ccrStoreNode,
7989
+ clampEffortToFloor,
7990
+ classifyCrushText,
7991
+ classifyKind,
7992
+ classifyTurn,
5728
7993
  clearDocFeatures,
5729
7994
  clearMessageFilters,
5730
7995
  clearRules,
5731
7996
  cloneWithDescriptions,
7997
+ codeTrimPlugin,
5732
7998
  collectBlockContent,
5733
7999
  collectLatestProtected,
5734
8000
  computeFoldEconomics,
8001
+ contentStoreStats,
5735
8002
  countMessageTokens,
5736
8003
  coveredMessageIds,
5737
8004
  createBpeTokenizer,
8005
+ createContentStore,
5738
8006
  createCore,
5739
8007
  createDirPackSource,
8008
+ createHeuristicClassifier,
5740
8009
  createInitialState,
5741
8010
  createPackResolver,
5742
8011
  createRenderRefsNode,
8012
+ crushText,
5743
8013
  deactivateBlock,
8014
+ decideImageRoute,
8015
+ decideOutputSteering,
5744
8016
  decomposeSample,
5745
8017
  defaultConfig,
5746
8018
  defaultCountTokens,
@@ -5750,7 +8022,10 @@ export {
5750
8022
  docCacheInfo,
5751
8023
  docFeatures,
5752
8024
  emptyRefMap,
8025
+ estimateImageTokens,
5753
8026
  estimateTokensFast,
8027
+ evaluateToolResult,
8028
+ extractCommand,
5754
8029
  findActiveAncestor,
5755
8030
  findBlocksOverlappingMessages,
5756
8031
  formatCacheReport,
@@ -5760,35 +8035,53 @@ export {
5760
8035
  formatRulesList,
5761
8036
  getMessageFilter,
5762
8037
  getSearchAlgorithm,
8038
+ hasMediaPayload,
8039
+ hasStoredRef,
8040
+ hashContent,
5763
8041
  hideAbsorbedMessages,
5764
8042
  hideConsumedCompressCalls,
5765
8043
  highestActiveTier,
5766
8044
  highestUsedIndex,
8045
+ imageShrinksForRef,
5767
8046
  indexToRef,
5768
8047
  isAbsorbCandidate,
8048
+ isImageFullRestored,
5769
8049
  isMessageLatestProtected,
5770
8050
  isMessageProtected,
5771
8051
  isRenderedSummaryMessage,
8052
+ isRetrievedMessage,
8053
+ isStoredPlaceholderText,
5772
8054
  isSummaryMessageId,
5773
8055
  isValidPackName,
8056
+ jsonFoldPlugin,
5774
8057
  leanPack,
8058
+ listCrushPlugins,
5775
8059
  listMessageFilters,
5776
8060
  listRules,
5777
8061
  listSearchAlgorithms,
8062
+ logSelectPlugin,
5778
8063
  makeIO,
5779
8064
  matchSession,
5780
8065
  matchToolPattern,
5781
8066
  messageDocs,
8067
+ normalizeHead,
8068
+ noteRetrieval,
5782
8069
  parseAbsorbInput,
5783
8070
  parseBlockIdArg,
5784
8071
  parseBoundary,
5785
8072
  parseCompressArgs,
5786
8073
  parseCompressInput,
8074
+ parseImageDimensions,
8075
+ parseImageDimensionsFromBase64,
8076
+ parseImageFullInput,
8077
+ pixelTileEstimate,
5787
8078
  prune,
5788
8079
  rawForRef,
5789
8080
  rebuildCompressionState,
8081
+ recordImageShrink,
5790
8082
  refForRaw,
5791
8083
  refToIndex,
8084
+ registerCrushPlugin,
5792
8085
  registerMessageFilter,
5793
8086
  registerSearchAlgorithm,
5794
8087
  removeRule,
@@ -5796,23 +8089,38 @@ export {
5796
8089
  renderMessage2 as renderMessage,
5797
8090
  renderNudgeText,
5798
8091
  renderRefsNode,
8092
+ renderSteeringBlock,
5799
8093
  renderVisibleRefs,
8094
+ resetCrushPlugins,
8095
+ resetImageFullState,
5800
8096
  resolveAbsorbConfig,
5801
8097
  resolveBlockSpan,
5802
8098
  resolveBoundaries,
8099
+ resolveCcrConfig,
8100
+ resolveCrushConfig,
8101
+ resolveImageCompressionConfig,
8102
+ resolveOutputSteeringConfig,
5803
8103
  resolvePrompts,
5804
8104
  resolveRuleLimits,
5805
8105
  resolveTransformChannel,
8106
+ resolveVerbosityLevel,
8107
+ retrieveByRef,
8108
+ retrievedMessageId,
5806
8109
  runPipeline,
5807
8110
  sanitizePackSurface,
5808
8111
  searchBlocks,
5809
8112
  searchBlocksAsync,
5810
8113
  setDocCacheCap,
8114
+ storeCoveredOriginals,
8115
+ storeLargeResults,
8116
+ storeOriginal,
5811
8117
  summarizeFoldEconomics,
5812
8118
  summaryMessageId,
5813
8119
  syncBlocks,
5814
8120
  truncateLargeToolOutputs,
8121
+ unregisterCrushPlugin,
5815
8122
  validateConfig,
8123
+ verbosityDirective,
5816
8124
  viableRanges,
5817
8125
  visibleBlockAnchor
5818
8126
  };