mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-09-28 06:45:17 +08:00
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:
committed by
kshitij
co-authored by
ahisblessed
salch-cred
parent
8bd34fbd5a
commit
40cb28e60c
+16
-24
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user