Files
claude-cli-proxy/ARCHITECTURE.md
vigdorov 4a91896732 feat: xterm.js + node-pty architecture for Claude CLI web terminal
Complete rewrite from tmux + JSONL parsing to a clean PTY-based approach.
The proxy spawns Claude CLI in a pseudo-terminal via node-pty and relays
terminal I/O as binary WebSocket frames to the simple-chat backend,
which forwards them to the browser where xterm.js renders a full terminal.

- node-pty PTY manager with 50KB replay buffer per session
- Binary frame protocol with chatId multiplexing
- WebSocket client with auto-reconnection and heartbeat
- Directory listing and session listing for the web UI
- README with setup instructions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 00:24:29 +03:00

262 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Claude CLI Proxy — Архитектура
## Обзор
claude-cli-proxy — это локальное приложение на Node.js, которое соединяет Claude Code CLI с веб-интерфейсом simple-chat. Оно подключается к бэкенду simple-chat через исходящее WebSocket-соединение, управляет tmux-сессиями для запуска экземпляров Claude CLI и отслеживает их вывод через JSONL-файлы транскриптов и захват терминала.
## Диаграмма компонентов
```
┌─────────────────────────────────────────────────────────────────┐
│ Mac разработчика │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ claude-cli-proxy │ │
│ │ │ │
│ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ WsClient │ │ TmuxManager │ │SessionMonitor│ │ │
│ │ │(connection/)│ │ (tmux/) │ │ (monitor/) │ │ │
│ │ └──────┬──────┘ └──────┬───────┘ └──────┬───────┘ │ │
│ │ │ │ │ │ │
│ │ │ ┌──────┴───────┐ ┌──────┴───────┐ │ │
│ │ │ │ tmux-сессия │ │ JSONL-файлы │ │ │
│ │ │ │"claude-proxy"│ │ ~/.claude/ │ │ │
│ │ │ │ ├─ окно 1 │ │ projects/... │ │ │
│ │ │ │ ├─ окно 2 │ └──────────────┘ │ │
│ │ │ │ └─ ... │ │ │
│ │ │ └──────────────┘ │ │
│ └─────────┼────────────────────────────────────────────────┘ │
│ │ WebSocket │
└────────────┼────────────────────────────────────────────────────┘
┌────────┴────────┐
│ simple-chat │
│ бэкенд │
│ (модуль │
│ agent-proxy) │
└─────────────────┘
```
## Компоненты
### 1. Точка входа (`main.ts`)
Класс `ClaudeCliProxy` оркестрирует запуск:
1. Загрузка конфигурации из `.env`
2. Установка/обновление хука SessionStart для Claude CLI
3. Подключение к бэкенду через WebSocket
4. Инициализация tmux-сессии
5. Запуск мониторинга активных окон
### 2. WebSocket-клиент (`connection/ws-client.ts`)
- Подключается к `SERVER_URL` с `PROXY_TOKEN` для аутентификации
- Обрабатывает входящие команды от бэкенда
- Отправляет события (сообщения, обновления статуса, скриншоты) на бэкенд
- Переподключается с экспоненциальной задержкой при разрыве соединения
### 3. Протокол (`connection/protocol.ts`)
Общие определения типов сообщений для WebSocket-протокола.
#### Сообщения от бэкенда к прокси
| Сообщение | Описание |
|-----------|----------|
| `sync_state` | Отправить текущее состояние всех окон при подключении |
| `create_window` | Создать новое tmux-окно, запустить `claude` или `claude --resume <id>` |
| `send_message` | Отправить текстовый ввод в Claude CLI в указанном окне |
| `send_command` | Отправить произвольную команду в tmux-панель |
| `send_command_capture` | Отправить команду и захватить вывод |
| `send_key` | Отправить нажатие клавиши (например, Enter, Escape, Tab) |
| `kill_window` | Закрыть tmux-окно |
| `request_screenshot` | Захватить текущее содержимое терминала |
| `list_directories` | Получить список доступных рабочих директорий |
| `list_sessions` | Получить список возобновляемых сессий Claude |
#### Сообщения от прокси к бэкенду
| Сообщение | Описание |
|-----------|----------|
| `window_ready` | Новое окно создано, сообщается windowId |
| `message` | Новое сообщение ассистента/пользователя из JSONL-транскрипта |
| `status` | Изменение статуса Claude CLI (размышление, использование инструмента, простой) |
| `interactive` | Обнаружен интерактивный UI (запрос разрешения и т.д.) |
| `status_line` | Разобранные данные строки состояния (модель, контекст %, сессия %) |
| `screenshot` | Скриншот терминала (захваченное содержимое панели) |
| `window_closed` | Окно было закрыто или процесс завершился |
| `directories` | Ответ на list_directories |
| `sessions_list` | Ответ на list_sessions |
| `command_output` | Ответ на send_command_capture |
| `error` | Отчёт об ошибке |
### 4. Менеджер Tmux (`tmux/manager.ts`)
Управляет tmux-сессией `claude-proxy` (настраивается через `TMUX_SESSION`).
Операции:
- **Создание окна:** `tmux new-window` с командой `claude` или `claude --resume <sessionId>`
- **Удаление окна:** `tmux kill-window -t <windowId>`
- **Отправка клавиш:** `tmux send-keys -t <windowId>` для пользовательского ввода
- **Захват панели:** `tmux capture-pane -t <windowId> -p` для скриншотов и строки состояния
Каждый чат в simple-chat соответствует одному tmux-окну. Идентификатор окна хранится в поле `agentWindowId` чата.
### 5. Монитор сессий (`monitor/session-monitor.ts`)
Оркестратор, запускающий два цикла опроса для каждого активного окна:
#### Опрос JSONL (интервал 2 секунды)
1. Определение пути к JSONL-файлу по sessionId: `~/.claude/projects/{encoded_cwd}/{sessionId}.jsonl`
2. Чтение файла с последнего известного смещения в байтах (хранится в `monitor-state.ts`)
3. Парсинг новых JSONL-записей с помощью `jsonl-parser.ts`
4. Фильтрация системных/внутренних сообщений
5. Отправка релевантных сообщений на бэкенд через WebSocket
#### Опрос терминала (интервал 1 секунда)
1. Захват последних 3 строк tmux-панели (область строки состояния)
2. Парсинг с помощью `terminal-parser.ts` для извлечения:
- **Строка 1:** Название проекта, использование сессии %, использование за неделю %
- **Строка 2:** Название модели, использование контекста %
- **Строка 3:** Индикатор режима разрешений
3. Обнаружение интерактивных состояний UI (запросы разрешений, запросы ввода)
4. Отправка событий `status_line`, `status` и `interactive` на бэкенд
### 6. JSONL-парсер (`monitor/jsonl-parser.ts`)
Разбирает файлы транскриптов Claude CLI в формате JSONL. Каждая строка — это JSON-объект, представляющий событие разговора. Парсер:
- Читает новые байты с последнего смещения
- Разбирает каждую строку как JSON
- Фильтрует системные сообщения (инициализация, конфигурация и т.д.)
- Извлекает сообщения пользователя, сообщения ассистента, вызовы инструментов и результаты инструментов
### 7. Парсер терминала (`monitor/terminal-parser.ts`)
Разбирает строку состояния Claude CLI, отображаемую в нижней части терминала:
```
Строка 1: agent_dev │ session: 24% │ week: 3%
Строка 2: Opus 4.6 (1M context) │ Ctx: 2%
Строка 3: ⏵⏵ bypass permissions on (shift+tab to cycle)
```
Извлекает структурированные данные: название проекта, модель, процент контекста, использование сессии/недели, режим разрешений.
Также обнаруживает интерактивные состояния UI, когда Claude ожидает ввода пользователя (запросы разрешений, вопросы да/нет).
### 8. Состояние монитора (`monitor/monitor-state.ts`)
Отслеживает состояние мониторинга для каждого окна:
- **Смещение в байтах:** Последняя позиция чтения в JSONL-файле (для инкрементального чтения)
- **Кеш mtime:** Время модификации файла (пропуск повторного чтения неизменённых файлов)
### 9. Система хуков (`hook/`)
#### hook.ts
Управляет хуком SessionStart для Claude CLI:
- Устанавливает/обновляет запись хука в `~/.claude/settings.json`
- Предоставляет CRUD-операции для `~/.claude-proxy/session_map.json`
#### session-start-hook.ts
Скрипт, который запускается при старте новой сессии Claude CLI. Он:
1. Получает sessionId от Claude CLI
2. Определяет текущий идентификатор tmux-окна
3. Записывает маппинг `{windowId: sessionId}` в `~/.claude-proxy/session_map.json`
Этот маппинг необходим, потому что путь к JSONL-файлу зависит от sessionId, который становится известен только после запуска Claude CLI.
### 10. Карта сессий
Хранится в `~/.claude-proxy/session_map.json`.
Сопоставляет идентификаторы tmux-окон с идентификаторами сессий Claude CLI:
```json
{
"3": "abc123-def456",
"5": "789ghi-012jkl"
}
```
Используется монитором для нахождения правильного JSONL-файла транскрипта для каждого окна.
### 11. Загрузчик файлов (`files/downloader.ts`)
Скачивает файлы по URL из MinIO, на которые есть ссылки в сообщениях. Используется, когда вывод Claude CLI содержит ссылки на файлы, которые должны быть доступны через веб-интерфейс.
## Потоки данных
### Поток создания нового чата
1. Пользователь создаёт чат в веб-интерфейсе simple-chat
2. Бэкенд отправляет `create_window` прокси через WebSocket
3. Прокси создаёт tmux-окно, запускает `claude` (или `claude --resume <id>`)
4. Claude CLI запускается, срабатывает хук SessionStart
5. Хук записывает маппинг sessionId -> windowId в session_map.json
6. Прокси читает session_map, начинает мониторинг JSONL-файла и терминала
7. Прокси отправляет `window_ready` на бэкенд с windowId
### Поток сообщений
1. Пользователь вводит сообщение в simple-chat
2. Бэкенд отправляет `send_message` прокси
3. Прокси отправляет нажатия клавиш в tmux-окно через `tmux send-keys`
4. Claude CLI обрабатывает сообщение, записывает в JSONL-транскрипт
5. Опросчик JSONL обнаруживает новые байты, разбирает сообщения
6. Прокси отправляет события `message` на бэкенд
7. Бэкенд передаёт их на фронтенд через SSE
### Поток строки состояния
1. Опросчик терминала захватывает последние 3 строки tmux-панели каждую секунду
2. Парсер извлекает модель, контекст %, статистику использования
3. Прокси отправляет `status_line` на бэкенд
4. Фронтенд отображает строку состояния в реальном времени в UI чата
## Кодирование пути JSONL
Claude CLI хранит транскрипты по пути:
```
~/.claude/projects/{encoded_cwd}/{sessionId}.jsonl
```
Кодирование пути: символы `/` и `_` в пути рабочей директории заменяются на `-`.
Пример:
- Рабочая директория: `/Users/vigdorov/agent_dev`
- Закодированный путь: `-Users-vigdorov-agent-dev`
- Полный путь: `~/.claude/projects/-Users-vigdorov-agent-dev/{sessionId}.jsonl`
## Логика переподключения
WebSocket-клиент реализует экспоненциальную задержку:
1. При разрыве соединения ожидание `baseDelay` (начиная с ~1 секунды)
2. Каждая неудачная попытка переподключения удваивает задержку
3. Ограничение максимальной задержкой
4. При успешном переподключении отправляется `sync_state` с текущим состоянием окон
5. Сброс таймера задержки при успешном подключении
## Интеграция с simple-chat
### Бэкенд
- Модуль: `agent-proxy`
- WebSocket gateway обрабатывает подключения прокси
- REST API для управления agent-устройствами
- SSE relay передаёт обновления в реальном времени на фронтенд
### Фронтенд
- Маршрут: `/agent`
- `AgentPage` — список устройств/чатов
- `AgentChatPage` — вид чата с сообщениями в реальном времени и строкой состояния
- Slash-команды: `/model` (выбор модели), `/resume` (выбор сессии), `/clear`, `/compact`, `/plan`
### База данных
- Таблица `agent_devices` — зарегистрированные прокси-устройства
- Поля таблицы `chats`: `agentWindowId`, `agentWorkDir`, `agentSessionId`