История изменений

Все значимые релизы trip2g. Новые записи идут первыми. Для каждого изменения указано
Что / Зачем / Как использовать, чтобы оператор или администратор мог решить,
стоит ли обновляться и как начать пользоваться функцией.

Теги старее v0.2.0 доступны только в истории git.


Unreleased

Поиск по сайту может не показывать недоступные заметки

  • Что. Новая настройка SEARCH_HIDE_UNREADABLE (--search-hide-unreadable), по умолчанию выключена. Когда она включена, поиск по сайту возвращает только заметки, которые читатель может открыть. Когда выключена, всё как раньше: каждая недоступная читателю заметка идёт после результатов как заглушка «Закрытый материал.» с настоящим заголовком и ссылкой.
  • Зачем. Заглушки показывают, что закрытый материал есть, и это удобно платному сайту. Сайт, чьи закрытые заметки не должны быть видны, не мог убрать их заголовки и ссылки из поиска.
  • Как использовать. Установите SEARCH_HIDE_UNREADABLE=true и перезапустите сервер. На администраторов, читателей с доступом и поиск через MCP настройка не влияет. См. Как работает поиск.

MCP expand снова описывает toc_path как список строк

  • Что. Схема входа инструмента expand говорит, что toc_path это массив строк, а last обычное число. С v0.11.0 в схеме toc_path был массивом без типа элементов, а тип элементов стоял у last.
  • Зачем. MCP-клиент, который строго проверяет схемы инструментов, мог из-за этого отказаться от expand или от всего списка инструментов.
  • Как использовать. Ничего делать не нужно. Клиент, который закэшировал список инструментов, получит исправленную схему при следующем tools/list.

Вошедший читатель открывает заметку с free: true

  • Что. Заметка с free: true открыта любому читателю: гостю, вошедшему пользователю без доступа к её подграфу и админу. Раньше вошедший пользователь без доступа видел пейволл на бесплатной заметке, которую гость открывал, а форма на этой заметке отвечала ему form_not_found. Страница, GraphQL-запрос note, поиск, похожие заметки, MCP, ассеты, живые обновления заметок и отправка формы читают одну и ту же проверку, поэтому меняются вместе. Остальные заметки платного подграфа и подграфа со входом закрыты, как и раньше.
  • Зачем. free: true публикует заметку для всех. Вход на сайт не должен закрывать то, что может прочитать гость.
  • Как использовать. Ничего делать не нужно.

Гость открывает заметку с free: true в подграфе со входом

  • Что. Заметка с free: true в подграфе с флагом Require sign-in теперь открыта и гостю. Раньше гость видел на её странице и в GraphQL-запросе note форму входа, её ассеты отвечали 401, в кеш страниц она не попадала, RSS-фиды и другие списки через .Public() её пропускали, а поиск, похожие заметки и MCP тому же гостю её уже показывали. Теперь все пути отвечают одинаково. Заметка такого подграфа без free: true по-прежнему показывает гостю форму входа. Карта сайта тоже включает такую заметку, см. ниже.
  • Зачем. free: true публикует заметку для всех, что бы ни говорили её подграфы (ru/user/subgraphs). Форма входа противоречила остальному сайту.
  • Как использовать. Ничего делать не нужно. Проверьте подграфы со входом: если там есть заметки с free: true, которые вы хотели оставить только для вошедших, уберите у них free. Бесплатная заметка показывает тем, кто её читает, и заметки, встроенные в неё через ![[...]], поэтому закрытая заметка, встроенная в бесплатную заметку подграфа со входом, теперь видна и гостю.

Карта сайта включает заметку с free: true из подграфа со входом

  • Что. sitemap.xml, основной и каждого кастомного домена, включает заметку с free: true из подграфа с флагом Require sign-in. Раньше в нём не было ни одной заметки такого подграфа, бесплатной или нет. Теперь карта сайта задаёт тот же вопрос, что страница, RSS-фиды и кеш страниц, и больше не может с ними разойтись. Как и раньше, в неё не попадают заметки без free: true, заметки с noindex: true, скрытые заметки, файлы .html и всё, что лежит по пути с /_.
  • Зачем. Такая заметка открыта гостю (см. выше), поэтому поисковик должен находить её, как любую бесплатную.
  • Как использовать. Ничего делать не нужно. Чтобы убрать бесплатную заметку из поисковиков, добавьте ей noindex: true.

v0.11.1 (2026-10-07)

Вебхуки оплат переехали в /_system/

  • Что. Уведомления NOWPayments теперь приходят на /_system/nowpayments/ipn, вебхуки Patreon — на /_system/patreon/webhook. По адресам /api/ больше ничего не отвечает. Уведомление NOWPayments отклоняется, если не задан --nowpayments-ipn-key.
  • Зачем. Все системные адреса живут в /_system/, эти два были последними вне его.
  • Как использовать. NOWPayments: ничего делать не нужно, trip2g передаёт адрес в каждом счёте. Счёт, созданный до обновления и оплаченный после, отправит уведомление на старый адрес, а он больше не отвечает: после обновления проверьте ожидающие покупки и выдайте доступ вручную тем, что в NOWPayments отмечены оплаченными. Patreon: ничего делать не нужно, trip2g регистрирует вебхук сам. При обычном перезапуске старая версия удаляет вебхук со старым адресом при остановке, а новая регистрирует новый адрес при старте. Если старая версия остановилась не штатно, новая всё равно зарегистрирует новый адрес; оставшийся вебхук со старым адресом ни на что не влияет, его можно удалить в кабинете автора Patreon. Участники Patreon в любом случае обновляются раз в час.

Отзыв доступа к подграфу из админки

  • Что. /api/admin/revokeusersubgraphaccess удалён. Вместо него — мутация администратора revokeUserSubgraphAccess и кнопка Revoke на странице доступа в админке. Для отзыва нужна причина; после него страница доступа показывает, когда, кем и почему он отозван, а в списке доступов появилась колонка «Revoked At».
  • Зачем. Старый адрес не проверял роль администратора, и ничто в trip2g его не вызывало. Теперь отзыв идёт через пространство admin, которое роль проверяет.
  • Как использовать. Админка → Users → Subgraph Accesses → открыть доступ → указать причину → Revoke. Или вызовите revokeUserSubgraphAccess с номером доступа и причиной (ru/user/user_management).

Документация: приложение поверх trip2g

  • Что. Приложение поверх trip2g теперь описывает весь путь JavaScript-приложения на своём шаблоне: как приложение получает доступ по cookie сессии посетителя, настройки во frontmatter, стандартные шапка и подвал сайта вокруг приложения, сохранение с expectedHash, живые обновления через server-sent events, вызовы API и админские вызовы, MCP из браузера над всей базой, своё API, которое спрашивает у trip2g, кому можно его вызывать, формы и небольшой магазин как пример. Своё API размещается под /_system/extra/: trip2g там ничего не обслуживает, и это проверяет тест в его роутере. Шаблоны показывают стандартные шапку и подвал в любом своём шаблоне.
  • Зачем. Страница описывала только шаблон, бандл и сохранение. Чтобы сделать настоящее приложение, авторизацию, живые обновления и место для своего бэкенда приходилось искать в исходниках.
  • Как использовать. Ничего делать не нужно. Перед тем как делать приложение, прочитайте Приложение поверх trip2g.

Документация: стандартный шаблон не рисует форму

  • Что. Формы теперь прямо говорят, что стандартный шаблон только встраивает форму заметки как JSON: полей и капчи он не рисует. Чтобы показать форму, нужен шаблон, который её строит: готовый form_template, который показывает форму как опрос, или свой. Ещё на странице описан результат TurnstileRequiredPayload у submitForm.
  • Зачем. Страница утверждала, что стандартный шаблон рисует форму в конце страницы и обрабатывает Turnstile. Он не делает ни того, ни другого, и заметка с формой показывалась без формы.
  • Как использовать. Положите form.html из form_template в _layouts/ и задайте заметке layout: form. См. Формы.

v0.11.0 (2026-10-07)

Ссылки «Назад»/«Далее» и хлебные крошки

  • Что. Стандартный шаблон показывает хлебные крошки над заголовком и ссылки «Назад»/«Далее» в конце страницы. Они берутся из первой заметки боковой панели (сначала левой, потом правой), где есть ссылка на страницу: соседние ссылки и заголовок над ней. Считаются и вики-ссылки, и markdown-ссылки. Свойства prev, next и breadcrumbs во frontmatter их переопределяют, false отключает.
  • Зачем. Документацию читают по порядку, а перейти к следующей странице можно было только через сайдбар. И не было видно, к какому разделу относится страница.
  • Как использовать. Если у страниц уже есть заметка-сайдбар, ничего делать не нужно. Чтобы отключить для папки, задайте prev: false, next: false, breadcrumbs: false frontmatter-патчем. См. Стандартный шаблон.

Ссылки во frontmatter ищутся как в Obsidian

  • Что. Вики-ссылки в header, footer, left_sidebar и right_sidebar ищутся по тем же правилам, что и ссылки в тексте заметки: по имени файла, от заметки, в которой стоит свойство. Часть |псевдоним и #заголовок не учитывается.
  • Зачем. Эти ссылки сравнивались только с адресом страницы. Поэтому [[Папка/_sidebar]] с кириллицей в имени папки, [[Site Footer]] или [[Заметка|псевдоним]] ничего не находили, и сайдбар или шапка молча пропадали, хотя Obsidian показывал ссылку рабочей.
  • Как использовать. Ничего делать не нужно. В режиме по умолчанию (global) ссылки, которые работали, ведут в тот же файл, а те, что ничего не находили, заработают. В режиме scoped голая [[Имя]] теперь выбирает ближайший файл, как ссылки в тексте заметки. См. Куда ведёт вики-ссылка.

Админ может скрывать заметки

  • Что. hideNotes и изменение hide в updateNotes работают для вошедшего админа: сессия в браузере, личный токен t2g_… в заголовке или в ?token=, токен сессии. Изменение hide работает и с токеном вебхука, в пределах его write patterns. Скрытие записывается на вошедшего админа, на админа, создавшего API-ключ, или на админа, создавшего вебхук.
  • Зачем. У всех, кроме API-ключа, оба вызова падали с записью в errors, у токена вебхука так же падало изменение hide: скрытие не на кого было записать.
  • Как использовать. Ничего делать не нужно. Токен вебхука, выданный до обновления, получает ErrorPayload от изменения hide, пока не истечёт. См. updateNotes.

Базовый шаблон может отметить место голым yield

  • Что. Базовый шаблон может отметить место через {{ yield main() }} вместо {{ block main() }}…{{ end }}; страницы, которые его наследуют, заполняют место своим {{ block main() }}. Автоимпорт больше не подключает шаблон, который начинается с {{ extends }}: такой файл является страницей, а не компонентом.
  • Зачем. Автоимпорт находил main в странице-наследнике и вставлял всю эту страницу в базу. База не загружалась, а все страницы, которые её наследуют, падали с template /base could not be found.
  • Как использовать. Голый yield подходит для места, которое заполняет каждая страница, block для места с содержимым по умолчанию. См. Шаблоны.

Колонка текста шире на больших экранах

  • Что. В стандартном шаблоне колонка текста растёт на широких экранах: 80 знаков от 1440px. От 1536px колонка шириной 56rem, раскладка растёт до 1680px с более широкими боковыми колонками, а базовый размер шрифта остаётся 18px и больше не растёт вместе с экраном. Ниже 1440px всё как раньше.
  • Зачем. На большом мониторе текст сжимался в узкую полосу, а таблицы и примеры кода приходилось листать вбок. wide: true решал это, но прятал оба сайдбара.
  • Как. Ничего делать не нужно. wide: true остаётся для страниц, которым нужна вся ширина (доски, большие диаграммы).
  • Что. [[Заметка#Текст заголовка]] и [[#Текст заголовка]] теперь ведут на id заголовка, тот же, что у страницы и её оглавления. Текст сравнивается сначала точно, потом без учёта регистра и лишних пробелов; id вроде faq-2 или заданный через {#id} тоже подходит. Если фрагменту не соответствует ни один заголовок, ссылка теперь помечается как битая, а trip2g lint сообщает broken link: Заметка#Заголовок.
  • Зачем. Фрагмент попадал в адрес как написан (#Текст%20заголовка) и не совпадал ни с одним id: ссылка открывала страницу с начала, а ссылка на заголовок в той же заметке помечалась как битая.
  • Как использовать. Ничего делать не нужно: ссылки, написанные как в Obsidian, теперь работают. См. Markdown.

«Ссылки сюда» идут по алфавиту

  • Что. Блок обратных ссылок в конце страницы и nvs.BackLinks(note) в шаблонах отдают заметки по алфавиту заголовка (без учёта регистра), при совпадении по адресу.
  • Зачем. Список собирался из словаря, и его порядок менялся от рендера к рендеру: нужную ссылку было трудно найти, а одна и та же страница выглядела по-разному.
  • Как. Ничего делать не нужно.

Шаблоны экранируют вывод по умолчанию

  • Что. Свой шаблон (layout) теперь экранирует то, что выводит. {{ note.Title() }}, значение из frontmatter, {{ note.ContentString() }} и любая другая обычная строка попадают на страницу текстом: <, >, &, " и ' превращаются в HTML-сущности. HTML, который собирает сам сервер, по-прежнему выводится как есть: note.HTMLString(), FirstListHTML(), TitleHTML и ContentHTML секции, HTML блока кода, FormSpecJSON(), SubgraphNamesJSON(), asset() и defaultTemplate.*.
  • Зачем. Шаблоны выводили всё без экранирования. Заметка, в markdown которой было </textarea><script>, вырывалась из <textarea>{{ note.ContentString() }}</textarea> и запускала свой скрипт, а заголовок с & или < ломал разметку вокруг. В каждом шаблоне приходилось помнить про | html на каждом значении, и хватало одного пропуска.
  • Как. Существующие шаблоны продолжают работать. | unsafe и | raw по-прежнему выводят строку как есть. | html и html(x) по-прежнему экранируют, ровно один раз, поэтому RSS-шаблон с html(n.HTMLString()) выводит то же, что раньше; для текста фильтр больше не нужен. | json по-прежнему экранирует <, > и & внутри JSON, так что {{ x | json }} безопасен внутри <script>. | url экранирует один раз.
  • Миграция. Проверьте шаблоны, которые выводят разметку из обычной строки: параметр блока вроде title="Строка<br>вторая", HTML, склеенный через + или полученный из replace(...). Теперь теги в нём покажутся текстом; выводите такое значение через {{ title | unsafe }}. Значения htmlInjectionsHead / htmlInjectionsBodyEnd остаются обычными строками, поэтому {{ injection.Content | unsafe }} оставьте. asset() теперь кодирует в URL ", ', <, >, обратный слэш, обратную кавычку и пробельные символы; обычные адреса, включая подписанные ссылки хранилища с &, не меняются. См. Шаблоны.

Свои якоря заголовков и больше данных заметки для шаблонов

  • Что. Заголовок может задать свой якорь: ## Цены {#plans} получает id="plans", {.class} добавляет CSS-класс. В шаблонах у секции появились ID и Level, а Section(...) находит заголовок и без учёта регистра и лишних пробелов, и по якорю ("plans" или "#plans"). Новое: Images() (markdown-картинки и встраивания ![[...]]), CodeBlocks(lang), поля Task и TaskMark у пунктов списка (включая пользовательские статусы Obsidian вроде [/]) и функции parseJSON, parseYAML, parseCSV.
  • Зачем. Сгенерированный якорь меняется вместе с текстом заголовка, и ссылки на него ломаются. Шаблон умел разбить заметку на секции, но не мог сослаться на них, найти секцию по якорю или прочитать картинки, блоки кода и статусы задач.
  • Как. Допишите {#id} в конец заголовка, см. Markdown. Функции шаблонов описаны в Шаблонах. Если заголовок уже заканчивался скобками такого вида, например ## Установка {#install}, они пропадут из текста, а заголовок получит этот id.

Пустое поле во frontmatter больше не ломает синхронизацию

  • Что. Заметка с полем description: или redirect: без значения (или со значением null, ~) теперь загружается: поле считается незаданным.
  • Зачем. Раньше такая заметка роняла загрузку, и вся пачка синхронизации из ста заметок не проходила с ошибкой «invalid description type: ». В справке Obsidian так оформлены несколько страниц.
  • Как. Ничего делать не нужно. Если синхронизация раньше падала с этой ошибкой, запустите её ещё раз.

Поиск находит заметку по её алиасу

  • Что. Поиск на сайте и MCP-поиск теперь читают поле aliases (и alias) из frontmatter. Если запрос целиком совпадает с названием заметки или одним из её алиасов, заметка получает прибавку вдвое весомее совпадения по тексту или по смыслу и обычно выходит на первое место. Регистр, лишние пробелы и ё/е не важны.
  • Зачем. Раньше алиасы не участвовали в поиске вообще: заметку, которую все зовут «Starred», а называется она «Bookmarks», по слову «Starred» было не найти. На справке Obsidian запросы, совпадающие с алиасом, стали находить нужную страницу почти всегда (nDCG@5 0,81 → 0,98), а на остальных запросах выдача не изменилась.
  • Как. Перечислите альтернативные названия в frontmatter, как в Obsidian: aliases: [Starred, Избранное]. Новые URL алиасы не создают.

Образ MinIO заменён на Silo

  • Что. Compose-файлы и инструкции по самостоятельному размещению теперь используют pgsty/silo вместо minio/minio.
  • Зачем. MinIO прекратил открытую дистрибуцию и 11 сентября 2026 года удалил образы minio/minio с Docker Hub. docker compose up падает с ошибкой pull access denied for minio/minio. Silo: последний открытый сервер MinIO, который пересобирает и обновляет проект Pigsty. Тот же S3 API, те же переменные MINIO_*, тот же формат данных на диске.
  • Как. В своём docker-compose.yml замените image: minio/minio:latest на image: pgsty/silo:latest и выполните docker compose up -d. Том с данными не трогайте: Silo читает существующие данные как есть. Подробнее: Самостоятельное размещение.

Синхронизация больше не принимает заметку за шрифт или картинку

  • Что. Сервер больше не отклоняет обычные заметки с ошибкой вида «Unsupported content type: application/vnd.ms-fontobject» (или audio/mpeg, image/gif, text/xml). Теперь он проверяет только одно: содержимое должно быть текстом в UTF-8 без нулевых байтов. Бинарный файл под видом заметки по-прежнему не пройдёт, ошибка теперь звучит так: «File content must be UTF-8 text».
  • Зачем. Тип файла угадывался по первым байтам, как это делает браузер. Для текста такая проверка ошибается: заметка, у которой заглавные буквы LP оказались на 35-м и 36-м байте (например, в заголовке «T HELPER» или «NLP»), считалась шрифтом EOT. Заметка, которая начинается с «ID3», считалась музыкой, а с «GIF89a» картинкой. Такая заметка не синхронизировалась, а по ошибке нельзя было понять, что с ней не так.
  • Как. Ничего делать не нужно. Если какая-то заметка раньше не синхронизировалась с такой ошибкой, запустите синхронизацию ещё раз.

Встраивания, превью и ссылки отображаются надёжно

  • Что. Заметка, которая встраивает другую (![[note]]), больше не выходит пустой, если у встроенной заметки нет текста, если встраивания замыкаются в кольцо или идут цепочкой на много уровней. О кольце теперь сообщает предупреждение «embed cycle» на заметке. Бесплатное превью (free_paragraphs, free_cut) сохраняет таблицы и callout'ы, каждый считается одним блоком. Вики-ссылки на адреса javascript:, vbscript:, file: и data: выводятся без адреса, как и обычные Markdown-ссылки. Встроенный твит теперь выводится стандартным способом X и собирается в браузере читателя, сервер его больше не скачивает. Виджеты и реклама Quail (quaily.com, quaily://) и чат-боты Dify (udify.app, dify://) больше не встраиваются: такой адрес в синтаксисе картинки выводится как обычная картинка.
  • Зачем. Страница могла потерять всё содержимое без единого предупреждения, таблица в начале заметки пропадала из превью, а встроенный твит заставлял каждую синхронизацию ждать ответа X. Кроме того, страницы, которые отрисовывались одновременно для разных посетителей, могли подменить друг другу ссылки на картинки в футере и карточках.
  • Как. Ничего делать не нужно. Если на заметке появилось предупреждение «embed cycle», уберите одно из встраиваний, которые образуют кольцо.

noindex: true убирает страницу из поиска и из sitemap.xml

  • Что. Новое свойство заметки noindex. Заметка с noindex: true не попадает в sitemap.xml (ни в основной, ни в sitemap отдельного домена), в RSS-фиды и в списки .Public() в layout, и не содержит JSON-LD. Её страница получает <meta name="robots" content="noindex"> в шаблоне по умолчанию и заголовок X-Robots-Tag: noindex в каждом ответе, в том числе со своим layout.
  • Зачем. Страница только для прямой ссылки (предложение, черновик, демо) могла вывести robots-тег из своего layout, но всё равно оставалась в sitemap.xml, и её адрес видел любой, кто откроет sitemap.
  • Как. Добавьте noindex: true во frontmatter заметки или задайте его для папки frontmatter-патчем. Это не защита доступа: по ссылке страницу по-прежнему откроет кто угодно. Подробнее: SEO.

Закрытая страница так и говорит, что она закрыта, и предлагает вход вместо листа ожидания

  • Что. Страница, к которой у посетителя нет доступа, теперь пишет «Эта страница закрыта», предлагает обратиться за доступом к владельцу базы знаний и сразу показывает форму входа. Блок листа ожидания (Telegram-бот и поле e-mail) по умолчанию выключен и появляется, только если включить новую настройку show_waitlists; как и раньше, он показывается только когда продавать нечего.
  • Зачем. Поле e-mail от листа ожидания было единственным полем ввода на странице, а текст над ним просил авторизоваться, и посетители вводили туда свою почту для входа и ждали письмо, которого не будет. Большинство баз закрывают, чтобы они оставались приватными, а не чтобы потом продавать доступ.
  • Как. Делать ничего не нужно: закрытые страницы получают новый текст и форму входа автоматически. Если вы всё-таки собираете контакты, пока предложений нет, включите show_waitlists в админке: System → Config.

Obsidian на телефоне: открыть заметку на сайте и закрыть вкладку с предупреждениями (плагин v0.11.0)

  • Что. В меню «...» заметки появились пункты Открыть на сайте и Скопировать ссылку на сайт (только для опубликованных заметок) и команда Open published URL in browser. У вкладки с предупреждениями синхронизации появилась кнопка Close, а по кнопкам её тулбара теперь можно попасть пальцем.
  • Зачем. Ни то ни другое на телефоне было недоступно. Индикатор с адресом живёт в статус-баре, которого в мобильном Obsidian нет, и до страницы заметки вела одна команда «Copy published URL»; а вкладку с предупреждениями приходилось закрывать через переключатель вкладок.
  • Как. Обновите плагин до 0.11.0 или новее и нажмите ... на заметке → Открыть на сайте. Чтобы открывать в один тап, закрепите команду Open published URL in browser в Obsidian: Настройки → Панель инструментов (мобильная).

Цепочки доставки: трейс каждого шага вебхука через граф агентов

  • Что. Каждая доставка вебхука теперь записывает, какая доставка её вызвала и к какой цепочке она принадлежит. В админке появился экран System → Цепочки доставок: список цепочек, а внутри шаги, у каждого видно, что он записал, какая запись его вызвала, его статус, стоимость и глубину в цепочке. Цепочка собирается через разные хосты агентов (на одном работают code-роли, на другом LLM-роли), и ни один из них про другой не знает.
  • Зачем. Когда изменение запускало вебхук, который записывал заметку, которая запускала следующий вебхук, вся последовательность была невидима: отдельные записи доставки были, а связи между ними не было. Теперь весь путь распространения читается с одного экрана.
  • Как. Откройте Админка → System → Цепочки доставок и выберите цепочку, чтобы посмотреть её шаги. Цепочки, которые ничего не записали, скрыты: расписание, не нашедшее работы, всё равно отрабатывает, и такие прогоны заваливают остальные. Включите Показывать пустые, чтобы их увидеть. Настраивать нечего: все доставки после обновления связываются сами.

Агенты сообщают стоимость прогона в своих единицах

  • Что. Ответ вебхук-агента теперь несёт объект costs вместо фиксированных полей tokens_used и steps: {"costs": {"tokens": 5186, "steps": 2}}. Единица живёт в ключе, поэтому агент, который платит деньгами, отдаёт {"usd": 0.004}, а считающий что-то своё отдаёт своё. Админка показывает то, что пришло: по каждому шагу и суммарно по единицам за всю цепочку.
  • Зачем. Токены и шаги: словарь одного исполнителя. Всему остальному (платному API, балансу кредитов, квоте очереди) сообщить о себе было нечем, а trip2g хранил понятие «токенов», знать которое ему незачем.
  • Как. Возвращайте из агента costs объектом чисел. Каждое значение должно быть числом: строка или объект внутри costs делают весь ответ нечитаемым для trip2g, и его changes тоже не применяются. Полей tokens_used и steps больше нет: агент, который всё ещё их шлёт, просто не сообщает стоимость.

Идемпотентные записи больше не поднимают события изменений

  • Что. Вызов updateNotes, пуш синхронизации или изменение в ответе вебхук-агента, содержимое которых совпадает с уже сохранённым, не создают новую версию и не поднимают событие изменения. Доставка вебхука не ставится в очередь, SSE-событие не отправляется, повторное встраивание не запускается, обновление Telegram-вью не происходит. Изменение хотя бы одного байта по-прежнему срабатывает как обычно. Скрытие заметки работает как раньше и событий не поднимает. Запись в скрытый путь возвращает заметку (другого способа снять скрытие нет); через updateNotes или пуш синхронизации это считается изменением, и вернувшаяся заметка поднимает обычные события.
  • Зачем. Агент, повторно записывающий то же содержимое, ре-триггерил вебхук, который его вызвал, на каждом цикле, с полной стоимостью LLM, ограниченной только max_depth. Исправление останавливает цикл на уровне записи: если ничего не изменилось, никому из downstream не нужно об этом знать.
  • Как. Автоматически. Агенты, возвращающие неизменённое содержимое, больше не порождают неконтролируемые цепочки. Защита max_depth остаётся полезной для агентов, которые действительно вносят изменения.

Фильтр путей заметок по frontmatter

  • Что. notePaths принимает filter: { frontmatter: [...] }. В каждом условии указан key и одно из exists: true, exists: false или equals: "<строка>". Условия должны выполняться все сразу и работают вместе с paths, like и search. Проверяется frontmatter последней версии заметки, уже после frontmatter-патчей. equals сравнивает только строковые значения: equals: "5" не найдёт count: 5, а equals: "true" не найдёт draft: true.
  • Зачем. Чтобы найти все заметки с нужным свойством (например, все роли с fleet_id: codellm), скрипту приходилось загружать заметки по одной и разбирать их frontmatter.
  • Как. notePaths(filter: { frontmatter: [{ key: "fleet_id", equals: "codellm" }] }), с API-ключом в X-Api-Key, как и раньше. Сервер сохраняет frontmatter каждой версии заметки при загрузке. При старте он заполняет текущие последние и опубликованные (live) версии. Старые версии не обрабатываются, поэтому ключ, который есть только в старых версиях, в индекс не попадает.

Поиск заметки без результата в шаблоне даёт nil

  • Что. В своём шаблоне nvs.ByPath, nvs.ByPermalink, nvs.ByWikilink, .First() и .Last() у запроса, note.LangAlternative(...), PartialRenderer().Section(...), Section(...) у секции и FirstList() возвращают nil, если ничего не нашлось. Результат теперь проверяют и {{ if x }}, и {{ if x == nil }}.
  • Зачем. При промахе возвращался пустой указатель конкретного типа. {{ if x }} считал его пустым, а x == nil давал false, поэтому шаблон с проверкой x == nil шёл дальше и работал с заметкой, которой нет.
  • Как. Ничего делать не нужно: шаблоны с {{ if x }} работают как раньше. Чтобы найти и проверить в одном теге, пишите {{ if about := nvs.ByPermalink("/about"); about }}. См. Шаблоны.

Шаблоны с try/catch, exec, range _ и дефисом в папке загружаются

  • Что. Шаблон с {{ try }}…{{ catch err }}…{{ end }}, с {{ return }} в файле, который вызывают через exec("путь", data), или с {{ range _, x := list }} теперь загружается и рендерится. Страница, которая начинается с {{ extends "..." }}, получает автоимпорт компонентов. @lid теперь заменяет на _ каждый символ, недопустимый в имени блока Jet (-, ., пробел), и ставит _ перед цифрой в начале: my-theme/card.html даёт my_theme_card, 2col/card.html даёт _2col_card.
  • Зачем. Обход шаблона в загрузчике останавливался на этих конструкциях, и шаблон не загружался с ошибкой разбора. В странице с extends автоимпорт вставлял компоненты над строкой extends, где Jet их не принимает. Компонент в папке вроде my-theme/ получал @lid с дефисом, а это недопустимое имя блока.
  • Как. Ничего делать не нужно. Префиксы yield_blocks следуют форме @lid: yield_blocks("_style_my_theme_"). См. Синтаксис Jet и yield_blocks.

Стандартный шаблон: блоки кода, широкие таблицы и боковое меню

  • Что. Блок кода с пометкой jet подсвечивается как шаблон Jet. Кнопка копирования больше не уезжает вместе с длинной строкой; её подпись зависит от lang страницы (русская для ru, иначе английская), а «Скопировано» озвучивается для экранных дикторов. Блок на языке, который подсветка не знает, показывается простым текстом. Широкая таблица прокручивается в своей рамке и не растягивает страницу. От 1024px левое меню прокручивается отдельно, открывается сразу на текущей странице и выделяет её цветным фоном.
  • Зачем. Кнопка копирования сидела внутри прокручиваемого кода и уезжала вместе с ним. Широкая таблица сдвигала вбок всю страницу. В длинном меню текущая страница могла оказаться за краем, а её отметку было плохо видно.
  • Как. Ничего делать не нужно.

Больше интерфейса на языке читателя

  • Что. Страницы 404 и 500 стандартного шаблона, подпись «Read in:» у переключателя языка, подписи кнопок меню, alt у логотипа и текст «пока не поддерживается» для файлов Canvas, Excalidraw и Bases теперь следуют языку интерфейса. На русском переведены и кнопка «Редактировать страницу», поиск файлов, список сохранения и сравнение версий в редакторе, переключатели автообновления и слежения за редактором, кнопка смены темы.
  • Зачем. Эти строки были зашиты по-английски, или их русский перевод лежал там, где сборка фронтенда его не находила.
  • Как. Ничего делать не нужно. Язык интерфейса берётся из языковой cookie или из Accept-Language браузера.

Читатели больше не скачивают редактор страниц

  • Что. Редактор страниц теперь отдельный бандл, /assets/ui/editor/pane/-/web.js. Его загружает только фрейм редактора, когда админ открывает редактор; в бандле, который скачивает каждый читатель, редактора больше нет.
  • Зачем. Редактор входил в публичный бандл, и каждый читатель скачивал код, который запускает только админ.
  • Как. С бинарниками из релиза и Docker-образом ничего делать не нужно. Свой шаблон, который выводит {{ defaultTemplate.UserSpaceScripts() }}, передаёт адрес редактора сам. Если вы собираете фронтенд сами, соберите и trip2g/editor/pane (npm start trip2g/editor/pane), как теперь делает Dockerfile.

Своя надпись на кнопке SSO и выключение входа по email

  • Что. У кнопки входа через OIDC может быть своя надпись вместо «Войти через SSO»: OIDC_DISPLAY_NAME для провайдера из переменных окружения или displayName в createOIDCCredentials для провайдера, созданного через API. Вход по коду на email можно выключить: DISABLE_EMAIL_SIGNIN=true в окружении или переключатель email_signin_enabled в Админке → System → Config. Когда он выключен, экран входа прячет поле email, его кнопку и капчу, а requestEmailSignInCode и signInByEmail отвечают ошибкой email_sign_in_disabled. Новое в GraphQL: Query.emailSignInEnabled, OAuthUrlPayload.label, AdminOIDCCredentials.displayName.
  • Зачем. «SSO» называет протокол, а не место, куда попадёт читатель. В инстансе, где все аккаунты приходят от провайдера, форма email предлагала запросить код для аккаунта, которого нет.
  • Как. Переменная окружения сильнее: пока задан DISABLE_EMAIL_SIGNIN, переключатель в админке не включит вход по email обратно. Прежде чем выключать email, убедитесь, что провайдер работает, иначе войти не сможет никто, включая вас. Пустая надпись оставляет текст по умолчанию. Подробнее: OIDC и OAuth.

После входа читатель возвращается на свою страницу, а редиректы не уводят с сайта

  • Что. После входа через Google, GitHub или OIDC читатель возвращается на страницу, где нажал кнопку, а не на главную. Редиректы входа теперь отдают относительный Location, поэтому за прокси, который снимает TLS, они больше не переводят браузер с https на http. Любой адрес редиректа, собранный из запроса (адрес возврата после OAuth и чистый URL после входа по ссылке ?hat= из оплаты), разбирается и собирается заново как путь на этом сайте; всё, что называет другой хост, протокольно-относительный //host, /\host или URL с управляющими символами, превращается в /.
  • Зачем. Кнопка отправляла полный URL страницы, а сервер отбрасывал любой абсолютный URL, поэтому каждый вход заканчивался на /. За TLS-прокси редирект вёл на http://, браузер не отправлял только что полученную Secure-куку, и удачный вход выглядел как неудачный. Редирект после ?hat= копировал строку запроса как есть, и запрос с абсолютным URL мог увести браузер на чужой сайт.
  • Как. Ничего делать не нужно.

Обратные ссылки в GraphQL учитывают права чтения

  • Что. NoteView.inLinks показывает только те ссылающиеся заметки, которые вызывающий может читать.
  • Зачем. Поле отдавало все заметки, которые ссылаются на текущую, вместе с их content и html, в том числе заметки из подграфов, к которым у вызывающего нет доступа.
  • Как. Ничего делать не нужно.

Векторный поиск видит новые заметки без перезапуска

  • Что. Эмбеддинги, посчитанные после синхронизации, теперь попадают в векторный поиск и в похожие заметки, пока сервер работает. Сервер перечитывает эмбеддинги и чанки из базы после того, как задачи эмбеддинга завершились: не чаще раза в EMBEDDING_RELOAD_INTERVAL (по умолчанию 5s), пока задачи продолжают завершаться, и ещё раз после последней. Заметки при этом не перерисовываются.
  • Зачем. Эмбеддинги загружались в память вместе с заметками, раньше, чем фоновые задачи успевали их записать, поэтому заметка, синхронизированная после старта, не находилась векторным поиском до следующего перезапуска.
  • Как. Ничего делать не нужно. Во время долгой заливки поиск улучшается по мере работы задач, а не после её окончания.

MCP: expand читает лист, показывает только края длинной заметки, а ошибки федерации называют базы

  • Что. expand на разделе без подразделов возвращает сам раздел: тот же текст, что note_html для этого toc_path, и section_html в структурированном ответе. Новые аргументы first и last у expand и federated_expand показывают только N самых старых и N самых новых подразделов; сводка говорит, что список неполный («newest 30 of 365 subsection(s)», между краями строка «… 330 subsection(s) not listed …»), а в ответе есть total_children и omitted. Ответ federation_not_configured теперь перечисляет базы, к которым агент может обратиться (connected_kb_ids), а если промах сообщил пир, то и базы, подключённые к этому хабу напрямую. Результаты поиска и заметки, которые указывают на другую базу, показывают её kb_id в тексте.
  • Зачем. Чтобы прочитать лист, агенту приходилось вызывать note_html с теми же аргументами. Заметка, в которую каждый день добавляется раздел с датой, отдавала список в десятки тысяч символов тому, кому нужна последняя запись. Ошибка «not configured» не называла ни одного верного kb_id, и агент не мог исправиться сам.
  • Как. { "name": "expand", "arguments": { "path": "log.md", "first": 5, "last": 30 } }. Если края сходятся или перекрываются, возвращается весь список. Подробнее: expand.

База знаний в одном контейнере

  • Что. Dockerfile.docs собирает образ, в который зашито хранилище. docker run запускает trip2g, один раз заливает в него хранилище клиентом синхронизации из onboarding-архива самого инстанса и продолжает работать. Ни тома, ни входа, ни настройки: база и секреты создаются внутри контейнера при старте. Внутренний адрес слушает только 127.0.0.1. По умолчанию в образе эта документация.
  • Зачем. Чтобы раздать базу знаний только для чтения (команде или MCP-клиенту), нужен был работающий инстанс и отдельный шаг синхронизации.
  • Как. docker build -f Dockerfile.docs -t trip2g-docs ., затем docker run --rm -p 8080:8080 trip2g-docs. Замените строки COPY своей папкой, а публичный адрес передайте через -e PUBLIC_URL=.... Подробнее: База знаний в одном контейнере.

Fleet: пауза для роли и более надёжный запуск кода

  • Что.
    • Заметка роли с enabled: false уходит из реестра при следующем опросе, её вебхуки удаляются, и cron-роль перестаёт срабатывать; сама заметка остаётся. --dry-run показывает STATUS: DISABLED (enabled: false). Значение, отличное от true/false (принимаются также yes/no, on/off, 1/0), считается ошибкой разбора.
    • Повторный вызов codellm больше не запускает код второй раз: fleet отправляет один Idempotency-Key на вызов и повторяет его при каждой попытке, а codellm 10 минут отвечает на повтор сохранённым ответом. Повторы считает codellm_exec_replays_total.
    • Итоговый stdout сверх лимита теперь роняет запуск с ошибкой «stdout limit exceeded» (HTTP 422 от /v1/chat/completions), а не отдаёт обрезанное начало. Лимит по умолчанию 10 MiB (было 1 MiB), задаётся через CODELLM_MAX_STDOUT_BYTES или --max-stdout-bytes.
    • Записи, которые вернул инструмент exec, проверяются одной пачкой до того, как применится хоть одна: путь вне области записи отбрасывается и называется в результате инструмента, а патч, чей find не найден или найден больше одного раза, отклоняет всю пачку.
    • codellm отказывается распечатывать роль, чей unseal_env_key попадает под CODELLM_EXPOSE_ENV_PREFIX, как уже отказывался для точных имён из CODELLM_EXPOSE_ENV.
    • Системный промпт агента начинается с неизменной части, а инструкция конкретной доставки идёт в конце, чтобы кеш промптов у провайдера мог его переиспользовать; в fleet_llm_tokens_total появился kind="cached" (часть prompt, а не дополнительные траты).
  • Зачем. Cron-выражение, которое никогда не наступит, не надёжная пауза. Сетевой повтор мог второй раз выполнить код с побочными эффектами. Обрезанный stdout выглядел как результат. Пачка могла закончиться наполовину применёнными записями и сообщением об успехе для модели. Список префиксов мог отдать ключ печати тому самому коду, от которого он защищает.
  • Как. Чтобы поставить роль на паузу, добавьте enabled: false в её frontmatter; чтобы снять, уберите строку. Подробнее: Fleet и секреты codellm.

Админ может выдать пользователю ссылку для входа

  • Что. На странице пользователя в админке появилась кнопка Sign-in link, а в admin GraphQL мутация createHatLink(input: { email, redirectUrl, expiresInMinutes }). Она возвращает ссылку /_system/hat?token=..., по которой человек входит в один клик. Ссылка живёт 5 минут по умолчанию и не больше 60, redirectUrl должен быть путём на том же сайте. Ссылка впускает только существующего пользователя: аккаунт она не создаёт и прав админа не даёт. Нерабочая ссылка теперь открывает короткую страницу с тем, что делать («Ссылка устарела», «Ссылка не работает», «Аккаунт не найден»), а не ошибку.
  • Зачем. Впустить человека без письма можно было только через CLI сервера с его секретом подписи. А при сбое посетитель видел сырую ошибку, с которой нечего делать.
  • Как. Админка → Пользователи → пользователь → Sign-in link, скопируйте ссылку и отправьте. Запрос попадает в журнал аудита, сама ссылка нет.

Личные токены для других пользователей

  • Что. Админ может выпустить личный токен (t2g_...) любому пользователю через adminCreateUserToken(input: { userId, name, expiresInDays }) (срок от 1 до 365 дней, необязательно) и отозвать любой токен по id через adminRevokeUserToken. Раздел Админка → Пользователи → Личные токены показывает все токены инстанса; ссылка Personal tokens на странице пользователя открывает его с фильтром по этому пользователю и заполненной формой нового токена. У CreateUserTokenPayload новое поле instructions: готовый текст с адресом MCP и самим токеном, который можно передать тому, кто будет им пользоваться. В окне аккаунта у токенов появились свои экраны, а главная аккаунта показывает email, под которым вы вошли, и избранные заметки.
  • Зачем. Токен мог создать только сам пользователь для себя, и чтобы дать доступ агенту, приходилось сначала входить от имени этого человека.
  • Как. Админка → Пользователи → пользователь → Personal tokens → новый токен. Скопируйте блок инструкций один раз: токен уже внутри. См. MCP.

Закрытые страницы отвечают 401 или 403 и не выдают описание

  • Что. Страница за стеной входа или пейволлом теперь отвечает 401 посетителю без входа и 403 вошедшему, у которого нет доступа, в обоих случаях с Cache-Control: no-store. Тело страницы прежнее. Закрытая заметка также больше не получает автоматический <meta name="description"> из первого абзаца; используется только явный description во frontmatter.
  • Зачем. Стены отдавались с 200, и поисковики, превью ссылок и API-клиенты принимали их за настоящую страницу. Запасное описание строилось из текста заметки и печаталось в <head> самой стены, так что первые строки закрытой заметки можно было прочитать без доступа.
  • Как. Ничего делать не нужно. Чтобы у закрытой страницы было описание в поиске, задайте description во frontmatter.

Логотип сайта грузится у анонимных посетителей

  • Что. Картинки из _header.md и _footer.md (в любой папке) отдаются посетителям без входа, есть у этих заметок free или нет. Ассеты других системных заметок подчиняются тому же правилу, что у обычной заметки: они анонимные, если заметку можно прочитать без сессии.
  • Зачем. Логотип сайта отвечал 401 каждому анонимному посетителю: ассет считался публичным, только если заметку-владельца можно показать в публичных списках, а системные заметки вроде _header.md туда не попадают никогда. Админ всегда залогинен и проблемы не видел.
  • Как. Ничего делать не нужно. Шапка, подключённая под другим именем через поле header: во frontmatter, по-прежнему требует free: true, чтобы её картинки были публичными.

Ссылки оставляют читателя на том хосте, куда он пришёл

  • Что. На своём домене шапка, подвал, сайдбар, обратные ссылки и запросы в шаблонах теперь строят ссылки так же, как тело страницы: ссылка на заметку того же хоста относительная, а не абсолютный URL. Заметка, открытая по своему route из frontmatter, отдаётся по этому пути, без редиректа на постоянный адрес.
  • Зачем. Общая обвязка рендерилась один раз, для основного домена. На своём домене её ссылки вели на основной домен или на абсолютный адрес, который не открывается на внутреннем или preview-хосте. Редирект с route зависел от того, есть ли в имени файла символы, которые транслитерируются, а на своём домене вёл на 404.
  • Как. Ничего делать не нужно. rel=canonical, og:url и JSON-LD остаются абсолютными.

MCP: стандартный транспорт, аргументы федерации и исправления поиска

  • Что. Эндпоинт MCP /_system/mcp теперь работает на официальном Go MCP SDK (Streamable HTTP, без состояния, ответы в JSON). Имена, описания и схемы инструментов не менялись. Вместе с этим:
    • Федеративные вызовы передают пиру все аргументы. federated_search раньше отправлял только запрос, и limit, detail_limit и подобные терялись; federated_note_html теперь принимает toc_path. Вызов к одному kb_id получил тот же таймаут, что и рассылка по всем.
    • API-ключ видит KB-заметки (mcp_federation_kb_url) без free, как и все остальные заметки. Раньше вызовы federated_* к такой базе отвечали «не настроено».
    • Фрагменты поиска больше не начинаются с битого символа в кириллице и других нелатинских текстах.
    • expand не показывает H1 с заголовком заметки и выводит короткое превью после слишком коротких заголовков. toc_path из search обрезается так же.
    • Заметки, зарегистрированные как инструменты (mcp_method), больше не попадают в результаты поиска.
    • У каждого совпадения в поиске есть section_url, ссылка на найденный заголовок на сайте.
  • Зачем. Самописный слой протокола расходился со спецификацией, а федеративный агент получал не тот результат, что тот же вызов локально.
  • Миграция. Запрос должен содержать Accept: application/json, text/event-stream, без него эндпоинт отвечает 400. Настоящие MCP-клиенты его шлют. Обновите curl-скрипты и свои интеграции (stdio-адаптер в документации обновлён). Инстанс старой версии, который обращается к этому через федерацию, заголовок не шлёт, поэтому обновите обе стороны. Другие заметные изменения: notifications/initialized отвечает 202 с пустым телом, tools/list отсортирован по имени. См. MCP.

Реранкинг включается для каждого поиска отдельно

  • Что. Если реранкер настроен, каждый поиск может его запросить: аргумент rerank у MCP-инструментов search и federated_search и поле rerank в GraphQL SearchInput. Новый ключ vector_search.reranker.default в FEATURES решает, что делать, когда запрос ничего не указал; по умолчанию false. Аргумент MCP показывается, только когда реранкер настроен.
  • Зачем. Кросс-энкодер на CPU тратит около секунды на кандидата, и поиск с top_n 20 шёл около 20 секунд. Агенту, который что-то исследует, это терпимо, человеку у строки поиска нет.
  • Миграция. reranker.enabled: true больше не реранжирует каждый поиск. Чтобы вернуть прежнее поведение, добавьте "default": true в объект reranker. Это касается и memcli up --reranker.

Полнотекстовый индекс может жить на диске

  • Что. Новая настройка SEARCH_INDEX_PATH (--search-index-path) хранит полнотекстовый индекс в этой папке, а не в памяти. После перезапуска сервер открывает готовый индекс, а не строит его заново, а заметки, удалённые, пока сервер стоял, убираются из индекса при следующей загрузке. Пустое значение оставляет индекс в памяти, как раньше.
  • Зачем. Индекс в памяти занимает примерно в 35 раз больше самого текста. На базе с 10 МБ markdown он держал 350 МБ кучи, и большие базы падали по памяти при перезапуске. На диске та же база занимает 4 МБ кучи и около 45 МБ диска, а после перезапуска индекс открывается за миллисекунды.
  • Как. Укажите в SEARCH_INDEX_PATH папку на постоянном томе. Не направляйте два инстанса в одну папку.

Федерация: один ключ передачи, который меняется при установке

  • Что. При добавлении входящего секрета теперь выдаётся один ключ передачи, в котором уже есть адрес и ID ключа; другая сторона вставляет его в Add Outbound. Прежде чем сохранить запись, принимающий хаб просит выдавший пир принять свежий случайный ключ, и значение, прошедшее через чат, перестаёт работать. На странице исходящего ключа есть кнопка Ротировать ключ (то же через rotateFederationSecret), видно, когда ключ меняли последний раз, и что пир выдал, по ответу его нового эндпоинта /_system/mcp/federation. У подграфа появилось описание в одну строку, которое пир видит рядом с именем. Список в админке показывает направление каждого ключа.
  • Зачем. Общий ключ, переданный сообщением, остаётся в истории переписки, а если его передавал агент, то и в его логе. И оператор не мог увидеть, что пара ключей на самом деле открывает.
  • Как. Add Inbound → скопируйте ключ передачи → отправьте → другая сторона вставляет его в Add Outbound. Переключатель Заменить ключ перед сохранением включён по умолчанию; выключите его для пира, который не умеет ротацию (публичная база, адаптер, инстанс старой версии), иначе пир откажет и ничего не сохранится. Ротация с пиром по http:// запрещена, если не разрешены приватные адреса федерации. См. Федерация.

Вебхук-агенты: лог прогона, долгая история, приватные адреса

  • Что. Вебхук-агент может вернуть logs, список записей {ts, level, msg, data}, и админка показывает их на шаге цепочки в разделе Log. На одну доставку хранится до 500 записей или 64 КБ; остальное заменяется одной записью о том, сколько отброшено. Записи доставок и тела запросов и ответов теперь хранятся 90 дней, срок задают --webhook-deliveries-retention и --webhook-delivery-logs-retention. Новый флаг --webhook-allow-private (WEBHOOK_ALLOW_PRIVATE) пускает вебхуки на приватные и внутренние адреса без режима разработки. Заметки, которые агент вернул в теле ответа, теперь запускают вебхуки изменений, SSE и индексацию frontmatter, как любая другая запись, и считаются на уровень глубже в цепочке.
  • Зачем. Оператор видел, что прогон упал, но не видел, что агент делал. Записи исчезали через 30 дней, а тела через сутки, раньше, чем до них доходили руки. Агентов в приватной сети рядом с trip2g блокировала защита от SSRF. Заметки, записанные через тело ответа, не запускали следующую роль в цепочке.
  • Как. Возвращайте logs рядом с changes и costs. Тела теперь хранятся в 90 раз дольше, поэтому заложите место на диске или уменьшите срок.

Fleet входит по личному токену, а агенты не могут писать заметки ролей

  • Что. Fleet аутентифицируется личным токеном админа (--trip2g-admin-personal-token, TRIP2G_FLEET_TRIP2G_ADMIN_PERSONAL_TOKEN), а не JWT-секретом сервера. Задайте на сервере OWNER_PERSONAL_TOKEN_VALUE со значением t2g_..., и при запуске он заведёт этот токен владельцу (OWNER_EMAIL); memcli up генерирует его сам. Fleet отклоняет запись или патч от агента, если в результате появится или изменится заметка роли (любая заметка с fleet_id во frontmatter), а патч применяется, только если заметка не изменилась после этой проверки. --allow-role-authoring отключает защиту. Fleet и codellm также отдают Prometheus /metrics, pprof и проверки здоровья на loopback-адресе: --metrics-addr (fleet 127.0.0.1:18090, codellm 127.0.0.1:18087, CODELLM_METRICS_ADDR); пустое значение отключает.
  • Зачем. С JWT-секретом fleet мог выпустить сессию кому угодно, и отключить его можно было только сменой секрета. Роль сама объявляет свои write_patterns, поэтому агент, которого текст заметки уговорил записать заметку роли, мог выдать себе больше прав.
  • Миграция. У fleet больше нет --jwt-secret, --admin-email и --admin-api-key. Задайте OWNER_PERSONAL_TOKEN_VALUE на сервере и передайте то же значение fleet. Если отозвать этот токен в админке, fleet остановится примерно через полминуты. См. Fleet.

Code-роли: набор инструментов, fleetkit и зашифрованные секреты

  • Что. Образ codellm теперь на Debian, с Python 3, Node 24 и привычными утилитами (jq, sqlite3, git, gh, ripgrep, curl и другие) и библиотеками вроде requests, httpx, pydantic, jsonschema, axios, zod и ajv. Помощник fleetkit для Python и Node собирает вывод code-роли (note, write, patch, emit) и читает её вход (bag, frontmatter, secrets, note_frontmatter). Заметка роли может хранить зашифрованные значения: перечислите их в unseal, и codellm откроет их своим SEAL_KEY (или ключом из unseal_env_key) только на этот прогон. Зашифровать значение можно командой codellm seal или формой /_system/codellm/seal (CODELLM_SEAL_PATH). Роль может сузить набор переменных окружения для своего кода полями env_passthrough и env_prefix, в пределах списка оператора.
  • Зачем. У code-ролей был голый Python без библиотек, заметки и JSON собирались вручную, а каждый новый секрет означал правку деплоя codellm и перезапуск.
  • Как. См. Fleet и Секреты для code-ролей.

Переэмбеддинг всех заметок из админки и ссылка на заголовок

  • Что. На странице Просмотры заметок в админке есть кнопка Переэмбедить все заметки. Она вызывает regenerateNoteEmbeddings(input: { force: true }), который ставит в очередь каждую заметку, даже если её сохранённый хеш говорит, что всё актуально; фрагменты, чей собственный хеш не изменился, модели повторно не отправляются. В стандартном шаблоне при наведении на заголовок рядом появляется ссылка #.
  • Зачем. После изменения разбиения на фрагменты все заметки по-прежнему выглядели актуальными, и новые фрагменты не попадали в векторный индекс. А чтобы сослаться на раздел, приходилось искать его id в оглавлении.
  • Как. Админка → Просмотры заметок → Переэмбедить все заметки. Нужен включённый векторный поиск.

CLI trip2g-sync: команда graphql, а сбой загрузки останавливает синхронизацию

  • Что. trip2g-sync graphql '<query>' ['<variables>'] выполняет GraphQL-запрос к инстансу, на который настроено хранилище, с ключом из .obsidian/plugins/trip2g/data.json; graphql --introspect '<pattern>' показывает подходящие типы схемы. Запросы идут через MCP, где API-ключ несёт свои админские права. Если CLI не смог загрузить список заметок с сервера, он теперь останавливается с ошибкой и кодом выхода 1. Голые ссылки на файлы вроде ![[logo.png]] находят файл в любом месте хранилища (побеждает самый неглубокий), в CLI и в браузерной синхронизации. Скачанное онбординг-хранилище теперь содержит AGENTS.md и CLAUDE.md для ИИ-агентов, которые в нём работают. CLI trip2g-sync (плагин 0.12.0) пишет, что произойдёт с каждой непустой строкой плана: например, что файлы из «Remote only» скачиваются как новые, а заметки из «Local deleted» скрываются на сервере. В справке теперь сказано, что удаление файла локально скрывает заметку на сервере без всяких флагов, а --two-way скачивает заметки, которые есть только на сервере.
  • Зачем. Сбой загрузки читался как пустой сервер: прогон сообщал, что все локальные заметки удалены на сервере, и завершался с кодом 0. Ссылка на файл без папки ничего не находила, если файл не лежал рядом с заметкой.
  • Как. Запускайте из папки хранилища, например node .obsidian/plugins/trip2g/trip2g-sync.mjs graphql '{ viewer { id } }'. Эти изменения CLI выходят с плагином и CLI 0.12.0.

Файлы из заметок отдаёт сам сайт, с проверкой доступа

  • Что. Картинки и другие файлы из заметок и шаблонов отдаются по адресу /_system/assets/<sha256>/<имя файла>. Раньше это были подписанные ссылки на сервер хранилища (MINIO_PUBLIC_URL) или, при локальном хранилище, /_assets/.... Файл, который использует шаблон или заметка, открытая всем, публичный и кешируется на год как неизменяемый. Для остальных файлов нужен вошедший читатель, который может прочитать хотя бы одну заметку с этим файлом, действующий API-ключ в X-API-Key или админ. Такие файлы кешируются только в браузере на 5 минут, потом доступ проверяется снова. Файл, который не использует ни одна текущая заметка или шаблон, доступен только админу. Работают запросы с Range и ETag. Адреса, которые уходят за пределы сайта (посты в Telegram, og:image, JSON-LD, RSS, адреса файлов в ответе pushNotes), дополняются публичным адресом сайта.
  • Зачем. Подписанная ссылка истекала через несколько дней и открывалась у любого, кому её передали, даже если файл из платной или закрытой заметки. Кроме того, сервер хранилища должен был быть доступен из браузера читателя.
  • Как. С контентом ничего делать не нужно: страницы получают новые адреса, как только сервер запущен в новой версии. MINIO_PUBLIC_URL и MINIO_URL_EXPIRES_IN больше не используются, публичный адрес хранилищу не нужен. Старые ссылки, скопированные со страниц до обновления, перестанут работать.

Формы: проверка доступа к заметке, капча без секрета не пропускает, forms: работает

  • Что. submitForm сначала проверяет, может ли посетитель прочитать заметку с формой. Если нет, ответ form_not_found, как для несуществующей формы. Каждое текстовое поле и поле email ограничено 8192 символами, а min_length / max_length теперь считают символы, а не байты. Если задан turnstile-site-key, а turnstile-secret-key пуст, проверка Turnstile теперь не проходит, а не пропускает всё подряд; это касается и капчи при входе по email. Несколько форм на одной заметке через ключ forms: теперь работают.
  • Зачем. Номер версии заметки в отправке легко подобрать, и гость мог отправить форму с платной заметки, заметки только для вошедших или из подграфа. Наполовину настроенный Turnstile выглядел как защита, но ничего не проверял. Размер поля ограничивал только лимит тела запроса. forms: не находился в настоящем frontmatter: YAML-парсер возвращает ключи в типе, которого код не ждал.
  • Как. Ничего делать не нужно. Если задан только ключ сайта Turnstile, добавьте секретный ключ или уберите оба, чтобы отключить капчу. У текста на кириллице, который был близок к max_length, теперь больше места: лимит больше не считает байты. См. Формы.

Без SMTP коды входа пишутся в лог сервера

  • Что. Если SMTP_HOST пуст, запрос кода для входа по email снова работает. Код пишется в лог сервера с уровнем warning вместе с адресом. LOG_SIGN_IN_CODES=true по-прежнему включает запись кодов и при настроенном SMTP.
  • Зачем. В прошлом выпуске запрос кода без SMTP возвращал ошибку, если не задан LOG_SIGN_IN_CODES, и вход по email не работал на dev-стендах и свежих установках.
  • Как. На публичном сервере настройте SMTP: любой, кто читает лог сервера, может войти за любого пользователя, запросившего код. См. Отправка писем (SMTP).

Упавший шаблон отдаёт страницу ошибки

  • Что. Свой шаблон сначала рендерится в буфер. Если рендер падает, читатель не получает из него ничего. Ответ: статус 500 и страница ошибки стандартного шаблона. Админ видит на ней имя шаблона и ошибку Jet со строкой. Остальные видят общую ошибку.
  • Зачем. Jet успевает вывести всё до упавшего выражения. Посетитель мог получить половину страницы, а админ видел текст ошибки, приклеенный к этой половине.
  • Как. Ничего делать не нужно. Чтобы увидеть, что сломалось, откройте страницу под админом.

Предупреждения шаблонов доходят до утилиты синхронизации, критические её останавливают

  • Что. pushNotes теперь возвращает предупреждения каждого файла шаблона, раньше вместо них был пустой список. CLI-утилита синхронизации (trip2g-sync.mjs) выводит предупреждения по файлам, CRITICAL красным, и завершается с кодом 1, если есть хоть одно CRITICAL (например, шаблон, который не разбирается). Новое предупреждение WARNING находит шаблон, где его собственное раскрытое имя @lid написано текстом (mesh-bar в mesh/bar.html): при переименовании файла там останется старое имя, а плейсхолдер переименовался бы вместе с файлом. Совпадения в HTML-комментариях и URL не считаются.
  • Зачем. Сломанный шаблон отправлялся молча, и сайт узнавал об этом при рендере.
  • Как. Обновите CLI и запускайте как раньше. В CI критическое предупреждение шаблона теперь роняет задачу. См. CLI для синхронизации.

coalesce() в шаблонах

  • Что. Новая глобальная функция для шаблонов Jet: coalesce(a, b, ...) возвращает первый аргумент, который задан и не пуст, иначе последний. Пустыми считаются отсутствующий ключ карты, nil, "", пустой список и пустая карта; 0 и false считаются значениями.
  • Зачем. || в Jet возвращает булево значение, поэтому videos[lang] || videos["en"] не мог выбрать значение по умолчанию.
  • Как. {{ coalesce(videos[note.Lang()], videos["en"]) }}. См. Функции Jet.

Диаграммы Mermaid: второй движок, масштаб, полный экран и экспорт

  • Что. Блок-схемы, диаграммы состояний, последовательностей, классов, ER и xychart рисует beautiful-mermaid. Остальные типы (gantt, pie, gitGraph, mindmap и другие) и любая диаграмма, которую он не разобрал, идут через mermaid.js, как раньше. У каждой диаграммы есть кнопки увеличения, уменьшения, сброса, полного экрана и экспорта в PNG / SVG, а саму диаграмму можно перетаскивать. Диаграмма занимает не больше 80% высоты экрана и вписывается в свою рамку.
  • Зачем. Перетаскивание диаграммы никогда не работало. Большие диаграммы сжимались до нечитаемого размера, а широкие и низкие превращались в узкую полоску.
  • Как. Ничего делать не нужно. См. Mermaid.

Сетка карточек: сколько карточек крупных, средних и мелких

  • Что. Три ключа frontmatter на странице с сеткой: magazine_featured (крупные карточки, по умолчанию 1), magazine_grid (средние карточки после них, по умолчанию 4) и magazine_grid_columns (колонки в сетке средних карточек, по умолчанию 2). Остальные заметки идут списком. 0 убирает уровень, а число больше количества заметок просто урезается. Кроме того, в отрывок на карточке больше не попадают таблицы.
  • Зачем. Раскладка была жёсткой: одна крупная карточка, четыре средних и список. Таблица в начале заметки ломала её карточку.
  • Как. Например, magazine_featured: 0 и magazine_grid: 8 дают сетку из восьми карточек без крупной. Значения по умолчанию сохраняют прежнюю раскладку. См. Стандартный шаблон.

Виджеты в списке content:

  • Что. Список content: стандартного шаблона теперь понимает toc, backlinks (или inlinks), outlinks и similar, те же слова, что и боковые панели. Каждое выводит свой блок в основной колонке.
  • Зачем. Шаблон уже умел рисовать эти блоки в основной колонке, но content: принимал эти слова за имена файлов и ничего не показывал.
  • Как. Например, content: [self, backlinks, similar] выводит обратные ссылки и похожие заметки под статьёй.

Выноски в постах Telegram

  • Что. Выноски Obsidian (> [!note] Заголовок) в посте Telegram становятся цитатой с жирным заголовком в первой строке. Выноска без заголовка получает тип с заглавной буквы. Свёрнутая выноска ([!type]-) становится раскрывающейся цитатой, где до раскрытия виден только заголовок.
  • Зачем. Конвертер для Telegram не знал выносок: он выбрасывал весь блок и писал в лог unexpected markdown node.
  • Как. Ничего делать не нужно: пишите выноски, как в Obsidian.

Токен вебхука не выходит за свои write_patterns

  • Что. Временный токен, который вебхук передаёт агенту (Pass API key), теперь ограничен своими write_patterns везде, где он может писать. uploadNoteAsset проверяет, что заметка лежит по пути, куда токену можно писать. pushNotes, hideNotes и commitNotes отказывают такому токену с ErrorPayload: у них нет ограничения по путям. В updateNotes токен без write_patterns больше никуда не может писать.
  • Зачем. Через эти вызовы токен мог писать за пределами своих write_patterns: прикреплять файлы к любой заметке, отправлять или скрывать любую заметку, а в некоторых случаях писать куда угодно через updateNotes, если write_patterns пуст.
  • Как. Агентам, которые пишут через updateNotes в своих путях, ничего делать не нужно. Агент, который вызывал pushNotes, hideNotes или commitNotes с токеном вебхука, должен перейти на updateNotes (заметку скрывает изменение hide). См. Вебхуки при изменении заметок.

Граф заметок: группы, сохранённые позиции и читатели в реальном времени

  • Что. В шапке графа заметок в админке (Notes & Content → Note Graph) есть Auto layout, Save positions, Public outside и поле Group by:. Заметки с одинаковым значением этого ключа frontmatter (по умолчанию subgraph) собираются в одно скопление. Перетащенная заметка сохраняет позицию. Пока граф открыт, переходы вошедших читателей между заметками показываются анимацией. Те же события доступны в GraphQL-подписке readerMoves только для админа (сессия админа или API-ключ инстанса). Читатель виден под анонимным ключом, который меняется каждый час, а не под id пользователя. Пока никто не подписан, сервер не делает для этого никакой работы.
  • Зачем. Заметки располагались случайно, раскладка терялась при каждой перезагрузке, и не было видно, как люди ходят по сайту.
  • Как. Откройте граф, впишите ключ в Group by: и нажмите Auto layout. Чтобы сгруппировать целые папки, добавьте frontmatter-патч. См. Граф заметок.

Флит: роль выбирает флит по fleet_id, код выполняет codellm

  • Что. Роль выбирает флит, который её выполняет, ключом frontmatter fleet_id. Флаг флита --fleet-id (TRIP2G_FLEET_FLEET_ID) теперь обязателен и не имеет значения по умолчанию (раньше это была метка со значением fleet1). Флит выполняет только роли со своим fleet_id. Роль без fleet_id не выполняется нигде, флит сообщает о ней как об ошибке при поиске ролей. Ключа executor больше нет. Код-роли выполняет codellm, отдельный сервис с OpenAI-совместимым API (cmd/codellm, образ Dockerfile.codellm). Флит, у которого --llm-base-url указывает на codellm, выполняет тело роли как код. Сам флит код больше не запускает: флага --allowed-programs у него нет, список интерпретаторов, песочница и доступ в сеть настраиваются в codellm (CODELLM_ALLOWED_PROGRAMS, CODELLM_SANDBOX, CODELLM_SANDBOX_NETWORK). codellm может требовать ключ (CODELLM_API_KEY), флит передаёт его через --llm-api-key или --exec-api-key. Ещё флит отдаёт GraphQL API только для чтения: роли и то, как они запускают друг друга (--graphql-addr, по умолчанию 127.0.0.1:9093), доступ только для админов хаба.
  • Зачем. Выполнение кода и песочница жили внутри процесса агента. В отдельном сервисе код-роль и LLM-роль могут работать на разных хостах, а роль переносится между ними правкой одной строки.
  • Как. До обновления флита: добавьте fleet_id во все роли, запустите по флиту на каждый fleet_id с таким же --fleet-id, для код-ролей запустите codellm и направьте на него --llm-base-url этого флита. Перенесите --allowed-programs в CODELLM_ALLOWED_PROGRAMS у codellm. См. Флит.

Мелкие исправления

  • Отозванный личный токен перестаёт работать сразу. revokeUserToken и adminRevokeUserToken теперь убирают токен из кэша сервера. Раньше отозванный токен работал ещё до 30 секунд.
  • Кастомный домен, на котором нечего индексировать, получает пустой sitemap. Его /sitemap.xml теперь пустой <urlset>. Раньше он отдавал sitemap основного домена со страницами другого сайта. См. Мульти домены.
  • После обновления браузер получает новые встроенные скрипты и стили. Файлы под /assets/ теперь отдаются с ETag и Cache-Control: адрес с текущим хэшем файла в ?h= кэшируется на год как неизменный, любой другой на час, потом перепроверяется. Раньше сервер отвечал «304 Not Modified» на любой If-Modified-Since, потому что у встроенных файлов нет времени изменения, и браузер мог держать старый скрипт после обновления.
  • Быстрее синхронизация файлов. uploadNoteAsset сначала проверяет, сохранён ли файл, и только потом разбирает заметку, а версия layout загружает только файлы из _layouts/, а не все заметки. Повторная синхронизация хранилища с большим числом картинок больше не разбирает заметку заново ради каждой картинки.
  • Коммит в логе запуска. Сервер пишет в лог trip2g starting с git-commit, из которого он собран.
  • Проверка email. Создание и правка пользователя в админке и createHatLink проверяют только формат адреса. MX-запись домена больше не запрашивается, поэтому домен без почты не вызывает ошибку, а форма не ждёт DNS.
  • Длинный инлайн-код. В стандартном шаблоне длинный URL или путь в инлайн-коде переносится, а не расширяет страницу.
  • Даты в админке. Журнал аудита и экраны листов ожидания показывают даты правильно; раньше они форматировались дважды.
  • $mol в шапке. Компонент $mol в шапке больше не занимает всю строку шапки, пока грузятся его стили.
  • Выход из админки. Кнопка Sign out в админке выполняет выход. Раньше она ничего не делала.
  • Заголовки выносок. В светлой теме заголовок выноски окрашен в цвет выноски и читается. Тип выноски, который начинается с нелатинской буквы ([!заметка]), получает правильную заглавную букву в заголовке по умолчанию, и на сайте, и в Telegram.
  • Лимиты Telegram. Отправка и правка постов Telegram (ботом и через аккаунт) делает не больше трёх попыток, если Telegram отвечает ошибкой лимита, а ожидание между ними прерывается при остановке сервера. Раньше число повторов не было ограничено.
  • Хранилище при старте. Сервер ждёт S3-хранилище до 45 секунд, а не завершается на первой неудачной проверке бакета. Неверные ключи или имя бакета по-прежнему останавливают запуск сразу. Имя MINIO_SECRET_ACCESS_KEY принимается как секретный ключ.
  • Пропавшие файлы в хранилище. Если клиент загружает файл, запись о котором уже есть, а данных в хранилище нет (например, бакет очистили), сервер сохраняет файл заново. Раньше он только привязывал запись, и файла так и не было.
  • Имя стартового хранилища. /_system/onboarding-vault?name=<имя> задаёт и имя zip-файла, и папку внутри. Буквы, цифры, точка, дефис и подчёркивание, до 64 символов. Неверное имя даёт 400.

v0.10.0 (2026-07-13)

RSS теперь шаблон, а не Go-фича

  • Что. Старый автоматический фид <адрес-заметки>.rss.xml (по фиду на заметку, элементы = ссылки внутри неё) убран, вместе с настройкой сайта enable_rss. RSS теперь обычный Jet-layout: заметка с frontmatter layout: rss + content_type: application/rss+xml; charset=utf-8 рендерится в RSS 2.0-фид, отдаётся там, куда вы его смаршрутизируете (по умолчанию — /feed.xml). Какие заметки попадают в фид, определяет frontmatter rss_glob/rss_limit, и в него всегда попадают только публично читаемые заметки (free, без sign-in-гейта, без системных).
  • Зачем. Фид «по ссылкам заметки» был жёстким и его было сложно найти. Шаблонный фид полностью кастомизируется — правьте _layouts/rss.html, чтобы поменять состав элемента, или сделайте несколько фидов с разным охватом — тем же layout-механизмом, что и весь остальной сайт.
  • Как. См. ru/user/rss-feeds. Старые ссылки подписчиков на .rss.xml теперь отдают 404, редиректа нет — настройте /feed.xml взамен.

Темы для дефолтного шаблона + живой редактор тем

  • Что. Две новые страницы про смену вида дефолтного шаблона. ru/user/themes объясняет: весь вид задают переменные Pico CSS --pico-* — переопределите их в блоке <style> через админку HTML Injection, и сайт перекрашивается целиком, без пересборки и форка. ru/user/theme-editor показывает живой редактор прямо на сайте: двигаете переменные, видите превью, а под админом жмёте Save — тема записывается в HTML injection вашего сайта. Редактор — это кастомный Jet-layout (layout: theme_editor), его можно поставить на любой сайт из trip2g/theme_editor_template.
  • Зачем. Раньше сменить вид означало читать стили и угадывать нужную переменную. Редактор превращает это в цикл «подвигал — сохранил» и заодно служит примером кастомного шаблона, работающего с admin API (тот же приём, что у канбан-доски).
  • Как. Список переменных и куда вставлять — в ru/user/themes. Попробовать редактор — ru/user/theme-editor; поставить к себе — curl layout из репозитория шаблона и заметка с layout: theme_editor.

Встраивание заметок с кастомным CSS-классом

  • Что. Новая страница ru/user/embedded-notes про ![[имя-заметки]] — содержимое целевой заметки рендерится прямо на странице, обёрнутое в <div class="embedded-note">. Добавьте embed_class: my-class во фронтматтер встраиваемой заметки — обёртка получит ещё и embedded-note__my-class, зацепку для стилей из админки. Любая заметка становится переиспользуемым оформленным блоком: колаут, промо-карточка, общий футер.
  • Зачем. У переиспользуемых блоков (баннер подписки, общий заголовок) не было описанного паттерна. Вместе со скрытыми _-заметками одно редактирование обновляет блок везде, где он встроен.
  • Как. См. ru/user/embedded-notes. Держите блок в _скрытой заметке, добавьте embed_class: для стилей и встраивайте через ![[_block]].

Доступ Telegram-группа ↔ подграф (в обе стороны)

  • Что. Новая страница ru/user/telegram-access про связку Telegram-группы с подграфом: доступ течёт в обе стороны — участники группы читают заметки подграфа на сайте, а читатели с доступом к подграфу получают приглашение в группу. Каждое направление — отдельный переключатель, и оба живые: вышли из группы — заметки закрылись; истёк доступ — фоновая задача убирает вас из группы. В основе — членство теперь отслеживает события Telegram chat_member, поэтому входы и выходы регистрируются сразу, а меню /content бота ведёт на нужные подграфы.
  • Зачем. Гейтинг контента по членству в платной группе был возможен, но плохо описан, а изменения членства не всегда учитывались. Теперь Telegram-группа — полноценный источник доступа, наравне с платным оффером или ручной выдачей.
  • Как. Добавьте бота админом группы, свяжите группу с подграфом в Админка → TG bots (по секции на каждое направление), пометьте заметки subgraph:, а участники открывают закрытые заметки через /content. Полный разбор — в ru/user/telegram-access.

Доступ к сайту по Git

  • Что. Новая страница ru/user/git: каждый сайт trip2g — это git-репозиторий. git clone https://ваш-сайт.com/_system/git, правьте markdown любым инструментом или агентом, коммит, push — сервер применяет ваши коммиты к живому сайту. Авторизация HTTP Basic (user + git-токен из Админка → Integrations → Git tokens), а области токена теперь разделяют pull и push — токен только на чтение клонирует, но не пишет.
  • Зачем. Массовым правкам, CI и кодинг-агентам нужен стандартный git, а не отдельный плагин. Ограниченные токены делают безопасной выдачу URL клонирования только на чтение.
  • Как. Создайте git-токен в админке, git clone URL _system/git и push в ветку master (единственная ветка, которую сервер принимает). Для ежедневного письма в Obsidian по-прежнему удобнее плагин синхронизации. См. ru/user/git.

Рецепты подграфов: приватность по умолчанию, всё бесплатно, скрытые _-заметки

  • Что. В ru/user/subgraphs добавлены три практических рецепта. Патч фронтматтера приватность по умолчанию назначает каждую непомеченную заметку в зарезервированный подграф, к которому ни у кого нет доступа — ничего не утечёт, пока вы явно не опубликуете. Симметричный патч всё бесплатно ставит free: true на весь vault для полностью публичного сайта. И уточнённый раздел описывает: любой файл или папка с именем на _ скрыты из списков, поиска и RSS (но остаются доступны по ссылке и встраиваемы).
  • Зачем. «Какие заметки кому видны» — самый частый вопрос про подграфы. Эти рецепты дают готовые точки старта для двух краёв спектра — приватность-в-первую-очередь и публичность-в-первую-очередь.
  • Как. В Админка → Notes & Content → Frontmatter Patches создайте правило с jsonnet из ru/user/subgraphs (приватность по умолчанию или всё бесплатно) и заведите зарезервированный подграф private без выдач.

v0.9.0 (2026-07-12)

Первый вход на свежем self-host сервере

  • Что. Первый вход администратора на новом сервере больше не требует почтового сервера. Рекомендованный путь: trip2g-server login-link печатает одноразовую ссылку для входа, действующую 5 минут — открываете её прямо из терминала (/_system/hat теперь принимает токен по GET, поэтому ссылка кликабельна в браузере). Хотите вход по коду из письма, но без почтового сервера? Поставьте LOG_SIGN_IN_CODES=true, и сервер пишет коды входа в лог (читаете из journalctl или docker logs). Два сопутствующих фикса: запрос кода без настроенного транспорта теперь возвращает понятную ошибку вместо молчаливого «письмо отправлено», а SMTP_STARTTLS=false работает — код уходит на локальный plaintext-relay (Postfix, MailHog) без навязанного STARTTLS.
  • Зачем. Первый вход на чистом сервере — без домена, без почты — был тупиком: запрос принимался, но код нигде не появлялся, а локальный relay падал на ненужном STARTTLS. Теперь одна команда даёт рабочую ссылку входа, с fallback через лог и честными ошибками.
  • Как. Запустите trip2g-server login-link на сервере (или через docker exec в контейнере) и откройте ссылку в течение 5 минут; повторный запуск даёт свежую ссылку. Fallback: LOG_SIGN_IN_CODES=true, запросите код на странице входа, скопируйте из лога. Уберите эти bootstrap-опции, когда настроите OAuth или реальный почтовый транспорт — см. ru/user/smtp.

Поиск: один движок для сайта и MCP

  • Что. Поиск на сайте (GraphQL) и MCP-инструмент search теперь используют один движок выдачи (текст + векторы + слияние рангов) вместо двух разошедшихся копий. Два изменения поведения для MCP-клиентов: анонимные (и федеративные) клиенты теперь ищут по опубликованным (live) заметкам, как анонимные посетители сайта — раньше поиск шёл по последним версиям, включая черновики публичных заметок; векторный поиск теперь пропускает устаревшие эмбеддинги после смены модели, а не ранжирует их случайно. Неиспользуемая серверная страница /search удалена (поисковый виджет сайта работает через GraphQL и не затронут).
  • Зачем. Исправления и настройка поиска попадали на одну поверхность и молча пропускали другую; агенты и посетители могли видеть разную выдачу по одному запросу. На сайтах с включённым показом черновиков анонимные MCP-клиенты могли читать черновики публичных заметок.
  • Как. Автоматически. Если интеграция полагалась на то, что анонимный MCP-поиск видит черновики, авторизуйте её API-ключом — клиенты с API-ключом по-прежнему ищут по последним версиям.

Руководство по SMTP-провайдерам

  • Что. Новая двуязычная страница документации ru/user/smtp — как выбрать провайдера транзакционной почты для self-hosted инстанса trip2g. Перечислены переменные окружения (SMTP_HOST, SMTP_PORT, SMTP_FROM и другие), разобраны категории провайдеров (dedicated transactional, почта регистратора, бесплатные тиры) и дан однозначный вывод: не запускайте собственный исходящий MTA.
  • Зачем. Доставляемость почты для self-hosted инстансов — частый вопрос на старте. Доступные переменные конфигурации были описаны разрозненно; единого места для сравнения вариантов не было.
  • Как. Прочитайте ru/user/smtp перед настройкой email на новом инстансе.

Instagram-фреймы (instaframes)

  • Что. Для instaframes появились двуязычное руководство и галерея-лендинг. На страницах с каруселью теперь есть виджет редактирования прямо на странице: кнопка открывает колоду слайдов для правки, не уходя с опубликованной страницы. Устаревший reel/teleprompter-layout удалён — carousel остаётся единственным layout для instaframes.
  • Зачем. Руководство и галерея делают функцию обнаруживаемой. Виджет редактирования закрывает петлю обратной связи: увидел опечатку на слайде — исправил здесь же. У reel-layout не было активных пользователей, и он дублировал кодовый путь карусели.
  • Как. Полное руководство и галерея: ru/user/instaframes. Виджет редактирования появляется на страницах карусели автоматически, если вы вошли как владелец сайта.

Исправления в панели администратора

  • Что. Два небольших исправления в admin-дашборде: ссылка на документацию по онбордингу теперь ведёт на нужную страницу (опечатка приводила к 404), а объём хранилища подписан MiB, что соответствует реальным расчётам в коде.
  • Зачем. Ссылка на онбординг была первым, что нажимал новый оператор; попасть на 404 — плохое первое впечатление. Прежний ярлык «MB» подразумевал десятичные мегабайты, тогда как код отдавал двоичные мебибайты.
  • Как. Автоматически после обновления.

Улучшения MCP и федерации

  • Что. Четыре точечных улучшения MCP-эндпоинта и слоя федерации. (1) Новый инструмент federated_instructions позволяет клиенту получить инструкционный текст для любого маршрута с кэшированием на маршрут. (2) Глубина федеративного обхода теперь работает по включающей семантике: запрос с max_depth=2 может пройти ровно 2 прыжка — раньше он останавливался на один прыжок раньше. Запросы, превышающие лимит, теперь возвращают чистый отказ. (3) Hop-rewrite распространяет kb_id вызывающего фрейма на федеративные результаты и ошибки — клиент всегда знает, от какого пира пришёл ответ. (4) Инструменты чтения заметок (note_html, note_text) теперь работают по path или match_id; документально зафиксировано, что note_id — это внутренний целочисленный идентификатор, непригодный для долгосрочных ссылок.
  • Зачем. Ошибка «на единицу» означала, что max_depth=1 не достигал ни одного пира — конфигурация выглядела рабочей, но не работала. Hop-rewrite позволяет строить цепочки атрибуции через федерированные хранилища. Ясные указания по note_id предотвращают кэширование агентами внутренних идентификаторов между синхронизациями.
  • Как. Изменений конфигурации не требуется. Если вы опирались на старую семантику глубины, уменьшите max_depth на единицу, чтобы сохранить прежнее поведение.

Визуализатор обхода графа MCP

  • Что. Новая интерактивная страница документации ru/search_visualizer позволяет наблюдать, как AI-модель обходит федеративный граф в реальном времени. Каждый вызов инструмента отображается как узел; есть мини-карта топологии федерации, быстрый выбор модели, временна́я ось шагов, панель импорта трейсов для воспроизведения сохранённых сессий. Бюджет — 50 шагов, достаточно для глубоких многоуровневых спусков.
  • Зачем. Обход федеративного графа непрозрачен: трудно понять, почему агент выбрал тот или иной путь и откуда взялась задержка. Визуализатор делает обход наблюдаемым.
  • Как. Откройте ru/search_visualizer, выберите модель и начальную базу знаний, введите запрос. Обход разворачивается узел за узлом. Трейс можно скачать и воспроизвести позднее.

memcli: локальный векторный поиск

  • Что. memcli теперь включает нативный arm64-ретривер, который выполняет векторный поиск локально — без обращений к внешнему сервису. Ретривер обрабатывает и эмбеддинги, и (опционально) ранжирование.
  • Зачем. Векторный поиск на локальном инстансе memcli раньше требовал сетевого вызова к серверу эмбеддингов. Встроенный ретривер делает локальный семантический поиск автономным на Apple Silicon.
  • Как. Ретривер запускается автоматически с memcli up на arm64 macOS. Дополнительная настройка не требуется. Гайд: ru/user/memcli.

Ретривер: поддержка Metal/MPS и безопасные сниппеты для UTF-8

  • Что. Ретривер-сайдкар теперь запускает инференс на Metal (Apple GPU) и MPS с точностью fp16 и исправлениями стабильности для micro-batch. Сниппеты результатов поиска обрезаются по границам rune, поэтому многобайтовые символы (кириллица, CJK, эмодзи) больше не обрезаются посередине кодпоинта.
  • Зачем. Инференс на CPU был в 3–10 раз медленнее, чем на GPU. Усечённый UTF-8 приводил к искажённому тексту сниппетов в многоязычных хранилищах.
  • Как. Автоматически. Ретривер выбирает лучшее доступное устройство при старте. Изменений конфигурации не требуется.

Операционные метрики: конвейер эмбеддингов и очереди задач

  • Что. На внутреннем эндпоинте метрик появились две новые группы Prometheus-метрик. (1) Метрики конвейера эмбеддингов — количество чанков, задержка и процент ошибок для каждого прохода эмбеддинга. (2) Метрики глубины очередей задач — сколько задач ждёт в каждой именованной очереди. Дополнительно: запросы на эмбеддинг чанков теперь разбиваются на под-батчи по максимальному размеру, который объявляет сервер ретривера, — это предотвращает шторм повторного эмбеддинга при лимите батча ниже числа чанков.
  • Зачем. Без метрик глубины очередей у операторов не было раннего предупреждения о росте бэклога. Фикс под-батчинга устранял цикл падений: oversized-батч → отказ эмбеддера → полный повторный эмбеддинг → снова oversized-батч.
  • Как. Новые метрики появляются на существующем эндпоинте без изменений конфигурации. Обновите дашборды, добавив панели глубины очередей и задержки эмбеддингов.

Telegram: навигационный браузер только для публичных live-заметок

  • Что. Встроенный в Telegram навигационный браузер теперь показывает только публичные live-заметки. Раньше в нём могли появляться приватные заметки и черновики.
  • Зачем. Пользователь, нажавший навигационную ссылку в Telegram, должен попадать на доступный ему контент. Черновики и приватные заметки открывали битые или закрытые страницы.
  • Как. Автоматически. Черновики и приватные заметки больше не отображаются в навигационном браузере Telegram.

v0.8.0 (2026-07-01)

Флот агентов (fleet)

  • Что. Заметки trip2g теперь могут запускать LLM-агентов. Вы пишете «роль-заметку» в папке (по умолчанию roles/): frontmatter описывает конфигурацию (модель, инструменты, шаблоны путей чтения/записи, триггер, лимиты бюджета, политика конкурентности), тело содержит инструкцию в виде Jet-шаблона против контекста триггера. Демон fleet обнаруживает роль-заметки, согласует change/cron-вебхуки и при срабатывании триггера запускает ограниченный агентный цикл с инструментами: search, read_note, patch_note, write_note. Инструмент executor: code (например, Python) отключён по умолчанию и контролируется через --allowed-programs / TRIP2G_FLEET_ALLOWED_PROGRAMS.
  • Зачем. Хранилище превращается в поверхность автоматизации (обработка транскриптов, построение баз знаний, сортировка, суммаризация) без внешней оркестрации. Trip2g остаётся простым источником событий; fleet является единственной движущейся частью.
  • Как. Бинарный файл fleet поставляется в образе trip2g по адресу /fleet и как отдельная загрузка. Настройте через переменные окружения TRIP2G_FLEET_* (или флаги cmd/fleet); аутентификация через admin HAT, сгенерированный из JWT-секрета приложения. Гайд: ru/agents_how_it_works.

Готовые бинарные файлы с контрольными суммами

  • Что. К каждому релизу на GitHub теперь прикреплены готовые архивы: linux amd64/arm64, macOS arm64/amd64, Windows amd64. В каждом архиве находятся trip2g-server и fleet плюс файл .sha256.
  • Зачем. Trip2g можно запустить без Docker: единственный бинарный файл на любой из пяти целевых платформ.
  • Как. Скачайте архив для своей ОС со страницы Release, проверьте через sha256sum -c *.sha256, распакуйте и запустите. macOS-бинари не подписаны: Gatekeeper предупредит при первом запуске; откройте через правый клик → «Открыть», чтобы разрешить.

trip2g lint docs

  • Что. Новая подкоманда lint docs запускает реальный загрузчик заметок над деревом документации и сообщает о проблемах: утечки межъязыковых вики-ссылок (русская заметка ссылается на английскую страницу), ошибки рендеринга layout-ов, битые ссылки (информационный режим). Заменяет прежний bash-скрипт check-doc-lang-links.sh; встроена в CI.
  • Зачем. Обнаруживает проблемы публикации до того, как они попадут в эфир: русская заметка ссылается на английскую страницу, layout не рендерится.
  • Как. trip2g lint docs (или go run -tags dev ./cmd/server lint docs из исходников). Код выхода ненулевой при предупреждениях; битые ссылки на ещё несозданные заметки считаются информационными и не блокируют проверку.

Метрики базы данных и более строгий SQL

  • Что. На существующем эндпоинте метрик теперь доступны три новые Prometheus-метрики: состояние пула соединений, размер файла базы данных и размер WAL. Двойные кавычки в SQL-строках отклоняются прагмой _dqs при старте, чтобы SQL-опечатки выявлялись сразу. Последний прямой импорт mattn/go-sqlite3 удалён из тестового инструментария; приложение работает на pure-Go modernc с более ранней версии.
  • Зачем. Метрики дают операторам видимость нагрузки на базу до того, как она стала инцидентом. Прагма _dqs превращает целый класс тихих SQL-ошибок в явную ошибку запуска.
  • Как. Метрики появляются на существующем эндпоинте внутренних метрик без изменения конфигурации. Прагма _dqs может обнаружить имеющиеся SQL-опечатки при обновлении; проверьте логи запуска, если сервер отказывается стартовать.

Поиск: cross-encoder reranker удалён

  • Что. Опциональный второй этап ранжирования на основе cross-encoder (добавлен по умолчанию отключённым в одном из предыдущих релизов) удалён вместе с Python-сайдкаром reranker-server/.
  • Зачем. Два раунда бенчмаркинга показали снижение качества поиска: nDCG упал с 0.9221 до 0.8881 на отрывках в 512 символов и до ~0.39 на полных текстах заметок. Cross-encoder переоценивал поверхностное совпадение терминов и продвигал тематически близкие, но нерелевантные результаты, которые bi-encoder + BM25 + RRF корректно располагал ниже. Подробнее: docs/dev/reranker.md.
  • Как. Ничего делать не нужно: функция была отключена по умолчанию. Любые ключи vector_search.reranker.* теперь игнорируются.

v0.7.1 (2026-06-26)

Широкие страницы (wide: true)

  • Что. Любую заметку теперь можно отрисовать на всю ширину, добавив wide: true во frontmatter: страница убирает обе боковые панели и узкую читаемую колонку, и контент занимает всё основное поле.
  • Зачем. Широкий контент (kanban-доски, большие Mermaid-диаграммы, широкие таблицы) был зажат в дефолтной колонке 65ch.
  • Как. Добавьте wide: true во frontmatter заметки. Гайд: ru/user/default-template.

Инструмент превью layout-ов: trip2g-preview, замена renderlayout.py (19483421, d0fe4179)

  • Что. Новый standalone node-CLI scripts/trip2g-preview.mjs рендерит Jet-layout против заметки через /_system/renderlayout и добавляет режим --watch: пере-POST-ит при каждом сохранении, а открытая по ?live страница перезагружается сама. Заменяет старый scripts/renderlayout.py (удалён), без зависимости от Python.
  • Зачем. Ускоряет цикл вёрстки: правишь _layouts/*.html и сразу видишь результат, ловишь ошибки Jet в терминале. Работает против любого сервера: локального memcli (читает data.json) или удалённого/staging через --api-url/--api-key.
  • Как. node scripts/trip2g-preview.mjs --watch --layout-file _layouts/article.html --note-path /about, затем открыть напечатанный ?live URL (с memcli сначала memcli open, чтобы браузер был залогинен). Гайд: ru/user/renderlayout; два локальных дизайн-цикла описаны в docs/dev/local_design_iteration.md.

memcli: изолированные инстансы через --name (5f0542f8, 353841e0)

  • Что. memcli up --name <id> поднимает второй инстанс в своём контейнере trip2g-memory-<id>. Тот же --name передавайте в down/status/logs и задавайте отдельный --port. Без --name поведение по умолчанию (trip2g-memory) не меняется.
  • Зачем. Позволяет держать несколько локальных memcli-инстансов рядом (например, по одному на vault), не конфликтуя за единственное захардкоженное имя контейнера.
  • Как. node cli/memcli/dist/memcli.js up --folder ./v2 --name v2 --port 24381. Состояние хранится отдельно для каждого --folder. Гайд: ru/user/memcli.

v0.7.0 (2026-06-23)

Режим read replica (a4423895)

  • Что. Второй инстанс trip2g теперь может работать как реплика только для чтения. Задайте TRIP2G_LEADER_ADDR на нём: он будет обслуживать все GET-запросы из локальной копии SQLite (реплицируемой через LiteFS) и пересылать каждую запись на первичный инстанс.
  • Зачем. Это снижает задержку на чтение при нагрузке на primary, позволяет перезапускать primary без потери ни одного читательского запроса и открывает возможность горизонтального масштабирования на отдельных машинах. В тестах во время перезапуска лидера не было зафиксировано ни одного сбоя чтения на реплике.
  • Как. Запустите LiteFS на обоих серверах (реплика получает потоковую копию WAL SQLite). Запустите реплику с --leader-addr=http://10.x.x.x:8082 --leader-shared-secret=.... Значение --leader-shared-secret должно совпадать с TRIP2G_LEADER_REPLICA_SECRET на primary. Полная инструкция по настройке: ru/user/read-replica.

Вход через OIDC / корпоративный SSO (1d2b12b5, b6446f76, e6d7afe9)

  • Что. Пользователи теперь могут входить с корпоративной учётной записью через Authentik, Keycloak или любой стандартный OpenID Connect-провайдер. На странице входа появляется кнопка «Войти через SSO», когда OIDC-провайдер настроен. Автопровизиинг опционален: при включении валидная идентичность IdP создаёт пользователя trip2g автоматически; при отключении пускаются только предварительно заведённые аккаунты.
  • Зачем. Команды с корпоративным IdP больше не управляют отдельными паролями trip2g, а MFA обеспечивает сам IdP.
  • Как. Задайте OIDC_CLIENT_ID, OIDC_CLIENT_SECRET и OIDC_DISCOVERY_URL на сервере. Строка в базе данных не нужна: провайдер читается из переменных окружения при каждом запросе. CRUD учётных данных администратора доступен через GraphQL-мутации для установок, которым это нужно. Полная инструкция: ru/user/oidc.

Хранилище на локальной файловой системе (0f5b84e2)

  • Что. Trip2g теперь может хранить загруженные ассеты (изображения, вложения) на локальной файловой системе вместо S3 или MinIO. Задайте STORAGE_BACKEND=local (или --storage-backend=local) и при необходимости укажите путь через STORAGE_LOCAL_PATH.
  • Зачем. Работа без объектного хранилища убирает главную внешнюю зависимость для одиночного сервера. Дешёвый VPS с подключённым томом становится полноценным автономным деплоем.
  • Как. Добавьте STORAGE_BACKEND=local в файл переменных окружения. Путь по умолчанию: ./data/storage; переопределите через STORAGE_LOCAL_PATH. При переходе с S3 нужно предварительно скопировать содержимое бакета в локальный путь. Полная инструкция для одного сервера: ru/user/local-quickstart.

Деплой без даунтайма (08a6cfde, 22dd8cf3)

  • Что. Trip2g теперь поддерживает socket activation systemd (LISTEN_FDS) и публикует два health probe на отдельном внутреннем порту: /livez (всегда 200, даже в период прогрева) и /readyz (503 до тех пор, пока инстанс не прогрелся, и снова во время дрейна). Вместе они дают балансировщику или оркестратору сигналы, необходимые для переключения трафика со старой версии на новую без потери запросов.
  • Зачем. Раньше при rolling restart возникал разрыв: старый процесс уже останавливался, пока новый ещё прогревал кэш заметок. Теперь новый инстанс прогревается параллельно, и трафик переходит только после того, как /readyz вернул 200.
  • Как. Добавьте socket unit в systemd, чтобы ОС удерживала порт в момент перезапуска. Задайте --internal-listen-addr=:8081 и настройте балансировщик на ожидание /readyz перед маршрутизацией. --shutdown-grace-period управляет тем, как долго старый инстанс продолжает обслуживать запросы после того, как /readyz переключился на 503. Используйте --simple-backup-on-shutdown=false, чтобы пропустить финальный бэкап во время rolling restart. Готовые рецепты (Nomad/Traefik, Caddy, k3s): ru/user/zerodowntime.

memcli: CLI для агентной памяти (a4e6fd1e, 951d52ed)

  • Что. memcli запускает инстанс trip2g как долгосрочную память для AI-агентов одной командой. Одна команда (node memcli.js up --folder ./memory) запускает Docker-контейнер с локальным хранилищем, создаёт admin-ключ через HAT, стартует sidecar trip2g-sync --watch и записывает заметку hub.md в memory-vault. Подкоманда hub и MCP-инструмент memory_bind_hub привязывают инстанс к удалённому federation hub, чтобы поисковые запросы агента охватывали федеративные базы знаний через единственный MCP-эндпоинт.
  • Зачем. Раньше настройка отдельного trip2g для агента требовала четырёх ручных шагов (сервер, ключ, синхронизация, MCP-конфиг). memcli сворачивает их в одну команду. Скомпилированный dist/memcli.js поставляется в репозитории, шаг сборки не требуется.
  • Как. node cli/memcli/dist/memcli.js up --folder ./memory-vault. После завершения MCP-эндпоинт доступен по адресу http://localhost:24081/_system/mcp. Добавьте его в .mcp.json вашего агента. Полная документация: ru/user/agent-memory.

Стабильность gitapi-зеркала (2845bb82, 8082c24d)

  • Что. Два исправления в зеркале DB→git (gitapi). Если вызов materialize завершается с ошибкой на полпути, осиротевшие loose-объекты теперь очищаются перед возвратом ошибки. После успешного materialize сразу запускается gc для упаковки loose-объектов. Раньше неудачные запуски могли оставлять orphaned-файлы, накапливавшиеся с каждой ночной перестройкой и в итоге заполнявшие диск.
  • Зачем. На инстансах с большим числом заметок повторные сбои materialize приводили к бесконтрольному росту директории git-данных. У операторов с небольшим VPS возникало давление на диск, требовавшее ручной очистки.
  • Как. Ничего делать не нужно. Оба поведения автоматические после обновления. Если наблюдался рост диска от осиротевших объектов, запустите git gc --prune=now в директории репозитория (или дождитесь следующего запуска materialize при перезапуске сервера).

v0.6.1 (2026-06-22)

Живые обновления в плагине Obsidian (плагин v0.5.0)

  • Что. Плагин trip2g sync теперь использует подписку noteChanges (появилась в v0.6.0) и подтягивает изменения с сервера в хранилище в реальном времени, без нажатия синхронизации и без ожидания периодической проверки.
  • Зачем. Для работы с нескольких устройств и правок агентами изменение на сервере (или на другом устройстве) появляется в Obsidian за секунду, а не после следующего опроса. Это изменение UX: мгновенная свежесть и меньше холостого трафика, а не прирост сырой скорости. Запрос списка заметок и так был чтением по индексу на ~10 мс.
  • Как. Обновите плагин до 0.5.0 (через BRAT), включите Two-way sync, затем задайте шаблоны Live pull в настройках плагина (include/exclude по glob, например **, или blog/** с исключением drafts/**). Безопасность сохранена: локальные правки никогда не перезаписываются (показывается окно конфликта), а удаление на сервере сначала спрашивает. Опрос раз в 60 секунд заменён на более лёгкую сверку-страховку раз в 5 минут. Документация: docs/ru/user/Двухсторонняя синхронизация.md. История: docs/ru/thoughts/sync-benchmark.md.

Намного быстрее массовая и CLI-синхронизация заметок с ассетами

  • Что. Синхронизация множества заметок с картинками стала радикально быстрее из CLI и browser-sync. Холодный пуш 2000 заметок с 2000 картинок упал с 231.8 с до 8.8 с (~26×).
  • Зачем. Каждая загрузка ассета запускала полный reload всех заметок на сервере, потому что CLI и browser-sync не батчили загрузки так, как уже делал плагин Obsidian. Один забытый флаг (skipCommit) превращал 2000 загрузок в 2000 полных reload-ов.
  • Как. Ничего делать не нужно, кроме обновления до плагина/CLI 0.5.0. Интерактивный плагин Obsidian был не подвержен.

Стабильность: подписка реального времени могла ронять сервер (c283963b)

  • Что. Гонка в in-process шине событий за подпиской noteChanges могла уронить весь сервер, когда подписчик отключался во время сохранения (send on closed channel).
  • Зачем. Пока подписчиков не было, она не стреляла, но новый плагин live-pull подключается и отключается регулярно, что делало баг реальным риском для всех, кто использует подписку.
  • Как. Ничего делать не нужно; шина теперь использует отдельный done-канал на подписчика и прогнана под нагрузкой с детектором гонок. Также в плагин добавлена команда Sync now (палитра команд).

v0.6.0 (2026-06-17)

Диаграммы Mermaid

  • Что. Блок ```mermaid в заметке рендерится как диаграмма на опубликованной странице: блок-схемы, диаграммы последовательности, круговые, Ганта, классов, состояний, ER и все остальные типы, которые поддерживает Mermaid.
  • Зачем. Диаграммы пишутся так же, как в Obsidian. Блок работает сразу.
  • Как. Добавьте блок кода с тегом mermaid и вставьте синтаксис диаграммы внутрь. Библиотека Mermaid загружается лениво, только на страницах, где есть блок mermaid; страницы без диаграмм ничего не платят. Документация: docs/en/user/mermaid.md, docs/ru/user/mermaid.md.

Графики из блоков datachart (7cadad3e, cb1403f5)

  • Что. Блок ```datachart превращается в интерактивный график на опубликованной странице (движок Apache ECharts). Данные берутся из четырёх источников: inline (строки прямо в блоке); frontmatter (файл из vault-а по [[вики-ссылке]] в frontmatter); url (внешний HTTP-JSON эндпоинт, который trip2g запрашивает на сервере и кеширует); internal (данные вашего сайта через SQL, скоро). Ошибки при загрузке URL фиксируются и видны автору сразу при синхронизации, а не только в браузере (97997bb4).
  • Зачем. Публикуйте живые графики так же естественно, как пишете любую заметку в Obsidian. Виджет ECharts загружается лениво, только на страницах с блоком datachart.
  • Как. Добавьте блок с тегом datachart. Внутри разместите JSON-объект с ключами data и config. Объект config передаётся прямо в ECharts: работают все типы графиков и опции, которые он поддерживает. Документация: docs/en/user/chartdata.md, docs/ru/user/chartdata.md.

MCP Federation: эндпоинт топологии для администраторов и настраиваемый kb_id (4485b6cd, 5c7492ae)

  • Что. Два дополнения к федерации, появившейся в v0.4.1. Новый эндпоинт GET /_system/federation/admin (только для администраторов) возвращает полную топологию федерации: все зарегистрированные KB-пиры, их скоупы и статус доступности. Идентификатор kb_id вашего инстанса теперь настраивается вручную; если не задан, берётся хост публичного URL.
  • Зачем. Операторы нескольких федеративных инстансов видят топологию без погружения в базу данных. Явный kb_id даёт стабильный, читаемый идентификатор, который остаётся корректным независимо от домена инстанса.
  • Как. Эндпоинт требует admin API-ключ или сессию. Задайте kb_id в конфигурации инстанса, чтобы переопределить значение по умолчанию. Существующие федеративные настройки менять не нужно.

SSE-подписки на изменения заметок в реальном времени (854c56b0)

  • Что. Новая GraphQL-подписка noteChanges передаёт клиентам события upsert и удаления заметок через SSE с опциональной фильтрацией по glob. Каждое событие содержит diff изменённых HTML-селекторов, чтобы клиент мог точечно обновить DOM без перезагрузки страницы.
  • Зачем. Позволяет строить интерфейсы, которые отражают изменения vault-а в момент, когда они попадают на сервер: редактор в браузере, панель предварительного просмотра, кастомный дашборд.
  • Как. Подпишитесь на noteChanges(glob: "posts/**") через GraphQL SSE-эндпоинт. Поле changedHtmlSelectors в каждом событии перечисляет CSS-селекторы, чей рендер изменился; это позволяет обновлять только нужные части страницы.
  • Что. В редакторе файлов в браузере (появился в v0.5.0) Ctrl+Click (или Cmd+Click на macOS) по wikilink открывает связанную заметку.
  • Зачем. Повторяет поведение Obsidian и убирает необходимость искать нужный файл вручную.
  • Как. Откройте редактор, наведите курсор на любую [[вики-ссылку]]; курсор меняется на указатель. Нажмите Ctrl+Click, и связанная заметка откроется.

v0.5.1 (2026-05-27)

Секреты вебхуков: зашифрованные данные в теле доставки (0b72acf2)

  • Что. У каждого change-вебхука и cron-вебхука в админке появился раздел Секреты. Добавляйте именованные пары ключ-значение (например auth_token, api_key); они хранятся зашифрованными и отправляются в каждом запросе доставки в поле payload.secrets. Получатель вебхука читает их без дополнительных вызовов API.
  • Зачем. Cron-вебхуки служат основой системы плагинов. Плагин представляет собой веб-сервер (или serverless-функцию), который получает payload с временным API-токеном и обрабатывает его, как правило в фоне, а затем патчит хранилище, когда готов. Плагины чаще всего stateless: им некуда безопасно хранить собственные учётные данные. Секреты решают эту проблему: trip2g хранит их зашифрованными и доставляет при каждом вызове, так что плагин остаётся credential-free на своей стороне. Если плагину нужно больше времени, он использует API-токен для асинхронных обновлений; секреты дают ему всё остальное для работы с внешними сервисами.
  • Как. Откройте Admin → Change Webhooks (или Cron Webhooks) → выберите вебхук → прокрутите до раздела Секреты. Введите название и значение, нажмите Add Secret. Чтобы обновить значение, введите его в поле строки и нажмите Save. Чтобы удалить, нажмите иконку корзины (подтверждение на второй клик). Секреты появляются в теле доставки в виде { "secrets": { "auth_token": "...", "api_key": "..." } }.

v0.5.0 (2026-05-26)

Редактор файлов в браузере (для администраторов)

  • Что. Администратор теперь может отредактировать любую страницу прямо на сайте. Иконка редактора появилась в правом верхнем углу админки и рядом с поиском на каждой странице. Откройте редактор, чтобы просмотреть все загруженные файлы деревом папок, открыть и отредактировать любой и сохранить. Файл можно откатить к прошлой версии.
  • Зачем. Поправить опечатку или обновить страницу за секунды. Без Obsidian и повторной синхронизации.
  • Как. Нажмите иконку редактора (только админы). Заметка текущей страницы открывается сразу; любой другой файл выбирается в дереве слева. Правки хранятся в браузере, пока не нажмёте «Сохранить»; панель версий позволяет загрузить и восстановить старую версию.

Публичный хаб курируемых баз

  • Что. Раздел hub/ с двуязычным индексом баз знаний, доступных через хаб (первая запись: «Журнал Ника Сенина», отфильтрованные кейсы Code with Claude 2026).
  • Зачем. Просматриваемая публичная точка входа к федеративным базам.
  • Как. См. docs/ru/hub/_index.md; добавьте свою через docs/ru/hub/_create.md.

v0.4.1 (2026-05-25)

MCP Federation: один хаб поверх многих баз знаний

  • Что. Ваш инстанс может работать как федеративный хаб. KB-заметка (заметка с mcp_federation_kb_url во frontmatter) регистрирует другую MCP-совместимую базу, а federated_search / federated_similar / federated_note_html обращаются ко всем сразу через ваш единственный MCP-эндпоинт. Публичным базам аутентификация не нужна; приватные пиры используют общий HMAC-секрет.
  • Зачем. Один эндпоинт, одна точка авторизации: агент ищет по вашим заметкам, инстансам партнёров и внешним адаптерам (GitHub, Telegram) одновременно, без правки .mcp.json.
  • Как.
    • Документация: docs/en/user/federation.md, docs/ru/user/federation.md
    • Публичная база: создайте заметку с mcp_federation_kb_url (+ опционально mcp_federation_kb_id) и free: true.
    • Приватный пир: обменяйтесь федеративным секретом в Админка → Federation, затем добавьте KB-заметку.

Файлы Canvas (Base и Excalidraw позже)

  • Что. Файлы .canvas синхронизируются и рендерятся. .base и .excalidraw тоже принимаются синхронизацией, но их рендеринг появится позже; пока вместо сломанной страницы показывается понятная заглушка.
  • Зачем. Vault-ы с Canvas работают уже сейчас; Base и Excalidraw синхронизируются без ошибок, пока готовится полная поддержка.
  • Как. Просто синхронизируйте: плагин и CLI принимают все три расширения; Canvas рендерится уже сейчас.

Telegram-боты навигации и canvas

  • Что. Бот-браузер по wikilink-ам и навигация на основе canvas поверх Telegram business connection.
  • Зачем. Читатели могут ходить по графу вашего vault прямо из Telegram.
  • Как. См. документацию по Telegram; включите на business connection.

Админка и конфиг

  • Что. GraphQL API для истории версий заметки; фильтр отправок форм в админке (статус / дата / обработано); переменные окружения принимают префикс TRIP2G_ (неизвестные переменные вызывают предупреждение).
  • Зачем. Смотреть и откатывать контекст, разбирать отправки и безопаснее настраивать self-hosted инстансы.
  • Как. Панели админки; добавьте префикс TRIP2G_ к любой переменной окружения.

Плагин синхронизации Obsidian + CLI

  • Что.
    • Принимает файлы .canvas, .base, .excalidraw.
    • Показывает детали GraphQL-ошибки при неудачном push (больше нет «тихих» сбоев).
    • Новый флаг --exclude <glob> (можно повторять). Исключённые пути никогда не пушатся; если они уже есть на сервере, они скрываются. Простое имя вроде dev совпадает с этой папкой и всем внутри неё. По умолчанию ничего не исключается: грузится всё.
  • Зачем. Держать тестовые/демо или внутренние папки (например, dev/, demo/) в репозитории, но вне опубликованного сайта, и обратимо скрывать их на сервере.
  • Как. trip2g-sync ./docs --exclude dev --exclude demo. Если вернуть путь обратно, он снова публикуется и автоматически перестаёт быть скрытым.

v0.4.0 (2026-05-21)

Мутация updateNotes: атомарный find/replace по заметкам

  • Что. Новая GraphQL-мутация патчит несколько заметок одной транзакцией через PathMap вида {path → [{find, replace}]}.
  • Зачем. Позволяет внешним инструментам, агентам и скриптам согласованно править весь vault без отдельных запросов на каждую заметку. Никаких частичных состояний, если одна замена не прошла.
  • Как.
    • Документация: docs/en/user/update_notes.md, docs/ru/user/update_notes.md
    • Пример: updateNotes(input: { pathMap: { "notes/post.md": [{ find: "old", replace: "new" }] } }).
    • End-to-end спецификация: e2e/updatenotes/* (см. коммит test(e2e): add updateNotes e2e spec and demo fixture).

Forms admin: обработка отправок формы

  • Что. Новая мутация markFormSubmitProcessed и поля processed у submit-ов; админ помечает отправку как обработанную, UI по умолчанию скрывает обработанные.
  • Зачем. Замыкает работу с формами внутри админки. Не нужно дёргать внешние сервисы.
  • Как.

Smoke-render шаблонов: runtime-ошибки Jet видны сразу при загрузке

  • Что. При загрузке заметок каждый успешно распарсенный Jet-шаблон выполняется на первых 10 заметках, которые ссылаются на него через frontmatter layout:. Ошибки и паники рендера превращаются в NoteWarning на шаблоне.
  • Зачем. Раньше шаблон, который парсится, но падает на рендере (например, {{ note.NoSuchField }}), ломался только при открытии страницы в браузере. Агент, делающий push-notes, об этом не знал. Smoke-render переносит ошибку на этап загрузки: варнинги попадают в тот же канал, что и parse-ошибки, и видны через pushNotes / админский список шаблонов без браузера.
  • Как. Автоматически, без флагов. После синхронизации ищите в варнингах шаблона smoke render error / smoke render panic. Шаблоны без View (parse-ошибка) и без ссылающихся на них заметок пропускаются.

Отладка шаблонов: Meta.Debug() и глобальный debug()

  • Что. В Jet-шаблонах: {{ Meta.Debug() }} выводит метаданные заметки; глобальный {{ debug(<expr>) }} через reflection печатает тип, значение и набор методов любого выражения.
  • Зачем. Убирает цикл «угадай, что видит шаблон» при работе с layout-ами и компонентами.
  • Как.

CLI renderlayout.py: рендер шаблона на заметке из терминала

Исправления

  • layoutloader: nil-guard для YieldNode.Parameters; ID шаблона в preview нормализуется как в продакшене.
  • Preview renderlayout: autoimport, проводка yield_blocks и htmlInjections совпадают с продакшеном.
  • renderpreview парсит YAML-frontmatter из note.src как реальный загрузчик.
  • templateviews.GetStrings возвращает пустой слайс вместо nil (нет сюрпризов при итерации в шаблоне).

Документация и chore

  • Dev-справка по Forms + roadmap, BEM rendering skill, секция про debug() в Jet.
  • Lint чист в updatenotes, layoutloader, noteloader.

v0.3.1 (исторический backfill)

Формы в заметках (первая версия)

  • Что. Формы встраиваются в vault-заметки через frontmatter и рендерятся стандартным шаблоном. Поддержка нескольких форм в одной заметке; submit хранит note_version_id и form_id.
  • Зачем. Опубликованный сайт собирает ввод (регистрации, контакты, опросы) без внешних сервисов.
  • Как. См. docs/dev/forms.md и docs/{en,ru}/user/forms.md.

HTML-инъекции в layout-ах

  • Что. Кастомные HTML-инъекции (placement head / body_end) теперь применяются и внутри Jet-layout-ов, не только в стандартном шаблоне.
  • Зачем. Аналитика, кастомные скрипты и SEO-теги работают и на страницах с кастомными layout-ами.
  • Как. Админка → HTML injections, placement head или body_end.

Переработанный переключатель языка

  • Что. Dropdown с полными нативными названиями языков и нормализацией; флаг US для английского; многоязычные документы.
  • Зачем. Мультиязычные сайты выглядят корректно и правильно выбирают альтернативу по языку.
  • Как. Frontmatter lang: / lang_redirect:; переключатель рендерится автоматически.

Preview-эндпоинт renderlayout (/_system/renderlayout)

  • Что. Админский эндпоинт для рендера произвольного layout-а на заметке.
  • Зачем. Редактор / IDE-интеграции могут показывать живой preview изменений в _layouts/*.
  • Как. Спецификация: docs/superpowers/specs/2026-05-10-renderlayout-endpoint-design.md.

Админка: включение/отключение API-ключей

  • Что. GraphQL-мутация и кнопка в админке для переключения состояния API-ключа; автоматическая чистка логов API-ключей через 90 дней.
  • Зачем. Можно приостановить ключ без его ротации; логи не разрастаются.
  • Как. Админка → панель API keys.

Onboarding vault: конфиги для агентов

  • Что. Скачиваемый онбординг-vault теперь содержит .mcp.json, codex.json, antigravity-конфиг и AGENTS.md.
  • Зачем. Drop-in настройка AI-агентов над Obsidian-vault без ручной проводки.
  • Как. Скачайте онбординг-vault, конфиги уже внутри.

Cronjob-ы заблокированы для редактирования по умолчанию

  • Что. Редактирование cronjob-ов выключено, пока не передан --cronjobs-allow-edit.
  • Зачем. Снижает риск ошибок в общих / production-развёртываниях.
  • Как. Добавьте флаг в стартовый скрипт, если правда нужно редактировать cron через админку.

Заметные исправления

  • Backlinks / similar notes исключают системные заметки (пути с _).
  • TOC-якоря снова работают (id у заголовков попадает в HTML).
  • Mesh template: yield_blocks перенесён в <head> с документированными ограничениями.

v0.3.0 (исторический backfill)

Документированный self-hosted деплой

  • Что. End-to-end гайд по запуску trip2g на своей инфраструктуре.
  • Зачем. Воспроизводимая установка вне хостинга.
  • Как. docs/{en,ru}/user/hosting.md и связанные документы.

Sign-in wall + капча (Auth phase 1A)

  • Что. Требование входа на конкретные заметки, капча на auth-флоу, усиленные сессии.
  • Зачем. Закрывает приватный контент; снижает абьюз публичных auth-эндпоинтов.
  • Как. Frontmatter / subgraph require_signin; стандартный шаблон рендерит стену.

Vault-секции layout-а

  • Что. Header / footer / sidebar могут браться из vault-заметок.
  • Зачем. Авторы правят «обвязку» так же, как контент, без хаков в шаблонах.
  • Как. Размещайте заметки по конвенциональным путям (см. docs/dev/default_template.md).

Vault-патчи frontmatter

  • Что. Markdown-файлы в vault могут декларировать патчи, которые применяются к frontmatter других заметок при загрузке.
  • Зачем. Массово тегировать, ставить layout или нормализовать метаданные без правки каждой заметки.
  • Как. docs/dev/frontmatter_patches.md.

Поиск: эмбеддинги bge-m3 + микросервис эмбеддингов

  • Что. Переход на модель bge-m3; отдельный сервис embedding-server; топ-K векторного поиска с matchOrigin.
  • Зачем. Лучше семантический поиск и развязка embedding-нагрузки.
  • Как. Настройте эндпоинт эмбеддингов; векторный поиск доступен через существующие search-API.

Улучшения MCP

  • Что. Результаты MCP-поиска адресуемы и самоописываемы, открываются как сфокусированный chunk-read; текстовые хиты маппятся в ближайшие чанки.
  • Зачем. RAG-клиенты получают богаче и проще навигируемые результаты.
  • Как. Подключайтесь по MCP; для проводки клиента см. docs/user/ai-agent-docs-setup.md (если есть в вашем vault).

«Читать в Telegram» + Telegram UX

  • Что. Кнопка на странице заметки; для опубликованных постов используются публичные TG-ссылки; UTM-теги на трафике из Telegram.
  • Зачем. Замыкает связь между публикациями на сайте и аудиторией Telegram.
  • Как. Автоматически для постов, опубликованных через TG; схема UTM описана в docs/superpowers/specs/.

CLI exporttgchannel

  • Что. Новая CLI-команда для экспорта Telegram-канала в Obsidian-markdown.
  • Зачем. Один шаг от существующего канала к vault-у.
  • Как. go run ./cmd/exporttgchannel --help.

Заметные исправления

  • Настраиваемая нормализация URL с 301-редиректами для альтернативных вариантов.
  • Аудио рендерится как <audio>, документы рендерятся как ссылки.
  • Очередь: устранена утечка горутин при двойном старте и deadlock при остановке.
  • Wikilink [[slug#anchor]]: якорь отрезается до lookup-а заметки.
  • Все варнинги golangci-lint исправлены, добавлен pre-push хук.