fix(agent): detect copied context-compression artifacts

(cherry picked from commit 09757f3af1c1b3eec1ccde560089448ffa704c92)
This commit is contained in:
Kevin Rajan
2026-09-23 16:43:58 -05:00
committed by kshitij
parent 8def746896
commit 459b3ee6cd

View File

@@ -19,6 +19,7 @@ from typing import Any, Dict, List, Optional
from agent.message_metadata import stamp_message_timestamp
from agent.tool_result_classification import (
FILE_MUTATING_TOOL_NAMES as _FILE_MUTATING_TOOLS,
tool_may_have_side_effect,
)
from tools.threat_patterns import scan_for_threats
@@ -67,6 +68,54 @@ _DESTRUCTIVE_PATTERNS = re.compile(
# Output redirects that overwrite files (> but not >>)
_REDIRECT_OVERWRITE = re.compile(r'[^>]>[^>]|^>[^>]')
# Legacy compressor builds ended long string leaves with these ambiguous tails.
# Keep the compatibility guard narrow: real historical rewrites had a 200-char head.
_LEGACY_CONTEXT_PRUNED_ARG_TAILS = ("...[truncated]", "…[truncated]")
def _context_pruned_argument_paths(tool_name: str, args: Any) -> list[str]:
"""Paths whose values contain model-visible context-compression artifacts.
The compressor's current marker carries numeric omitted/total counts. Match
that rendered shape rather than the prefix alone so Hermes can still edit
source/docs that mention the compression marker constant or its template.
Unknown/plugin/MCP tools stay effect-capable by default; known read-only
tools may inspect or quote compressed history.
"""
if not tool_may_have_side_effect(tool_name):
return []
# Late import keeps this helper from making the dispatch module own a second
# copy of the compressor sentinel (and avoids a heavy import at CLI startup).
from agent.context_compressor import _COMPRESSION_MARKER_PREFIX
marker_re = re.compile(
re.escape(_COMPRESSION_MARKER_PREFIX)
+ r"\s+\d[\d,]* of \d[\d,]* chars omitted here by Hermes's context compressor\."
)
found: list[str] = []
def _walk(value: Any, path: str) -> None:
if isinstance(value, str):
stripped = value.rstrip()
if marker_re.search(value) or (
len(stripped) > 200 and stripped.endswith(_LEGACY_CONTEXT_PRUNED_ARG_TAILS)
):
found.append(path)
return
if isinstance(value, dict):
for key, child in value.items():
key_text = str(key)
child_path = f"{path}.{key_text}" if key_text.isidentifier() else f"{path}[{key_text!r}]"
_walk(child, child_path)
return
if isinstance(value, (list, tuple)):
for index, child in enumerate(value):
_walk(child, f"{path}[{index}]")
_walk(args, "$")
return found
def _is_destructive_command(cmd: str) -> bool:
"""Heuristic: does this terminal command look like it modifies/deletes files?"""
@@ -543,7 +592,8 @@ def _maybe_wrap_untrusted(name: str, content: Any) -> Any:
__all__ = [
"_NEVER_PARALLEL_TOOLS", "_PARALLEL_SAFE_TOOLS", "_PATH_SCOPED_TOOLS", "_PATH_SCOPED_READERS",
"_PATH_SCOPED_WRITERS", "_DESTRUCTIVE_PATTERNS", "_REDIRECT_OVERWRITE", "_is_destructive_command",
"_PATH_SCOPED_WRITERS", "_DESTRUCTIVE_PATTERNS", "_REDIRECT_OVERWRITE", "_context_pruned_argument_paths",
"_is_destructive_command",
"_plan_tool_batch_segments", "_should_parallelize_tool_batch", "_canonical_path",
"_extract_parallel_scope_path", "_extract_parallel_scope_paths", "_paths_overlap",
"_is_multimodal_tool_result", "_multimodal_text_summary", "_append_subdir_hint_to_multimodal",