diff --git a/agent/conversation_compression.py b/agent/conversation_compression.py index 4827c28d1d..3a145f1dc2 100644 --- a/agent/conversation_compression.py +++ b/agent/conversation_compression.py @@ -2428,17 +2428,25 @@ def _strip_stale_todo_snapshot(content: Any) -> Any: return content return content[:idx].rstrip() if isinstance(content, list): - return [ - part - for part in content - if not ( - isinstance(part, dict) - and part.get("type") == "text" - and str(part.get("text") or "") - .lstrip() - .startswith(TODO_INJECTION_HEADER) - ) - ] + cleaned = [] + for part in content: + if not isinstance(part, dict): + cleaned.append(part) + continue + if part.get("type") == "text": + text = str(part.get("text") or "") + idx = text.find(TODO_INJECTION_HEADER) + if idx != -1: + stripped = text[:idx].rstrip() + if stripped: + p = dict(part) + p["text"] = stripped + cleaned.append(p) + else: + cleaned.append(part) + else: + cleaned.append(part) + return cleaned return content diff --git a/tests/agent/test_skill_todo_retention_parity.py b/tests/agent/test_skill_todo_retention_parity.py index 30822cdf5f..ca48b91320 100644 --- a/tests/agent/test_skill_todo_retention_parity.py +++ b/tests/agent/test_skill_todo_retention_parity.py @@ -269,6 +269,25 @@ class TestNoticeStripLifecycle: assert stripped == "real user words" assert _PRUNED_SKILL_RELOAD_NOTICE_HEADER not in stripped + def test_strip_removes_snapshot_from_list_content(self): + content = [ + { + "type": "text", + "text": ( + "real user words\n\n" + + TODO_INJECTION_HEADER + + "\n- [ ] t1. old task (pending)\n\n" + + _PRUNED_SKILL_RELOAD_NOTICE_HEADER + + "\nreload skill_view(name='old-skill') first." + ), + }, + {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}, + ] + stripped = _strip_stale_todo_snapshot(content) + assert len(stripped) == 2 + assert stripped[0] == {"type": "text", "text": "real user words"} + assert stripped[1] == {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}} + def test_repeated_boundaries_keep_single_notice(self, tmp_path): """Second compaction with a tail already carrying snapshot+notice refreshes in place instead of stacking duplicates (#26981 parity)."""