fix(agent,slack): close remaining compressor elision gaps from review

- salvage summary cap and _bound_oversized_record still composed bare
  truncation idioms in model-visible text; route them through elide /
  elide_middle so a copied marker is guard-visible.
- the active-task line repr()'d the elided text, escaping the marker's
  apostrophe when the user text held both quote kinds and hiding it from
  the guard; elide after repr instead (text within the cap stays whole).
- a leftover budget smaller than the marker produced a content-free,
  over-budget marker line in _build_verbatim_user_section and the Slack
  nested-attachment path; skip the item instead.
- _build_verbatim_user_section elided twice, reporting the wrong total;
  one elide at min(cap, remaining).
- drop redundant len() pre-checks before elide() and name verification
  stop's repeated 1200.

Co-authored-by: ahisblessed <ahisblessed@users.noreply.github.com>
Co-authored-by: salch-cred <salch-cred@users.noreply.github.com>
This commit is contained in:
kshitijk4poor
2026-09-26 23:49:25 +05:30
committed by kshitij
co-authored by ahisblessed salch-cred
parent 8bd34fbd5a
commit 40cb28e60c
4 changed files with 22 additions and 30 deletions
+16 -24
View File
@@ -16,8 +16,10 @@ from typing import Any, Dict, List, Optional, Sequence, Tuple
from agent.image_eviction_policy import outbound_image_retire_count
from agent.compression_marker import (
ELISION_MARKER_MAX_LEN,
_COMPRESSION_MARKER_PREFIX,
_COMPRESSION_MARKER_TEMPLATE,
_elision_marker,
elide,
elide_middle,
)
@@ -610,8 +612,7 @@ def salvage_grown_transcript(
and len(content) > _SALVAGE_SUMMARY_MAX_CHARS
and _looks_like_compaction_summary(msg, content)
):
msg["content"] = (content[:_SALVAGE_SUMMARY_MAX_CHARS].rstrip()
+ "\n…[summary truncated so compaction can shrink]\n\n" + _SUMMARY_END_MARKER)
msg["content"] = elide(content, _SALVAGE_SUMMARY_MAX_CHARS) + "\n\n" + _SUMMARY_END_MARKER
_prune_stale_reasoning_replay(out)
if estimate_messages_tokens_rough(out) >= budget:
_salvage_reduce_todo_snapshot(out)
@@ -1008,10 +1009,9 @@ def _build_verbatim_user_section(turns: List[Dict[str, Any]]) -> str:
if remaining <= 0:
break
text = content.strip()
if len(text) > _LEAN_USER_MESSAGE_MAX_CHARS:
text = elide(text, _LEAN_USER_MESSAGE_MAX_CHARS)
if len(text) > remaining:
text = elide(text, remaining)
if len(text) > remaining and remaining <= ELISION_MARKER_MAX_LEN:
break # no room for marker + content: a marker-only quote would overshoot the budget
text = elide(text, min(_LEAN_USER_MESSAGE_MAX_CHARS, remaining))
collected.append("> " + text.replace("\n", "\n> "))
used += len(text)
if not collected:
@@ -1271,8 +1271,7 @@ def _compact_fallback_turn(value: Any) -> str:
text = _redact_compaction_text(_content_text_for_contains(value))
text = re.sub(r"\bgh[pousr]_[A-Za-z0-9_]{8,}\b", "[REDACTED]", text)
text = re.sub(r"\s+", " ", text).strip()
if len(text) > _FALLBACK_TURN_MAX_CHARS:
text = elide(text, _FALLBACK_TURN_MAX_CHARS)
text = elide(text, _FALLBACK_TURN_MAX_CHARS)
return re.sub(r"\bgh[pousr]_[A-Za-z0-9_.-]+", "[REDACTED]", text)
@@ -1800,8 +1799,7 @@ def _sum_clarify(name, args, content, content_len, line_count):
# Escape lone UTF-16 surrogates so the message stays UTF-8/SQLite safe.
serialized = json.dumps(response, ensure_ascii=False).encode("utf-8", errors="backslashreplace")
summary = response_prefix + serialized.decode("utf-8")
if len(summary) > max_summary_chars:
summary = elide(summary, max_summary_chars)
summary = elide(summary, max_summary_chars)
return summary
return "[clarify] asked user a question"
@@ -3555,8 +3553,7 @@ class ContextCompressor(SummaryDispatchMixin, MicroCompactionMixin, ContextEngin
previous_summary_note = ""
if self._previous_summary:
previous_summary = redact_sensitive_text(self._previous_summary.strip())
if len(previous_summary) > _FALLBACK_PREVIOUS_SUMMARY_MAX_CHARS:
previous_summary = elide(previous_summary, _FALLBACK_PREVIOUS_SUMMARY_MAX_CHARS)
previous_summary = elide(previous_summary, _FALLBACK_PREVIOUS_SUMMARY_MAX_CHARS)
previous_summary_note = (
"\n\n## Previous Summary Snapshot\n"
f"{previous_summary}\n\n"
@@ -3682,17 +3679,11 @@ Summary generation was unavailable, so this is a best-effort deterministic fallb
"""Bound an oversized record with an explicit intra-record truncation marker."""
if len(record) <= limit:
return record
marker_template = "\n...[record truncated: {elided:,} chars elided — recover via session_search]...\n"
marker_reserve = len(marker_template.format(elided=len(record)))
marker_reserve = len(_elision_marker(omitted=len(record), total=len(record)))
if limit <= marker_reserve:
return record[:limit]
remaining = limit - marker_reserve
head_len = remaining // 2
tail_len = remaining - head_len
head = record[:head_len].rstrip("\n")
tail = record[-tail_len:].lstrip("\n")
elided = len(record) - len(head) - len(tail)
return head + marker_template.format(elided=elided) + tail
head_len = (limit - marker_reserve) // 2
return elide_middle(record, head_len, limit - marker_reserve - head_len)
def _record_summary_input_coverage(self, coverage: Dict[str, int]) -> None:
"""Expose lean sampling coverage without including transcript content in telemetry."""
@@ -4433,10 +4424,11 @@ Write only the summary body. Do not include any preamble or prefix."""
if not text:
continue
text = re.sub(r"\s+", " ", text)
if len(text) > _ACTIVE_TASK_MAX_CHARS:
text = elide(text, _ACTIVE_TASK_MAX_CHARS)
# Elide AFTER repr: repr would escape the marker's "Hermes's" and hide a copy from the
# guard. Text within the cap stays whole (the split-turn path relies on that).
text = repr(text) if len(text) <= _ACTIVE_TASK_MAX_CHARS else elide(repr(text), _ACTIVE_TASK_MAX_CHARS)
return (
f"User asked (deterministic, from compacted turns): {text!r}\n"
f"User asked (deterministic, from compacted turns): {text}\n"
"Historical only; newer protected-tail messages after this summary win."
)
return None
+1 -3
View File
@@ -82,9 +82,7 @@ def run_inline_shell(command: str, cwd: Path | None, timeout: int) -> str:
# rc!=0 with no output at all is indistinguishable from a legit empty result; it is the
# "interpreter never ran the command" signature (WSL stub without a distro) — say so.
return f"[inline-shell exit {completed.returncode} with no output: {command}]"
if len(output) > _INLINE_SHELL_MAX_OUTPUT:
output = elide(output, _INLINE_SHELL_MAX_OUTPUT)
return output
return elide(output, _INLINE_SHELL_MAX_OUTPUT)
def expand_inline_shell(content: str, skill_dir: Path | None, timeout: int) -> str:
+2 -2
View File
@@ -13,6 +13,7 @@ from agent.compression_marker import elide
_MAX_CHANGED_PATHS_IN_NUDGE = 8
_MAX_STATUS_SUMMARY_CHARS = 1200
# Prose/data extensions and extension-less prose filenames (case-insensitive) with
# no verifiable runtime behavior: a turn touching ONLY these suppresses the nudge
@@ -151,8 +152,7 @@ def _status_detail(status: dict[str, Any]) -> str:
if command:
parts.append(f"last command `{command}`")
if summary:
if len(summary) > 1200:
summary = elide(summary, 1200)
summary = elide(summary, _MAX_STATUS_SUMMARY_CHARS)
parts.append(f"last output:\n{summary}")
return "\n".join(parts)
+3 -1
View File
@@ -33,7 +33,7 @@ from pathlib import Path as _Path
sys.path.insert(0, str(_Path(__file__).resolve().parents[3]))
from agent.compression_marker import elide
from agent.compression_marker import ELISION_MARKER_MAX_LEN, elide
from agent.retry_utils import parse_retry_after_seconds
from agent.secret_scope import get_secret
from gateway.config import Platform, PlatformConfig
@@ -4267,6 +4267,8 @@ class SlackAdapter(BasePlatformAdapter):
nested_text = ""
if blocks_budget > 0:
nested_text = _extract_text_from_slack_blocks(att.get("blocks") or [])
if len(nested_text) > blocks_budget and blocks_budget <= ELISION_MARKER_MAX_LEN:
nested_text = "" # leftover budget cannot hold marker + content: skip, don't overshoot
nested_text = elide(nested_text, blocks_budget)
if nested_text and nested_text not in body:
blocks_budget -= len(nested_text)