fix(agent): detect copied context-compression artifacts
(cherry picked from commit 09757f3af1c1b3eec1ccde560089448ffa704c92)
This commit is contained in:
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user