1.5.3
Fixed (#395)
- Byte-обрезка превью больше не ломает multibyte UTF-8 (#395): срезы вида
content[:500] + "..."резали кириллицу/CJK/эмодзи посреди символа — PostgreSQL отвергал такие строки на INSERT (SQLSTATE 22021: неверная последовательность байт ... 0xd1 0x2e), из-за чего на PG-инстансах молча терялись персист tool-результатов иmemory_context(уmemory_search— оба пути сразу; баг с v0.167.8, на SQLite невидим — тот не валидирует UTF-8). Новый пакетinternal/truncate— единая точка обрезки:UTF8(s, maxRunes, marker)— рунный бюджет,Bytes(s, maxBytes, marker)— байтовый с откатом к границе руны (контракты вида MCPMaxResponseBytes),HeadTail— head+tail-превью по рунам. Переведены все найденные обрезки:tools/memory.go(3, источник прод-ошибок),tools/tool_log.go(6),tools/chat_history.go,tools/message_get.go,tools/exec.go,tools/web_fetch.go(head/tail),tools/mcp.go,tools/email_send.go(тема письма в имени файла),plan_strategy.go(3),subagent.go,tool_preview.go+ дубликат-хелперыtool_audit_hook.go/skill_run_steps_hook.go(audit-суммари),server/handler/document.go(2). Бюджеты превью тулов — в рунах («символы», как в докстрингах/доках); audit-суммариmaxArgsSummary/maxResultSummary— в символах (контракт docs/audit); короткие превью iteration-лога (tool_preview.go) — сознательно байтовые (наследие, комментарий в коде).webhook.go maskTokenне переводился: токены ASCII, срез безопасен.
Review (#395)
- ocr review (несколько прогонов) — закрыты: high «смена семантики audit-суммари 500 символов → байты» (переведены на
UTF8, рунный бюджет — как в docs/audit; комментарий-контракт в коде); medium «web_fetch head/tail должен идти через пакет» (добавленtruncate.HeadTail); low «докстринг Bytes: предусловие валидности s/marker», «неточный комментарий в mcp.go», «тонкие обёртки chat_history» (инлайн), «докстринг обёртки truncateUTF8», low «tool_preview — UTF8 или Bytes» (зафиксировано комментарием: Bytes осознанно). Отклонены с обоснованием: low «Sprintf-паника на постороннем % в midFormat HeadTail» — посылка неверна:fmt.Sprintfне паникует на несовпадении глаголов (даёт%!d(MISSING)), а предлагаемый guard!strings.Contains(midFormat, "%d") → ""ломал бы легитимный маркер без счётчика; контракт задокументирован в докстринге. Проверки:go test ./... -count=1,golangci-lint run ./...(0),go vet,make build-cross(6 платформ) — зелёные; фронт не менялся.