0.167.39

Added

  • Семантические дайджесты tool-выводов вместо слепой обрезки head+tail ( — P2). Прежде FastTrimToolResults резал старые tool-результаты вслепую по байтам (~2/3 голова + ~1/3 хвост): середина grep-списка терялась без счётчиков, git diff рвался посередине ханка, упавшие кейсы тестов выпадали из середины exec-вывода — модель получала обрубок и перезапускала инструмент целиком (ровно тот цикл, который #265 пытался разорвать). Введён DigestToolResult (internal/agent/tool_result_digest.go): реестр дайджестеров по имени инструмента, подключённый в FastTrimToolResultsMessage уже есть ToolName). Дайджестеры: grep — сводка «N matches in M files» со счётчиками по файлам (top-15, отсортированы по числу совпадений) + первые 20 матчей; все три режима (content/files/count) парсятся устойчиво к двоеточиям в путях (content — lazy-regex, files/count — LastIndex, корректно для Windows-путей). exec — exit code + stderr (head/tail) + stdout (head/tail + все строки с error/fatal/panic/traceback из СЕРЕДИНЫ, без дублирования с head/tail; слово-границы + негативный фильтр «no errors»/«0 errors»); парсер секций терпим к пустому stdout-блоку и «stdout:»/«stderr:» в теле. list_dir/glob — entries/matches из атрибута (с fallback на пересчёт по телу, чтобы overflow-note не пропадал) + статистика dirs/files + первые записи. read_file — путь, диапазон строк (футер «showing X-Y of Z» или «file read in full»), head/tail нумерованных строк с кольцевым буфером O(1)/строку (без копирования всего файла в память). Общий каркас: строка Full output saved to: <path> (из truncateAndSaveResult) резервируется из бюджета ДО ужимания тела и гарантированно остаётся последней строкой дайджеста; при микробюджете путь приоритетнее сводки. Инструменты без дайджестера (включая MCP mcp__*) — прежний head+tail фолбэк. Тесты (24 новых): счётчики/файлы grep во всех режимах, error-строки из середины exec + негативы + дедуп с head/tail, бюджет во всех дайджестах, UTF-8-валидность на невалидном входе, lazy-regex на двоеточиях в тексте, saved-path (обычный/tiny/micro-бюджет, завершаемость), bounded read_file, интеграция с FastTrimToolResults.

Fixed

  • TruncateToolResult нарушал собственный бюджет на 1 токен (найдено тестами ). Запас +0.5 у хвостового бюджета + round-half-up в tokens.Count давали 501 токен при бюджете 500; при CJK-весах (1.0/руна) и микробюджетах расхождение достигало размера головы. Переписан пост-условием tokens.Count(result) <= maxTokens при любом maxTokens >= 1: tight-fit-цикл дозажимает хвост (1-2 итерации, ранний выход если голова+маркер уже не влезают — иначе O(n²)), при невлезающем маркере — prefixByTokens-hard-cap ценой потери маркера. Регрессионные тесты: бюджеты {1,2,5,6,7,25,100,500,4000} на ASCII и CJK-контенте.