[2026-09-15] eagle: family/how-to/truenas-infrastructure.md personal/tech/deepseek-language-drift.md personal/tech/hermes-agent-improvements.md personal/tech/hermes-fork-vs-upstream.md personal/tech/hermes-git-repo.md personal/tech/xray-reverse-tunnel-kraken-truenas.md

This commit is contained in:
Alexey Martemyanov
2026-09-15 12:48:53 +06:00
parent d750de3e53
commit 48c9c72b58
6 changed files with 549 additions and 6 deletions
+42 -1
View File
@@ -13,7 +13,7 @@
> - **⚠️ «Камера на TrueNAS» — ИСПРАВЛЕНО (2026-09-14, вечер-10) + ✅ РЕШЕНО ОКОНЧАТЕЛЬНО (вечер-13):** прежняя запись «камера = USB-вебка на t610, upstream `cam.*:8090` к камере отношения не имеет» — **❌ НЕВЕРНА**. Поиск в `Caddyfile.bak` доказал: **`cam.mallexxx.duckdns.org → 192.168.2.197:8090` — ЭТО И БЫЛА камера** на TrueNAS: отдельный HTTP-MJPEG-сервис (`ustreamer`/`mjpg-streamer`, порт 8090 — канон для «USB-вебка → MJPEG»). **Контейнер УТРАЧЕН** при пересоздании пула (локальный образ не пережил `.ix-apps`; из живого Caddyfile строка удалена — `cam.*` больше нет). **✅ ФИНАЛ (вечер-13):** вебка `046d:0825` физически в **t610**, работает через **аддон `a889bffc_go2rtc-hardware`** → RTSP H.264 `rtsp://192.168.2.176:8554/usb_camera_h264` → **Generic Camera** `camera.192_168_2_176` (зона `kotelnaia`, `unique_id`, **WebRTC работает**, поворот `#rotate=90`). Схема `camera: platform: ffmpeg` в Core — **❌ ОТВЕРГНУТА** (`Resource busy` + нет `unique_id`). Подробно — [[family/how-to/home-automation]] §5-кватер-И-3/И-6.
> - **Не перенесено с TrueNAS:** ~~погашение TrueNAS-стека (Этап 4 п.6 — заблокировано: Caddy на TrueNAS держит точку входа)~~ → **✅ ЗАКРЫТО 2026-09-14 (ночь): стек автоматизации ПОГАШЕН, Caddy остался на TrueNAS (так и задумано).** См. §5-кватер-Л в [[family/how-to/home-automation]].
> Обновлено: 2026-09-02 (vpn.mallexxx:443 = РАБОЧИЙ Xray-сервер, проверено end-to-end; vless-proxy outbound мёртв)
> Обновлено: 2026-09-15 (subscription-канал 3x-ui: ссылки `/sub/<subId>` работают, base64 vless-список; раздел `xray-admin` → «SUBSCRIPTION-КАНАЛ»). Ранее: 2026-09-02 (vpn.mallexxx:443 = РАБОЧИЙ Xray-сервер, проверено end-to-end; vless-proxy outbound мёртв)
> ## ⚠️ ПРИОРИТЕТ 2026-09-01: VPS qentra.top УДАЛЁН → vless-proxy и сетевые туннели сломаны
> - **VPS `91.207.28.205` больше НЕ существует** (вся его Xray/OpenVPN/WG-инфраструктура утрачена). Подробно: [[family/how-to/vps-qentra]].
@@ -245,6 +245,47 @@ arr, caddy, cups, filebrowser, gitea, hermes, immich, inpx-web, inpxer, library,
### `xray-admin` — 3x-ui панель (Xray server, добавлена 2026-09-01)
> ### ✅ SUBSCRIPTION-КАНАЛ (auto-обновление клиентов) — работает, проверено 2026-09-15
>
> **Прямой ответ на вопрос Alex «можно поменять на xray subscription channel (auto)?»: да — он УЖЕ есть, включать ничего не надо. Ошибка была в том, что в клиент вбивали одиночный `vless://…`, а нужна ССЫЛКА ПОДПИСКИ.**
>
> | Что | Значение (проверено живьём 2026-09-15) |
> |---|---|
> | Базовый sub-URL | `https://vpn-panel.mallexxx.duckdns.org/sub/` → **HTTP 200** |
> | `user1` (direct, TrueNAS `90.189.160.148`) | `https://vpn-panel.mallexxx.duckdns.org/sub/68c5cy5n5ui138yh` |
> | `kraken-user` (egress через Kraken `92.62.70.41`) | `https://vpn-panel.mallexxx.duckdns.org/sub/f43074a029dc656e` |
> | Формат ответа | **base64-список `vless://`** (стандарт «subscription channel») |
> | Settings в `x-ui.db` | `subDomain=vpn-panel.mallexxx.duckdns.org`, `subPort=443`, `subScheme=https`; у каждого клиента своё поле **`subId`** (16 символов) и **`subId` есть только у клиентов `user1` / `kraken-user`** |
>
> **Разложенный payload `user1`** (для проверки совпадения с ручным конфигом):
> ```
> vless://ce320965-6956-4759-84bb-7cb71cfc6252@vpn.mallexxx.duckdns.org:443?encryption=none&host=vpn.mallexxx.duckdns.org&path=%2Fvless&security=tls&type=ws#vless-ws-user1
> ```
> ```
> vless://93a5dc4b-1b8d-4af5-9363-eb0091734293@vpn.mallexxx.duckdns.org:443?encryption=none&host=vpn.mallexxx.duckdns.org&path=%2Fvless&security=tls&type=ws#vless-ws-kraken-user
> ```
>
> **Как включить авто-обновление в клиенте (3 шага):** ① удалить вручную вбитый профиль/сервер; ② «Add subscription» → вставить ПОЛНЫЙ путь `…/sub/<subId>` (без `/sub` не работает); ③ включить **auto update interval** (612 ч). Новые клиенты, добавленные в панели, после этого появляются в клиенте сами — руками `vless://` больше вбивать не нужно.
>
> **Команды проверки подписки (с TrueNAS, без правки чего-либо):**
> ```bash
> ssh truenas_admin@mallexxx.duckdns.org
> curl -sk -o /tmp/s.txt -w 'HTTP %{http_code} size %{size_download}\n' https://vpn-panel.mallexxx.duckdns.org/sub/<subId>
> curl -sk https://vpn-panel.mallexxx.duckdns.org/sub/<subId> | base64 -d # → vless://… построчно
> ```
> **Где брать `subId`** (в контейнере нет `sqlite3`):
> ```bash
> docker exec xray-admin sh -c "strings /etc/x-ui/x-ui.db | grep -o '\"subId\":\"[a-z0-9]*\"'"
> ```
> ⚠️ `jq`/`python3` в контейнере `xray-admin` не проверялись — использован `strings` (проверенный на TrueNAS приём, см. диагностические питфоллы ниже).
>
> **Что панель ТЕКУЩЕЙ версии (3.7.0) умеет и не умеет по подпискам:**
> - ✅ **Есть:** `subId` на клиента, sub-сервер (`/sub/<subId>` → base64 vless-список), настройки `subEnable/subPath/subURI/subJsonEnable/subJsonPath/subJsonURI/subClashEnable/subClashPath/subClashURI/subTitle/subListen/subUpdates/subDomain/subPort/subScheme/subCertFile/subKeyFile`, шаблоны `custom-subscription-templates`, таблица `sub_balancers`, `last_sub_fetch` в `client_traffics` (панель видит, кто и когда тянул подписку).
> - ❌ **НЕТ «subscription channel» как отдельной сущности с ветками/каналами** (типа mihomo-«channel»/Nekoray-«channel»): в бинарнике `/app/x-ui` **нет ни одной строки `channel` в этом смысле** и нет API-роутов каналов. Есть категории **`subJsonURI` / `subJsonPath`** — это JSON/Mihomo-формат той же подписки, `XUI`/`subClashEnableRouting` — mihomo-специфика. То есть «channel» = **ссылка подписки + клиентский auto-update**, отдельного механизма в панели нет.
> - Роуты панели по темам подписок (из `strings /app/x-ui`): `/panel/api/clients/sub`, `/panel/api/sub-balancers*`, `/panel/api/xray/outbound-subs*`. (`outbound-subs` = панель сама тянет ВНЕШНИЕ подписки в свои outbound — противоположное направление, не клиентская подписка.)
>
> **🔴 ОТКРЫТЫЙ ВОПРОС БЕЗОПАСНОСТИ (задан Alex 2026-09-15, решения НЕТ):** путь `/sub/<subId>` отдаётся **без авторизации** — `/sub/abc` тоже даёт 200. Кто знает `subId` — получает рабочий ключ. Возможный фикс: подписка по токену/`subUpdates`-проверке в настройках панели либо короткий `subId` + ротация. **Ничего не менялось, ждём решения.**
**Цель:** 3x-ui frontend для Xray-reverse туннеля Kraken↔TrueNAS (выход клиентов сети TrueNAS через Kraken). Подробно: [[personal/tech/xray-reverse-tunnel-kraken-truenas]].
| Параметр | Значение |
+92
View File
@@ -0,0 +1,92 @@
# DeepSeek — спонтанный переход на китайский
**Дата:** 2026-09-15
**Статус:** известный баг модели, официального фикса нет. Решение не принято.
**Проявление:** Кит (Whale) начал отвечать Alex'у на китайском вместо русского.
## Симптом
Модель `deepseek-chat` (провайдер `deepseek`, наш основной) выдаёт ответ
целиком на китайском, хотя весь диалог — на русском или английском.
Диалог был: коммиты git, английский технический вывод, короткие ответы.
**Опять же**, баг плавающий: тот же промпт то даёт правильный язык, то нет.
## Первопричина (подтверждена)
Модель **рассуждает внутри на китайском** (это её дефолт — так эффективнее
по токенам), а финальный шаг «перевести на язык пользователя» иногда
пропускается или сбоит.
Два фактора:
1. **Мультиязычное reasoning по дизайну.** Внутренняя цепочка рассуждений
генерируется на китайском намеренно, для эффективности. Перевод обратно
не всегда происходит.
2. **Недетерминированность генерации.** Тот же вход иногда даёт английский,
иногда китайский. Поэтому ни один фикс не гарантирован.
**Промпт игнорируется:** в багрепортах отмечено, что модель продолжает
на китайском **даже после прямой просьбы** не переключать язык.
## Триггеры
- **Короткие технические ответы без явной языковой привязки** — самый частый
триггер. Формулировка из разбора: «short, context-lacking prompts more
likely trigger default reasoning language».
- Большой объём англоязычного вывода (git-логи, diff'ы, термины) —
сбивает языковую привязку.
- В нашем system prompt **нет жёсткой языковой инструкции**: единственное
упоминание — строка 4 `SOUL.md`: `respond in the language you're
addressed in — Russian or English`. Формулировка мягкая.
## Статус в апстриме
Issue **открыты**, мейнтейнеры не ответили, официального фикса **нет**:
- [DeepSeek-V3 #1226](https://github.com/deepseek-ai/DeepSeek-V3/issues/1226) — Model sometimes responds in Chinese even when the conversation is in English
- [DeepSeek-V3 #1443](https://github.com/deepseek-ai/DeepSeek-V3/issues/1443) — Model switches to Chinese despite all English prompts
- [DeepSeek-V3 #1463](https://github.com/deepseek-ai/DeepSeek-V3/issues/1463) — DeepSeek responds in Chinese instead of English
Все решения ниже — community workarounds, не официальные.
## Способы решения (по возрастанию стоимости)
### 1. Жёсткая языковая инструкция в SOUL.md — дешёвый, не гарантирован
Заменить мягкую формулировку на явную:
> Отвечай **только** на языке собеседника — русский или английский.
> Никогда не используй китайский, даже внутри рассуждений.
> Если сомневаешься — пиши по-русски.
**Плюс:** правится без кода, `SOUL.md` редактируется напрямую.
**Минус:** в багрепортах модель игнорировала и прямые запреты.
Полезно ещё потому, что теперь есть `prompt_overrides` — можно вынести
формулировку в `review/*.md`, не трогая Python. См. [[hermes-agent-improvements]].
### 2. Детектор языка + retry — надёжно, дорого
Программная проверка вывода: если китайский — повтор запроса.
Недетерминированность играет в нашу пользу: retry часто даёт правильный язык.
**Требует кода в Hermes** (перехват ответа, детекция, повтор). Отдельная задача.
### 3. Смена модели — радикально, эффективно
Claude / GPT следуют языковым инструкциям заметно лучше. Если китайский
будет повторяться — вариант.
**Открытый вопрос:** `deepseek-chat` выбран сознательно или это дефолт,
который никто не выбирал? Ответ определяет выбор между путями 1+2 и 3.
## Что НЕ работает
- Повтор той же просьбы «говори по-английски» — модель игнорирует.
- Опора на мягкую формулировку (`respond in the language you're addressed in`).
## Связанное
- [[hermes-whale-system-prompt]] — где живёт языковая инструкция (SOUL.md, строка 4)
- [[hermes-agent-improvements]] — prompt_overrides, способ правки без кода
+131
View File
@@ -0,0 +1,131 @@
# Hermes Agent — вынос хардкода промптов в файлы (prompt_overrides)
**Дата:** 2026-09-15
**Статус:** реализовано, закоммичено и запушено
**Репо:** `~/.hermes/hermes-agent` (сабмодуль, форк `mallexxx/hermes-agent`)
## Проблема
Стабильный слой system prompt был **захардкожен** в `agent/prompt_builder.py`.
Чтобы поменять формулировку любого блока (например, ужесточить инструкции
про язык ответа или про завершение задачи) — надо было править Python-код
сабмодуля. Это значит: правка живёт в форке, конфликтует при подтяжке
апстрима, и её надо переносить при каждом обновлении.
## Решение: prompt_overrides
Каждый хардкод-блок stable-слоя читается из `.md` файла. Маппинг — в конфиге.
Файла нет → fallback на хардкод, ничего не ломается.
### Реализация (3 файла)
**1. `agent/system_prompt.py`** — новая функция:
```python
def _load_prompt_block(agent, block_name: str, default: str) -> str:
"""Load a prompt block from a file if configured, else return default."""
overrides = getattr(agent, "_prompt_overrides", None) or {}
path = overrides.get(block_name)
if path:
try:
resolved = os.path.expanduser(path)
content = Path(resolved).read_text(encoding="utf-8").strip()
if content:
return content
except Exception:
logger.debug("Could not load prompt override '%s' from %s", block_name, path)
return default
```
7 констант заменены на её вызовы:
| Блок | Константа (fallback) |
|------|---------------------|
| `hermes_help` | `HERMES_AGENT_HELP_GUIDANCE` |
| `task_completion` | `TASK_COMPLETION_GUIDANCE` |
| `memory_guidance` | `MEMORY_GUIDANCE` |
| `session_search_guidance` | `SESSION_SEARCH_GUIDANCE` |
| `skills_guidance` | `SKILLS_GUIDANCE` |
| `tool_use_enforcement` | `TOOL_USE_ENFORCEMENT_GUIDANCE` |
| `execution_discipline` | `OPENAI_MODEL_EXECUTION_GUIDANCE` |
**2. `agent/agent_init.py`** — чтение конфига в `agent._prompt_overrides`
(+11 строк, в секции рядом с tool-loop guardrail config).
**3. `~/.hermes/hermes-whale/config.yaml`** — секция маппинга:
```yaml
agent:
prompt_overrides:
hermes_help: ~/.hermes/hermes-whale/review/hermes_help.md
task_completion: ~/.hermes/hermes-whale/review/task_completion.md
memory_guidance: ~/.hermes/hermes-whale/review/memory_guidance.md
session_search_guidance: ~/.hermes/hermes-whale/review/session_search_guidance.md
skills_guidance: ~/.hermes/hermes-whale/review/skills_guidance.md
tool_use_enforcement: ~/.hermes/hermes-whale/review/tool_use_enforcement.md
execution_discipline: ~/.hermes/hermes-whale/review/execution_discipline.md
```
**4. `~/.hermes/hermes-whale/review/`** — 7 новых `.md` файлов с содержимым
блоков. Раньше в этой папке лежали только `memory_prompt.md` и `skill_prompt.md`.
## Pitfall: prompt_overrides — это про Python-код, а не про config
Файлы `review/*.md` **обязаны быть закоммичены**. Если закоммитить `config.yaml`
с путями, но не сами файлы — подмены промпта молча исчезнут (fallback на
хардкод), потому что `_load_prompt_block` ловит отсутствие файла в `except`
и отдаёт default. Ошибки нет, поведение просто возвращается к хардкоду.
Это ровно то, что чуть не произошло: 7 файлов `review/*.md` были untracked.
## Pitfall: порядок коммита сабмодуля
Правки Python живут в сабмодуле. Порядок:
```bash
cd ~/.hermes/hermes-agent && git add agent/*.py && git commit -m "..."
cd ~/.hermes && git add hermes-agent && git commit -m "..." # обновить пин
```
Если пин не обновить — в родительском репо видно
`hermes-agent (new commits, modified content)`, и `-dirty` в diff сабмодуля.
## Коммиты и push (2026-09-15)
| Репо | Коммит | Что |
|------|--------|-----|
| `hermes-agent` | `207a1de16` | feat: load stable system-prompt blocks from config prompt_overrides |
| `~/.hermes` | `7fd200c` | feat: wire prompt_overrides + whale-thread-guard multi-bot routing |
| `~/.hermes` | `7893c35` | chore: sync skills — curator consolidation |
| `~/.hermes` | `bdf84da` | chore(lsp): add vue/dockerfile/pyright/yaml language servers |
| `~/.hermes` | `176ce9c` | chore: thread-scoped memory store + claude token refresh script |
| `~/.hermes` | `9b111fc` | chore: untrack lsp/node_modules (1957 files, 43M) |
**Запушено:**
- `hermes-agent``github.com:mallexxx/hermes-agent` (20 коммитов)
- `~/.hermes``git.mallexxx.duckdns.org/git_admin/eagle-hermes` (9 коммитов)
## Осталось в коде (не вынесено)
- `DEFAULT_AGENT_IDENTITY` — fallback, остаётся хардкодом
- `GOOGLE_MODEL_OPERATIONAL_GUIDANCE` — неактуально для Whale
- `COMPUTER_USE_GUIDANCE`, `KANBAN_GUIDANCE` — неактуально
- `PLATFORM_HINTS` — webhook там нет
- `build_skills_system_prompt()` — автогенерируется, файлом не заменить
## Зачем это было нужно
Два практических повода:
1. **Правки промпта без правки Python** — теперь формулировки живут в `.md`,
редактируются в vault-стиле, не конфликтуют при подтяжке апстрима.
2. **Фикс языкового дрейфа DeepSeek** — нужна жёсткая языковая инструкция
в промпте; теперь её можно добавить файлом, а не патчем кода.
См. [[deepseek-language-drift]].
## Связанное
- [[hermes-whale-system-prompt]] — полный состав system prompt (3 слоя)
- [[hermes-git-repo]] — структура репо, .gitignore, что коммитить
- [[deepseek-language-drift]] — баг с китайским выводом, повод для правок промпта
- [[hermes-fork-vs-upstream]] — расхождение с апстримом (24k коммитов)
+213
View File
@@ -0,0 +1,213 @@
# Hermes — апстрим vs наш форк
**Дата:** 2026-09-15
**Статус:** актуально — анализ расхождения сделан, **стратегия подтяжки НЕ выбрана**
**Репо:** `~/.hermes/hermes-agent` (сабмодуль, форк `mallexxx/hermes-agent`)
## Текущее решение: не принято
На 2026-09-15 Alex'у представлены 3 варианта (A merge / B перенос на свежий
апстрим / C заморозка). **Выбор не сделан.** Работа над подтяжкой не начата.
Наш форк запушен и стабилен — можно спокойно решать.
Открытый вопрос, определяющий выбор: **нужны ли нам вообще 24k коммитов
апстрима?** Мы используем Hermes как Zulip-бота с webhook-флоу; апстрим растёт
десктопом/TUI/платформами, которые нам не нужны. Точечный cherry-pick
(security-фиксы, фиксы `agent/`, новые модели) может быть достаточен.
## Масштаб расхождения
| Метрика | Значение |
|---------|----------|
| Коммитов апстрима впереди | **24 407** |
| Наших коммитов | 32 |
| Common ancestor | `4ed63170e4` (2026-06-04) |
| Дата последнего коммита апстрима | 2026-09-15 |
| Наша версия | `0.15.1` |
| Версия апстрима | `0.21.3` |
| Файлов изменено апстримом | 12 634 |
| Строк: +2 362 152 / 763 849 | |
**Вывод:** это не «догнать на пару недель», а **~3.5 месяца и 24k коммитов**.
Апстрим ушёл на мажорные версии вперёд (0.15 → 0.21).
## Ремоуты
| Remote | URL | Роль |
|--------|-----|------|
| `origin` | `git@github.com:mallexxx/hermes-agent.git` | наш форк |
| `upstream` | `https://github.com/NousResearch/hermes-agent.git` | источник |
## Главные изменения апстрима
### 1. Платформы переехали из ядра в плагины ⚠️ КРИТИЧНО
Апстрим **удалил** `telegram.py`, `slack.py`, `matrix.py`, `discord.py` из
`gateway/platforms/` — теперь они живут в `plugins/platforms/`.
**Состав `plugins/platforms/` у апстрима (19):**
`a2a, buzz, dingtalk, discord, email, feishu, google_chat, homeassistant, irc,
line, matrix, mattermost, ntfy, photon, raft, simplex, slack, sms, teams,
telegram, wecom, whatsapp`
**Наш `plugins/platforms/` (8):** `discord, google_chat, irc, line, mattermost,
ntfy, simplex, teams`
**⚠️ Zulip НЕТ ни в ядре апстрима, ни в `plugins/platforms/` апстрима.**
Проверено: `git ls-tree -r upstream/main | grep -i zulip` → пусто.
Zulip — **полностью наша разработка**:
- `gateway/platforms/zulip.py` — не существует в апстриме
- `tools/zulip_thread_tool.py` — не существует в апстриме
Это значит: наш Zulip-адаптер живёт в **устаревшем месте** (`gateway/platforms/`),
которое апстрим планомерно опустошает. При подтяжке его придётся переносить в
`plugins/platforms/` — иначе он повиснет на api, которого больше нет.
### 2. Смежные подсистемы
Топ-скоупы по числу feat/refactor коммитов:
| Скоуп | Коммитов | Что это |
|-------|----------|---------|
| `desktop` | 773 | Десктоп-приложение — крупнейшая новая подсистема |
| `hermes_cli` | 607 | CLI переработан |
| `gateway` | 412 | Ядро гейтвея |
| `tools` | 339 | Инструменты |
| `agent` | 217 | Ядро агента |
| `cli` / `tui` / `tui_gateway` | 141/100/76 | TUI (Ink/React) |
| `state` | 106 | Персистентность сессий |
| `cron` | 91 | Планировщик |
| `computer_use` | 86 | Управление десктопом |
| `mcp` | 79 | MCP-клиент |
| `relay` | 54 | Новая подсистема релея |
Типы коммитов: `fix` 10 994, `refactor` 4 158, `feat` 2 113, `test` 1 836.
**Читается так:** апстрим прошёл большую волну рефакторинга (4158) + массовый
десктоп/TUI-фронт. Наши правки в `gateway/run.py` (13 коммитов) и
`gateway/stream_consumer.py` (9) — ровно в эпицентре этого рефакторинга.
## Наши 32 коммита
Сгруппированы по темам:
**Zulip-платформа (наша, в апстриме отсутствует):**
- `b0f097a299` feat: add Zulip platform adapter (parallel to Discord)
- `ce093b0a75` zulip: switch approval prompt emojis ✅/❌ → 👍/👎
- `b1e36eb2d7` fix(zulip): lock→locked emoji name
- `f39671ba97` fix(routing): pre_gateway_dispatch before session guard
- `d4e98a8b07` / `ef69e76413` / `40149225af` / `ddc6aa7c46` webhook: zulip in
BUILTIN_DELIVER_PLATFORMS, session_chat_id из X-Chat-Id
- `8255ea0c64` feat(zulip): session_search читает Zulip thread history
- `049922d8cc` fix(session_search): dual Zulip+SQLite recall, FTS5 sanitization
**Webhook no-edit streaming (наш флоу без редактирования сообщений):**
- `fa271d2d19`, `ee41e8a847`, `9003d4cbbe` (revert), `1ac06d593a`, `9253fe52cc`,
`88cbb77b95`, `6c95153aa8`, `eb8dd7b5ed`, `a92840dcde`, `a08527e85c`,
`a88bec1762`, `5d3e95841b`, `ec9efce7e7`
**Фичи профиля Whale:**
- `972004546f` feat: thread-scoped memory + configurable memory instruction
- `4ebad4f692` test: thread-scoped memory persistence, drift guard, snapshot
- `da146da5fa` feat: configurable background review prompts and allowed toolsets
- `207a1de16c` feat: load stable system-prompt blocks from config prompt_overrides
**Прочее:**
- `6e09a3704a` fix(approval): interrupt pending approvals on user message
- `a17d02f31e` fix(api_server): tool.progress → plain-text chunks
- `81f10c306f` approval: reaction emoji на webhook
## Риск конфликтов при merge
Dry-run `git merge-tree`**16 конфликтных файлов, 160 конфликтных маркеров**.
| Файл | Наших коммитов | Коммитов апстрима | Риск |
|------|---------------|-------------------|------|
| `gateway/run.py` | 13 | **974** | 🔴 экстремальный |
| `cron/scheduler.py` | 1 | 307 | 🟠 |
| `agent/agent_init.py` | 3 | 226 | 🟠 |
| `gateway/platforms/base.py` | 2 | 208 | 🟠 |
| `gateway/platforms/api_server.py` | 1 | 177 | 🟠 |
| `agent/model_metadata.py` | 1 | 164 | 🟡 |
| `tools/approval.py` | 1 | 147 | 🟡 |
| `agent/prompt_builder.py` | 0 | 131 | auto-merge ok |
| `gateway/stream_consumer.py` | 9 | 73 | 🟠 (наш файл, апстрим переписал) |
| `agent/system_prompt.py` | 2 | 65 | 🟡 |
| `gateway/platforms/webhook.py` | 7 | 59 | 🟠 |
| `toolsets.py` | 2 | 62 | 🟡 |
| `tools/session_search_tool.py` | 2 | 41 | 🟢 |
| `tools/memory_tool.py` | 1 | 34 | 🟢 |
| остальные | 1 | <30 | 🟢 |
**Худший случай:** `gateway/run.py` — 974 коммита апстрима против наших 13.
Это фактически переписывание файла. Конфликты там будут не «принять/отклонить
строку», а разбор чужой новой архитектуры.
## Стратегии подтяжки
### Вариант A: merge (сейчас) — НЕ рекомендуется
`git merge upstream/main` → 16 конфликтных файлов, ручной разбор 160 маркеров
в горячих файлах. Оценка: **дни на `run.py` + `stream_consumer.py`**, риск
потерять наши webhook/Zulip-правки.
### Вариант B: перенос нашей функциональности на свежий апстрим (рекомендуется)
Взять чистый `upstream/main` как новую базу, затем **перенести наши фичи
патчами заново**:
1. Сохранить наши diff'ы как patch-файлы (`git format-patch`)
2. Форк от свежего апстрима
3. Переносить по темам: Zulip-адаптер (в `plugins/platforms/`!), 3 фичи Whale
(thread-scoped memory, bg-review, prompt_overrides), webhook-стриминг
4. Webhook-стриминг — вероятно **уже решён апстримом**, проверить перед переносом
**Плюс:** результат на актуальной кодовой базе, без 24k коммитов долга.
**Минус:** ручная работа по переносу, ~1-2 дня.
### Вариант C: заморозить и жить на форке
Принять, что мы отдельный продукт. Апстрим берём точечно (`git cherry-pick`
отдельных фиксов безопасности). Дёшево сейчас, растёт долг потом.
## Ключевой вопрос перед подтяжкой
**Нужны ли нам вообще 24k коммитов апстрима?**
Мы используем Hermes как **Zulip-бота с webhook-флоу**. Ядро апстрима
прирастает десктопом, TUI и платформами (telegram/slack/...), которые нам
не нужны. Реальная ценность от апстрима для нас:
- фиксы безопасности (`tools/tirith_security.py`, approval)
- фиксы багов в `agent/` (метаданные моделей, сжатие контекста)
- поддержка новых моделей/провайдеров
Это можно брать точечно (cherry-pick), не таща весь долг.
## Команды для проверки расхождения
```bash
cd ~/.hermes/hermes-agent
# Обновить инфу об апстриме
git fetch upstream
# Насколько отстали
git rev-list --count main..upstream/main # апстрим впереди
git rev-list --count upstream/main..main # мы впереди
# Общий предок
git merge-base main upstream/main
# Изменения по нашим файлам
git diff --name-only $(git merge-base main upstream/main)..main | sort -u
# Насколько апстрим трогал конкретный файл
git rev-list --count $(git merge-base main upstream/main)..upstream/main -- gateway/run.py
# Dry-run merge (безопасно, не трогает рабочую копию)
git merge-tree --write-tree --name-only main upstream/main
# Есть ли файл в апстриме
git cat-file -e upstream/main:gateway/platforms/zulip.py && echo yes || echo NO
```
## Связанное
- [[hermes-git-repo]] — структура репо, .gitignore, что коммитить
- [[hermes-whale-system-prompt]] — prompt_overrides (наша фича)
- [[hermes-memory-architecture]] — thread-scoped память (наша фича)
+58 -5
View File
@@ -89,10 +89,16 @@ cd ~/.hermes && git check-ignore -v .env.bak .env.new \
Каждый файл должен вывести строку `.gitignore:<N>:<pattern>\t<file>`.
**Pitfall:** `.gitignore` НЕ влияет на уже отслеживаемые файлы.
`hermes-whale/lsp/node_modules/` остаётся в репо несмотря на правило `node_modules/`.
`hermes-whale/lsp/node_modules/` оставался в репо несмотря на правило
`node_modules/` (закрыто коммитом `9b111fc` через `git rm -r --cached`).
Вывод — применять `git add` по именам файлов, а не `git add .`, иначе новое правило
может дать ложное чувство защиты.
**Второй pitfall — `git add -f`:** после добавления правила `node_modules/`
попытка `git add` файла внутри него (например `lsp/node_modules/.package-lock.json`)
падает с «paths are ignored by one of your .gitignore files». Уже отслеживаемые
файлы коммитятся нормально без `-f` — правило на них не действует.
## Что коммитить / что не коммитить
**Коммитим:**
@@ -111,10 +117,56 @@ cd ~/.hermes && git check-ignore -v .env.bak .env.new \
## Долги (не исправлено, требуют решения Alex)
1. **`hermes-whale/lsp/node_modules/` в git** — 39M бинарников + `gopls` 41MB.
`.gitignore` не помогает (файлы уже отслеживаются). Чистка = `git rm -r --cached` + rewrite истории.
2. **`channel_directory.json` в .gitignore, но отслеживается** — правило добавлено после коммита.
1. **`channel_directory.json` в .gitignore, но отслеживается** — правило добавлено после коммита.
В коммите `7fd200c` из него удалено 869 строк (файл в основном генерируемый).
Также живёт как ` M` в статусе постоянно — это рантайм-запись текущей сессии,
коммитить бессмысленно. Решение: `git rm --cached channel_directory.json`.
2. **История git не похудела.** 43M node_modules убраны из трекинга, но остаются
в истории. Для реальной экономии — `filter-repo` + force push (ломает клоны).
### Закрыто 2026-09-15
- ~~`hermes-whale/lsp/node_modules/` в git~~**убрано из трекинга** коммитом
`9b111fc`: 1957 файлов / 43M. Файлы **остались на диске** (108 каталогов),
`git rm -r --cached` индекс не удаляет. Проверка:
`git ls-files | grep -c node_modules` → 0.
## Коммиты и push (2026-09-15)
| Коммит | Что |
|--------|-----|
| `207a1de16` (submodule) | load stable system-prompt blocks from config prompt_overrides |
| `7fd200c` | wire prompt_overrides + whale-thread-guard multi-bot routing |
| `7893c35` | sync skills — curator consolidation (516 удалений, 55 новых) |
| `bdf84da` | add vue/dockerfile/pyright/yaml language servers |
| `176ce9c` | thread-scoped memory store + claude token refresh script |
| `9b111fc` | untrack lsp/node_modules (1957 files, 43M) |
**Запушено:**
- `hermes-agent``github.com:mallexxx/hermes-agent` — 20 коммитов
- `~/.hermes``git.mallexxx.duckdns.org/git_admin/eagle-hermes` — 9 коммитов
Оба репо: `main...origin/main` синхронно, секретов в истории нет.
## Утечка секретов (закрыта 2026-09-15)
`.env.bak` и `.env.new``~/.hermes/` и `~/.hermes/hermes-whale/`) содержали
**живые токены**: `TELEGRAM_BOT_TOKEN`, `OPENROUTER_API_KEY`, `DISCORD_BOT_TOKEN`,
`ZULIP_API_KEY`, `DEEPSEEK_API_KEY`. Игнорился только `.env` — варианты
`.bak`/`.new` НЕ покрывались. Один `git add .` затащил бы их в публичную историю.
Также `webhook_subscriptions.json` (16-символьные per-endpoint секреты) —
не был игнорирован. Проверено фактически: `jq -r '.whale.secret | length'` → 16.
Проверка, что секреты не попали в историю:
```bash
cd ~/.hermes && git log --all --diff-filter=A --name-only --pretty=format: | grep '\.env'
# пусто = чисто
```
`scripts/refresh-claude-token.sh`**не секрет**: refresh token читается
из `~/.claude/.credentials.json` в рантайме, `CLIENT_ID` — публичный OAuth
идентификатор.
## Команды
@@ -134,5 +186,6 @@ cd ~/.hermes && git log --all --diff-filter=A --name-only --pretty=format: | gre
## Связанное
- [[hermes-whale-system-prompt]] — состав system prompt, prompt_overrides
- [[hermes-agent-improvements]] — вынос хардкода промптов в файлы
- [[hermes-agent-improvements]] — вынос хардкода промптов в файлы (механизм + коммиты)
- [[hermes-memory-architecture]] — thread-scoped память
- [[hermes-fork-vs-upstream]] — расхождение с апстримом (24k коммитов)
@@ -380,6 +380,19 @@ TrueNAS:
Rollback network fix: вернуть compose-бэкап на Kraken и выполнить `docker compose up -d`. Это вернёт нерабочий Docker bridge mode, поэтому делать только для диагностики/отката.
## ✅ Subscription-канал (auto-обновление клиентов) — 2026-09-15
> Проверено живьём: клиенты панели отдаются **ссылкой подписки**, а не только одиночным `vless://`.
>
> | Клиент | Ссылка подписки | Egress |
> |---|---|---|
> | `user1` | `https://vpn-panel.mallexxx.duckdns.org/sub/68c5cy5n5ui138yh` | `direct` → TrueNAS `90.189.160.148` |
> | `kraken-user` | `https://vpn-panel.mallexxx.duckdns.org/sub/f43074a029dc656e` | `via-kraken` → Kraken `92.62.70.41` |
>
> Ответ — **base64-список `vless://`** (стандартная подписка: `type=ws`, `security=tls`, `path=/vless`, `host=vpn.mallexxx.duckdns.org`). В клиенте: удалить ручной профиль → «Add subscription» с ПОЛНЫМ путём `…/sub/<subId>` → включить auto-update (612 ч).
> **Питфолл:** вбитый руками `vless://` авто-обновляться не будет — только ссылка подписки. Ссылка открыта без авторизации (кто знает `subId` — имеет ключ) — открытый вопрос к Alex.
> Полный разбор + команды проверки + что панель 3.7.0 умеет по подпискам: [[family/how-to/truenas-infrastructure]] → раздел `xray-admin` → «SUBSCRIPTION-КАНАЛ».
## ✅ Per-user egress на `vpn.mallexxx.duckdns.org` (2026-09-02)
В существующий VLESS-WS inbound `in-10095-tcp` добавлен второй клиент. Маршрутизация теперь различает клиентов по `email`: