# spacesh — техническая спецификация > Терминал-воркспейс под AI-агентов. macOS-first. Tauri 2 + Rust + React/TypeScript. > Версия документа: v0.1 (handoff-ready). Назначение: спецификация для реализации. --- ## 0. Допущения и зафиксированный скоуп Документ фиксирует решения, принятые на этапе проектирования. Несколько вещей сознательно **вынесены за рамки v1**: - **Учёт токенов и остатков лимитов — НЕ входит в v1.** Решено убрать из уравнения; провайдерские API для остатков грязные/против ToS, а ценность размыта существующими menu-bar трекерами. Может вернуться позже как отдельный подписчик на шину. - **Юридический слой (авторизация пользователей, хранение чужих токенов, ToS-обвязка) — НЕ входит в v1.** Инструмент в первую очередь делается под себя. Вопрос продажи откладывается. - **Встроенный браузер — выкинут.** Большая поверхность поддержки, нулевая ценность для агентского флоу. - **Полноценный редактор (Monaco-класс) — отложен.** В v1 — дерево файлов + diff-просмотр изменений агента (CodeMirror 6), не IDE. **Фича «модификации» уточнена как внешние нотификации.** В v1 это рассылка уведомлений о событиях агентов во внешние каналы — **в первую очередь Telegram и MAX**. Реализуется как подписчик внутри демона на события шины (см. §9.2). Guardrails (перехват команд) — не входит в v1. --- ## 1. Идея проекта ### 1.1. Проблема Один AI-агент в терминале — управляем глазами. Пять-восемь агентов параллельно в разных проектах — нет: непонятно, кто закончил, кто упал, кто ждёт ввода, к кому возвращаться. Обычный терминал/tmux не сообщает статус сам, раскладки не переживают перезапуск удобно, а единого способа дёрнуть нужную панель из скрипта нет. ### 1.2. Что такое spacesh Терминал-воркспейс, где **бэкенд-демон владеет живыми сессиями**, а GUI и CLI — тонкие клиенты к нему. Воркспейс на проект, реальные PTY со сплитами, видимый статус каждого агента (детерминированный, через хуки агентов), единый CLI поверх той же командной шины. Всё с клавиатуры, macOS-native. ### 1.3. Ключевые отличия от аналогов (pilotry и пр.) 1. **Демон переживает GUI.** Краш/обновление приложения не убивает агента в середине задачи. Прямая дорога к remote/SSH-воркспейсам (демон на ноде — GUI на макбуке). 2. **macOS-native.** Аналоги Windows-first; spacesh строится сразу под WKWebView, launchd, нативные хоткеи и уведомления. 3. **Детерминированный статус** через хуки агентов вместо угадывания по выводу. 4. **Единая командная шина** — GUI и CLI идут через один socket, CLI получается почти бесплатно. ### 1.4. Чем spacesh НЕ является Не IDE, не браузер, не замена tmux в смысле мультиплексора для серверов (хотя архитектурно к remote готов), не агент сам по себе — spacesh запускает чужие официальные CLI (Claude Code, Codex, Gemini, opencode, shell) в PTY и не подменяет их логику. ### 1.5. Целевой пользователь (v1) Senior-инфраструктурщик/разработчик, гоняющий несколько агентов параллельно на macOS, скриптующий своё окружение, ценящий клавиатурный флоу и переживаемость сессий. --- ## 2. Принципы и нефункциональные требования | Принцип | Следствие в реализации | |---|---| | Демон — единственный источник истины | GUI и CLI не хранят состояние; только шлют команды и слушают события | | Keyboard-first | Весь рабочий цикл закрывается с клавиатуры; мышь — только ресайз | | macOS-native | launchd user-agent для демона, нативные уведомления с actions, WKWebView-aware фронт | | Лёгкость | Системный webview (Tauri), без встроенного Chromium | | Переживаемость | Сессии живут в демоне между перезапусками GUI | | Расширяемость через шину | Новые фичи = новые команды/события/подписчики, спина не двигается | **Бюджеты производительности:** - Латентность ввода (keypress → echo в панели): < 16 мс при нормальной нагрузке. - Батчинг вывода PTY: коалесцировать ~4–8 мс **или** до ~16 КБ, что раньше; не эмитить событие `output` по байту. - Backpressure: при отставании рендера демон не должен расти в памяти неограниченно — кольцевой лимит на неподтверждённый поток к клиенту. - Repaint при reattach: снапшот экрана панели < 100 мс на типовой грид. --- ## 3. Архитектура — топология ``` ┌─────────────────────────────────────────────────────────┐ │ spacesh-daemon (spaceshd) │ │ владеет: │ │ • PTY-процессы (по surface) │ │ • терминальный грид каждой панели (alacritty_terminal)│ │ • модель воркспейсов / дерево сплитов / раскладки │ │ • движок статусов │ │ • подписчики (нотификации Telegram/MAX) │ │ │ │ хостит: Unix-domain socket ~/.spacesh/sock │ └───────────────┬───────────────────────┬──────────────────┘ │ │ attach/команды/события команды/события (--json) │ │ ┌──────────┴─────────┐ ┌────────┴───────────┐ │ GUI-клиент (Tauri) │ │ CLI-клиент (spacesh)│ │ рендер xterm.js │ │ тонкий бинарь │ │ ввод, attach/det. │ │ shell-completions │ └────────────────────┘ └─────────────────────┘ ``` Один socket, один протокол. И клик в GUI, и `spacesh focus s_8f3` из скрипта — это одна и та же команда `focus`. Никакой дубликации операционной логики. --- ## 4. Процессная модель (вариант B — демон) ### 4.1. Жизненный цикл демона - Демон стартует лениво: первый клиент (GUI или CLI), не найдя socket, поднимает `spaceshd` и ждёт готовности. - Под macOS оборачивается в **launchd user-agent** (`~/Library/LaunchAgents/xyz.spacesh.daemon.plist`) с `KeepAlive` — автоперезапуск при краше, автостарт при логине (опционально, по настройке). - Демон single-instance: лок-файл `~/.spacesh/daemon.lock` + проверка живости socket. - Graceful shutdown по команде `shutdown` или при отсутствии сессий и клиентов дольше TTL (настраиваемо; по умолчанию демон живёт, пока живы сессии). ### 4.2. Переживаемость сессий PTY-процессы принадлежат демону, а не GUI. Закрытие/краш/обновление GUI **не убивает** агентов. При следующем запуске GUI выполняет `attach` к живым панелям и репейнтит их из снапшота грида (см. §6.4). Раскладка (определение воркспейсов, дерево сплитов, привязка агентов к панелям) персистится на диск отдельно от живых процессов (§8.4) — так что даже полная перезагрузка демона восстанавливает структуру (с перезапуском агентов), а краш GUI восстанавливает и структуру, и живые процессы. ### 4.3. Эволюция к remote (вне v1, но заложено) Socket абстрагирован за трейтом транспорта. UDS локально сегодня → проброс через SSH (`ssh -L` к UDS на ноде) завтра → нативный TCP+mTLS позже. Демон не знает, локальный клиент или удалённый. --- ## 5. Протокол командной шины ### 5.1. Транспорт и кадрирование - Транспорт: Unix-domain socket, асинхронный (tokio). - Кадрирование: **length-prefixed** (`u32` BE длина + payload). - Сериализация: **JSON** в v1 (читаемость, отладка, `--json` для CLI). Возможна замена на MessagePack при упоре в объём вывода — изолировано в `spacesh-proto`. - Мультиплексирование: один socket на клиента; внутри — корреляция запрос/ответ по `id`, плюс независимый поток push-событий. ### 5.2. Конверт сообщения ```jsonc // Запрос (клиент → демон) { "id": 42, "kind": "req", "cmd": "focus", "args": { "surface_id": "s_8f3" } } // Ответ (демон → клиент), коррелируется по id { "id": 42, "kind": "res", "ok": true, "data": { /* ... */ } } { "id": 42, "kind": "res", "ok": false, "error": { "code": "NOT_FOUND", "msg": "..." } } // Событие (демон → подписчики), без id запроса { "kind": "evt", "evt": "state", "data": { "surface_id": "s_8f3", "state": "done" } } ``` ### 5.3. Команды (request/response) | Команда | Аргументы | Ответ | Назначение | |---|---|---|---| | `open` | `{ path }` | `{ workspace_id }` | Открыть директорию воркспейсом; повторный вызов не плодит дубль | | `close_workspace` | `{ workspace_id }` | `{}` | Закрыть воркспейс (с подтверждением при живых панелях) | | `new_surface` | `{ workspace_id, agent, split? }` | `{ surface_id }` | Поднять панель с агентом; `split: right\|down` — сразу сплитом | | `close` | `{ surface_id }` | `{}` | Закрыть панель | | `focus` | `{ surface_id }` | `{}` | Сфокусировать панель и поднять окно | | `input` | `{ surface_id, bytes }` | `{}` | Ввод с клавиатуры (base64 в JSON) | | `resize` | `{ surface_id, cols, rows }` | `{}` | Ресайз PTY (→ SIGWINCH) | | `attach` | `{ surface_id }` | `{ snapshot, cols, rows, cursor }` | Подписаться на панель + получить снапшот для репейнта | | `detach` | `{ surface_id }` | `{}` | Отписаться от потока панели | | `status` | `{ json? }` | `{ workspaces[] }` | Список воркспейсов и панелей с состояниями | | `set_state` | `{ surface_id, state }` | `{}` | Внешний сигнал статуса (от хуков агента, см. §7) | | `move_surface` | `{ surface_id, target }` | `{}` | Перетаскивание/переупорядочивание | | `apply_preset` | `{ workspace_id, preset_id }` | `{ surface_ids[] }` | Развернуть пресет раскладки | | `shutdown` | `{}` | `{}` | Остановить демон | ### 5.4. События (push) | Событие | Данные | Назначение | |---|---|---| | `output` | `{ surface_id, bytes }` | Живой поток вывода PTY для рендера (батчированный) | | `state` | `{ surface_id, state }` | Смена статуса: `done\|wait\|work\|error\|idle` | | `exit` | `{ surface_id, code }` | Процесс панели завершился | | `surface_created` | `{ surface_id, workspace_id, agent }` | Появилась панель | | `surface_closed` | `{ surface_id }` | Панель закрыта | ### 5.5. Жизненный цикл attach/detach (сердце варианта B) ``` GUI стартует → status // получить структуру → для каждой видимой панели: attach { surface_id } ← res { snapshot, cursor } // репейнт xterm.js из снапшота ← evt output ... // дальше живой поток GUI сворачивается/закрывается → detach (или просто рвёт соединение — демон чистит подписку по closed-socket) GUI возвращается → attach снова → новый снапшот → живой поток ``` Снапшот собирается демоном из Rust-грида (`alacritty_terminal`) и сериализуется в ANSI-дамп, который xterm.js скармливает себе для мгновенного репейнта. Это закрывает главную боль демон-архитектуры — восстановление экрана панели после reattach — без отдельного ring-buffer. --- ## 6. Терминальный слой ### 6.1. PTY - Крейт: **`portable-pty`** (спавн, master/slave, resize). Кроссплатформенно — пригодится, даже если v1 только macOS. - На surface: один PTY + дочерний процесс (агентский CLI или shell), рабочая директория = папка воркспейса, прокинутый env. ### 6.2. Reader-loop, батчинг, backpressure - Отдельная async-задача на PTY читает в буфер. - **Коалесцирование:** накапливать вывод и эмитить `output` раз в ~4–8 мс или по достижении ~16 КБ. Билд-лог на 50k строк не должен порождать 50k событий. - **Backpressure:** на каждого подписчика — ограниченный канал. При переполнении (рендер не успевает) — дропать/сжимать промежуточные кадры, но грид в демоне (§6.3) обновлять всегда — он авторитетный. ### 6.3. Гибрид-парсинг (xterm.js рендер / Rust-грид семантика) Один байтовый поток PTY раздаётся в **три** места: 1. **GUI** (событие `output`) → **xterm.js** парсит ANSI и рисует. Аддоны: `webgl` (рендер), `search`, `serialize`, `web-links`. 2. **Rust-грид** (`alacritty_terminal`) в демоне → авторитетная модель экрана/скроллбэка. Используется для: - детекции статуса (fallback-паттерны, §7.3), - поиска по скроллбэку на стороне демона, - извлечения вывода последней команды (по маркерам OSC 133), - снапшота при reattach (§6.4). 3. Тот же грид служит источником снапшота — отдельный буфер не нужен. Развод **отображения** (xterm.js) и **анализа** (alacritty-грид) снимает ложный выбор «или рендер, или умная модель». Два парсера на один stream дёшево. > Выбор `alacritty_terminal` (а не голого `vte`): он держит полноценный грид с курсором и скроллбэком из коробки, что нужно и для семантики, и для снапшота. `vte` дал бы только парсинг esc-последовательностей без модели экрана. ### 6.4. Снапшот и репейнт При `attach` демон сериализует текущий грид панели → ANSI-дамп (видимая область + позиция курсора + при необходимости часть скроллбэка) → GUI пишет его в свежий инстанс xterm.js. Дальше едет живой `output`. Бюджет — см. §2. --- ## 7. Детекция статуса (инверсия) Статус не угадывается из вывода, а **приходит в демон как команда** `set_state`. Источники, по убыванию надёжности: ### 7.1. Хуки агентов (основной путь) - **Claude Code** поддерживает хуки (Stop / Notification и родственные). Вешаем на них вызов CLI: ``` spacesh notify --surface $SPACESH_SURFACE_ID --state done # на завершение spacesh notify --surface $SPACESH_SURFACE_ID --state wait # на запрос ввода ``` CLI шлёт `set_state` в socket → демон ставит статус → событие `state` → кольцо панели меняет цвет. Детерминированно. - `$SPACESH_SURFACE_ID` инжектируется в env панели при спавне — так хук знает, про какую панель сообщает. > **Внимание при реализации:** точные имена хуков и формат payload у Claude Code меняются между версиями. Слой интеграции с хуками изолировать в адаптер и версионировать; не зашивать имена в ядро. ### 7.2. OSC 133 для shell Для обычных shell-панелей инжектировать shell-integration с **OSC 133** (semantic prompt marking): чёткие маркеры «команда началась / закончилась + exit code». Даёт статус (`work` между A/C-маркерами, `done`/`error` по exit code), а заодно навигацию по промптам и извлечение вывода последней команды. ### 7.3. Fallback — паттерны вывода Для агентов без хуков — эвристики поверх alacritty-грида (спиннеры, промпт-паттерны, запросы подтверждения). Изолировано в один модуль, считается best-effort. Меньшинство кейсов. ### 7.4. Состояния `work` (работает) · `wait` (ждёт ввода) · `done` (готов) · `error` (ошибка) · `idle` (простаивает). Маппятся на цвет кольца панели и бейдж в сайдбаре. --- ## 8. Модель воркспейсов и раскладки ### 8.1. Сущности - **Workspace** — папка проекта + дерево сплитов + метаданные (имя, цвет, группа, флаг «не забыть»). - **Surface (панель)** — лист дерева сплитов; PTY + агент + состояние. - **Split node** — внутренний узел: ориентация (`h`/`v`) + дети + пропорции. - **Group** — именованная цветная коллекция воркспейсов с порядком. ### 8.2. Дерево сплитов Рекурсивное бинарное (или n-арное) дерево. Ресайз — изменение пропорций узла. 10 пресет-раскладок: `1`, `2↔`, `2↕`, `2+1`, `1+2`, `3`, `2×2`, `4`, `2×3`, `2×4`. ### 8.3. Пресеты Пресет = (раскладка панелей) + (привязка агента к каждой панели). Сохранённый пресет → запуск в один клик (`apply_preset`). Хранится в конфиге пользователя. ### 8.4. Персистентность - Раскладка и метаданные — на диск (`~/.spacesh/state.json` или SQLite, если вырастет), отдельно от живых процессов. - Сохранение по изменению (debounce), атомарная запись (temp + rename). - При старте демона без живых сессий — структура восстанавливается, агенты перезапускаются по запросу/настройке. ### 8.5. UX-метаданные (дёшево, эффект большой) - **Цветные группы** воркспейсов. - **Метка «не забыть»** — unread-модель как в почте: кружок на воркспейсе, пока к нему не вернулись. - **Свой порядок** — перетаскивание, раскладка сохраняется. - **Центр событий** — лента уведомлений (готов/ждёт/ошибка) с «отметить прочитанным». --- ## 9. Фичи поверх ядра (v1) ### 9.1. Зум-панели Временный фуллскрин одной панели поверх раскладки. Чисто **UI-состояние GUI** — дерево сплитов не трогается, демона не касается. Хоткей-тоггл, по выходу возврат к прежней раскладке. ### 9.2. Внешние нотификации (Telegram, MAX) Когда окно свёрнуто или ты не за макбуком, событие агента должно догнать снаружи. Реализуется как **подписчик внутри `spaceshd`** на события шины — спине это ничего не двигает, чистое расширение. **Механика:** - Подписчик слушает события `state` (по умолчанию реагирует на `done`, `wait`, `error`; набор настраивается) и `exit`. - На совпадении формирует сообщение (`воркспейс · агент · состояние`, время, опционально хвост вывода) и диспетчит во включённые каналы. - Каждый канал — отдельный **адаптер** за общим трейтом `NotificationChannel { async fn send(&self, msg) }`. Добавить новый канал = новый адаптер, ядро не трогается. **Адаптеры v1:** | Канал | Транспорт | Конфиг | Заметки | |---|---|---|---| | **Telegram** | Bot API, HTTPS `sendMessage` | `bot_token`, `chat_id` | Бот создаётся через @BotFather. Только исходящие — webhook/long-poll не нужны | | **MAX** | MAX Bot API (`dev.max.ru`), HTTPS, JSON | `bot_token`, `chat_id` | Токен от Master Bot. Авторизация заголовком `Authorization: ` (query-параметр устарел). Только исходящие | > MAX Bot API построен по тем же принципам, что Telegram (токен → HTTPS-запросы → JSON), но детали отличаются: тип апдейтов в явном поле `update_type`, свой набор методов и формат сообщений. Для нотификаций нам нужен только метод отправки сообщения — приём апдейтов/webhook не требуется. Есть официальный TS-клиент `@maxhub/max-bot-api` и python-клиенты, если не захочется бить по HTTP напрямую. **Поведение:** - **Анти-спам / дедуп:** не слать повторное уведомление об одном и том же состоянии панели; коалесцировать всплески (несколько `done` подряд → одно сводное). Окно дедупа настраивается. - **Rate limits:** соблюдать лимиты каналов (у MAX базово ~30 msg/s на бота; Telegram ~30 msg/s глобально, ~1 msg/s на чат). Для персонального инструмента не критично, но очередь с троттлингом заложить. - **Фильтр по воркспейсу/группе:** опционально слать только по помеченным воркспейсам (увязывается с меткой «не забыть», §8.5). - **Доставка best-effort:** сбой канала логируется, ретраится ограниченно, не блокирует работу демона. **Конфиг (пример, `~/.spacesh/config.toml`):** ```toml [notifications] on_states = ["done", "wait", "error"] dedup_window_secs = 30 [notifications.telegram] enabled = true bot_token = "env:SPACESH_TG_TOKEN" chat_id = "123456789" [notifications.max] enabled = true bot_token = "env:SPACESH_MAX_TOKEN" chat_id = "..." ``` > Токены — через ссылку на env-переменную, не хранить в открытом виде в конфиге. --- ## 10. Структура кода ### 10.1. Rust workspace (cargo) ``` spacesh/ ├── crates/ │ ├── spacesh-proto/ # типы команд/событий, serde, кадрирование. Шарится всеми. │ ├── spacesh-core/ # модель воркспейсов/сплитов, грид (alacritty_terminal), │ │ # движок статусов. Без I/O — юнит-тестируемо. │ ├── spacesh-pty/ # спавн/io PTY (portable-pty), reader-loop, батчинг, resize. │ ├── spaceshd/ # демон: владеет сессиями, хостит socket, fan-out событий, │ │ # персист раскладки, launchd-обвязка, подписчики. │ └── spacesh-cli/ # тонкий бинарь-клиент к socket. └── app/ # Tauri-приложение ├── src-tauri/ # Rust-сторона: клиент к socket + мост в webview └── src/ # React/TS фронт ``` | Крейт | Ответственность | Ключевые зависимости | |---|---|---| | `spacesh-proto` | Протокол: типы, serde, length-prefix framing | `serde`, `serde_json` | | `spacesh-core` | Доменная модель, грид, статусы | `alacritty_terminal` | | `spacesh-pty` | PTY io, батчинг, backpressure | `portable-pty`, `tokio` | | `spaceshd` | Демон, socket-сервер, fan-out, персист, подписчики-нотификации (Telegram/MAX) | `tokio`, `reqwest`, `spacesh-*` | | `spacesh-cli` | CLI-клиент | `clap`, `tokio` | | `app/src-tauri` | Мост socket ↔ webview | `tauri` 2, `tokio` | ### 10.2. React/TS фронт | Компонент | Ответственность | |---|---| | `TerminalView` | xterm.js + аддоны (webgl/search/serialize/links) на панель; ввод → `input`, репейнт из снапшота | | `LayoutEngine` | рекурсивное дерево сплитов, ресайз мышью, **зум-панели** | | `Sidebar` | воркспейсы/группы, цвета, метка «не забыть», бейджи статуса | | `EventCenter` | лента уведомлений, «отметить прочитанным» | | `Wizard` | новый воркспейс (папка+раскладка→агент в панель), пресеты | | `Settings` | конфиг нотификаций (Telegram/MAX), хоткеи, правила; редактирование `config.toml` | | `socketBridge` | подписка на события демона через Tauri IPC, отправка команд | --- ## 11. Хоткеи (черновой набор) | Действие | Хоткей | |---|---| | Новый воркспейс | `Cmd + N` | | Переключить воркспейс | `Cmd + 1…9` | | Сплит панели | `Cmd + Shift + T` | | К свежему агенту | `Cmd + Shift + U` | | Соседняя панель | `Cmd + Opt + ← ↑ → ↓` | | Зум панели (тоггл) | `Cmd + Shift + Z` | | Поиск по скроллбэку | `Cmd + F` | | Настройки | `Cmd + ,` | > macOS-маппинг (Cmd вместо Ctrl). Сверить конфликты с системными/агентскими хоткеями; вынести в настраиваемую схему. --- ## 12. Порядок реализации (milestones) **M0 — живой терминал через socket (вертикальный срез).** `spacesh-proto` + `spaceshd` с командами `open` / `new_surface` / `input` / `output` + реальный PTY (`spacesh-pty`). Цель: байты летают GUI ↔ демон ↔ PTY. Минимальный фронт с одним `TerminalView`. **M1 — переживаемость и reattach.** Грид в демоне (`spacesh-core` + alacritty), `attach`/`detach` со снапшотом, репейнт xterm.js. launchd-обвязка. Цель: убил GUI — агент жив, открыл — экран восстановился. **M2 — раскладки и воркспейсы.** `LayoutEngine`, дерево сплитов, ресайз, персист (`state.json`), сайдбар с воркспейсами. Пресеты + `apply_preset`. Wizard. **M3 — статусы.** `set_state`, интеграция хуков Claude Code, OSC 133 для shell, fallback-паттерны. Кольца + бейджи + центр событий + нативные уведомления. **M4 — CLI.** `spacesh-cli` поверх готовой шины: `open`/`status`/`focus`/`close`/`new-surface`/`notify`, `--json`, shell-completions. **M5 — фичи поверх.** Зум-панели, внешние нотификации (Telegram + MAX, §9.2), поиск по скроллбэку, diff-просмотр изменений агента (CodeMirror 6). **M6 (post-v1) — remote.** Транспорт-трейт → проброс socket через SSH → демон на ноде, GUI на макбуке. --- ## 13. Открытые вопросы и риски | # | Вопрос/риск | Заметка | |---|---|---| | 1 | Имена/формат хуков Claude Code дрейфуют по версиям | Изолировать в версионируемый адаптер; не зашивать в ядро | | 2 | Нотификации: блокировка бота за массовые рассылки/спам (особенно MAX) | Только исходящие, низкий объём, дедуп + троттлинг; не использовать для рассылок | | 3 | WKWebView ≠ WebView2: IME (рус/англ), фокус, DnD, перехват клавиш | Отлаживать на macOS с нуля, не переносить допущения с Windows-аналогов | | 4 | Производительность xterm.js при шквальном выводе | webgl-аддон обязателен; батчинг §6.2; профилировать на реальных билд-логах | | 5 | Подпись/нотаризация для раздачи (Developer ID, stapling) | Заложить в CI заранее, иначе Gatekeeper ломает «скачал и запустил» | | 6 | Автообновление вне App Store | Tauri updater / Sparkle; решить на M1 | | 7 | Single-instance демона и гонки при старте | Лок-файл + проверка живости socket; обработать «socket есть, демон мёртв» | | 8 | Объём `output` по JSON-socket | Если упрёмся — MessagePack, изменение локализовано в `spacesh-proto` | --- *Конец спецификации v0.1.*