fix(slack,trajectory): mint the compression marker for agent-facing truncations
The Slack Block Kit payload dump and the nested-attachment text budget are both fed to the agent, and trajectory_compressor's summarizer input becomes training data; all three still used the imitable bare "... [truncated]" idiom. Route them through elide()/elide_middle() with module-level imports and extend the no-idiom invariant to scan plugins/platforms/slack and trajectory_compressor.py. Co-authored-by: salch-cred <salch-cred@users.noreply.github.com>
This commit is contained in:
@@ -33,6 +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.retry_utils import parse_retry_after_seconds
|
||||
from agent.secret_scope import get_secret
|
||||
from gateway.config import Platform, PlatformConfig
|
||||
@@ -764,8 +765,7 @@ def _serialize_slack_blocks_for_agent(blocks: list, max_chars: int = 6000) -> st
|
||||
payload = json.dumps(_sanitize(inspectable), ensure_ascii=False, indent=2)
|
||||
except Exception:
|
||||
payload = repr(inspectable)
|
||||
if len(payload) > max_chars:
|
||||
payload = payload[: max_chars - 18].rstrip() + "\n... [truncated]"
|
||||
payload = elide(payload, max_chars)
|
||||
return f"[Slack Block Kit payload for this message]\n```json\n{payload}\n```"
|
||||
|
||||
|
||||
@@ -4267,8 +4267,7 @@ 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:
|
||||
nested_text = nested_text[:blocks_budget].rstrip() + "\n... [truncated]"
|
||||
nested_text = elide(nested_text, blocks_budget)
|
||||
if nested_text and nested_text not in body:
|
||||
blocks_budget -= len(nested_text)
|
||||
body = f"{body}\n{nested_text}".strip() if body else nested_text
|
||||
|
||||
@@ -19,7 +19,14 @@ from agent.compression_marker import (
|
||||
elide_middle,
|
||||
)
|
||||
|
||||
AGENT_ROOT = pathlib.Path(__file__).resolve().parents[2] / "agent"
|
||||
REPO_ROOT = pathlib.Path(__file__).resolve().parents[2]
|
||||
AGENT_ROOT = REPO_ROOT / "agent"
|
||||
# Agent-facing renderers outside agent/: Slack payload dumps and trajectory training data.
|
||||
SCANNED_PATHS = (
|
||||
*sorted(AGENT_ROOT.rglob("*.py")),
|
||||
*sorted((REPO_ROOT / "plugins" / "platforms" / "slack").rglob("*.py")),
|
||||
REPO_ROOT / "trajectory_compressor.py",
|
||||
)
|
||||
# Any "...[<words> truncated]" variant, not just the bare one (e.g. "...[fallback summary truncated]").
|
||||
IMITABLE_MARKER_RE = re.compile(r"(?:\.\.\.|…)\s?\[[^\]\n]*truncated\]")
|
||||
|
||||
@@ -68,12 +75,12 @@ def test_no_imitable_truncation_marker_in_agent_strings():
|
||||
helpers now, so any such literal in a string token is a new imitation surface.
|
||||
"""
|
||||
offenders = []
|
||||
for path in sorted(AGENT_ROOT.rglob("*.py")):
|
||||
for path in SCANNED_PATHS:
|
||||
with tokenize.open(str(path)) as fh:
|
||||
for tok in tokenize.generate_tokens(fh.readline):
|
||||
if tok.type != tokenize.STRING:
|
||||
continue
|
||||
for match in IMITABLE_MARKER_RE.finditer(tok.string):
|
||||
rel = path.relative_to(AGENT_ROOT.parent)
|
||||
rel = path.relative_to(REPO_ROOT)
|
||||
offenders.append(f"{rel}:{tok.start[0]}: {match.group()}")
|
||||
assert not offenders, "imitable truncation markers in agent/ strings:\n" + "\n".join(offenders)
|
||||
|
||||
@@ -6,6 +6,7 @@ each carrying its own blocks, so without a shared ceiling the projection grows l
|
||||
attachment count while the top-level ``blocks`` path is capped once.
|
||||
"""
|
||||
|
||||
from agent.compression_marker import _COMPRESSION_MARKER_RE
|
||||
from plugins.platforms.slack.adapter import SlackAdapter
|
||||
|
||||
|
||||
@@ -27,7 +28,7 @@ def test_nested_block_text_stops_growing_with_attachment_count():
|
||||
per_attachment_growth = (sizes[20] - sizes[5]) / 15
|
||||
assert per_attachment_growth < 200, sizes
|
||||
assert sizes[20] < 3 * sizes[1], sizes
|
||||
assert "[truncated]" in SlackAdapter._append_link_unfurls("intro", [_attachment(i) for i in range(20)])
|
||||
assert _COMPRESSION_MARKER_RE.search(SlackAdapter._append_link_unfurls("intro", [_attachment(i) for i in range(20)]))
|
||||
|
||||
|
||||
def test_first_attachment_keeps_its_body_and_headers_survive_exhaustion():
|
||||
|
||||
@@ -8,6 +8,8 @@ from unittest.mock import AsyncMock, patch, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from agent.compression_marker import _COMPRESSION_MARKER_RE
|
||||
|
||||
from trajectory_compressor import (
|
||||
CompressionConfig,
|
||||
TrajectoryMetrics,
|
||||
@@ -268,7 +270,7 @@ class TestExtractTurnContent:
|
||||
{"from": "tool", "value": "x" * 5000},
|
||||
]
|
||||
content = tc._extract_turn_content_for_summary(trajectory, 0, 1)
|
||||
assert "...[truncated]..." in content
|
||||
assert _COMPRESSION_MARKER_RE.search(content)
|
||||
assert len(content) < 5000
|
||||
|
||||
def test_empty_range(self):
|
||||
|
||||
@@ -30,6 +30,7 @@ import fire
|
||||
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn, TimeElapsedColumn, TimeRemainingColumn
|
||||
from rich.console import Console
|
||||
from hermes_constants import OPENROUTER_BASE_URL, get_hermes_home
|
||||
from agent.compression_marker import elide_middle
|
||||
from agent.retry_utils import jittered_backoff
|
||||
from hermes_cli.env_loader import load_hermes_dotenv
|
||||
|
||||
@@ -401,7 +402,7 @@ class TrajectoryCompressor:
|
||||
turn = trajectory[i]
|
||||
value = turn.get("value", "")
|
||||
if len(value) > 3000:
|
||||
value = value[:1500] + "\n...[truncated]...\n" + value[-500:]
|
||||
value = elide_middle(value, 1500, 500)
|
||||
parts.append(f"[Turn {i} - {turn.get('from', 'unknown').upper()}]:\n{value}")
|
||||
return "\n\n".join(parts)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user