# CursorRemote → форк для Zulip **Источник:** Анализ кода CursorRemote (v0.1.46) из `~/Developer/cursorremote/` ## Архитектура CursorRemote ``` Cursor IDE ←──CDP──→ Relay Server ←──socket.io──→ Browser Client (Node.js) ←──Bot API───→ Telegram ``` Relay Server построен на EventEmitter. Основные компоненты: ### Data Flow (наблюдение) 1. **CDPBridge** (`cdp-bridge.ts`) — подключается к Cursor через CDP WebSocket (`--remote-debugging-port=9222`), управляет жизненным циклом соединения 2. **DOMExtractor** (`dom-extractor.ts`) — через `Runtime.evaluate` дёргает DOM Cursor внутри его renderer'а, вытаскивает `[data-flat-index]` элементы, парсит их в `ChatElement[]` 3. **WindowMonitor** — циклически опрашивает все окна Cursor, собирает `WindowSnapshot` 4. **StateManager** (`state-manager.ts`) — диффает новый state с предыдущим, эмитит `state:patch` 5. **Транспорты** подписываются на события StateManager ### Transport Architecture (ключевое для нас) Контракт транспорта (`transports/types.ts`): ```typescript export interface Transport { readonly name: string; start(): Promise; stop(): Promise; } ``` **TelegramTransport** (`transports/telegram/base.ts`): - Наследуется от `BaseTelegramTransport` (абстрактный класс) - Подписывается на `windowMonitor.on('window:update')` и `stateManager.on('state:patch')` - В `processWindow()` получает `WindowSnapshot` → находит/создаёт topic (thread_id) → отправляет diff в Telegram - В `onStatePatch()` отдельно обрабатывает `pendingApprovals` и `questionnaire` (они не привязаны к конкретному окну) - Использует `formatter.ts` для конвертации `ChatElement` в HTML ### Что нам нужно реплицировать Для Zulip не нужны: - Интерактивные кнопки (approve/reject в Zulip — это реакция на пост, не инлайн-кнопки) - Переключение вкладок/мод/моделей - Typing indicator Нужно: - Дублировать сообщения (`HumanMessage`, `AssistantMessage`, `ToolCallElement`, `PlanBlock`, `RunCommand`) - Публиковать в Zulip канал `#cursor` в отдельный топик (например `{windowTitle}/{tabTitle}`) - Апдейтить существующие сообщения (через `edit_message` в Zulip API) - Сообщать о новых approval-запросах ### Точки интеграции в код | Файл | Что менять | |---|---| | `config.ts` | Добавить ZulipConfig (url, email, api_key, stream) | | `index.ts` | Создавать ZulipTransport если включён в конфиге | | `transports/zulip/` | Новая папка — весь транспорт | | `transports/zulip/zulip-api.ts` | REST клиент для Zulip API | | `transports/zulip/message-tracker.ts` | Отслеживание message_id (можно переиспользовать или скопировать) | | `transports/telegram/formatter.ts` | Можно частично переиспользовать форматирование | ## Детали реализации (из code review) ### Конфигурация: что добавить в `config.ts` Текущий конфиг — только telegram: ```typescript telegram: { enabled: process.env.TELEGRAM_ENABLED === 'true', botToken: process.env.TELEGRAM_BOT_TOKEN ?? '', preRegisteredUsers, impl: (process.env.TELEGRAM_IMPL === 'raw' ? 'raw' : 'grammy') as 'grammy' | 'raw', }, ``` Нужно добавить: ```typescript zulip: { enabled: process.env.ZULIP_ENABLED === 'true', url: process.env.ZULIP_URL ?? 'https://zulip.qentra.top', email: process.env.ZULIP_BOT_EMAIL ?? '', apiKey: process.env.ZULIP_BOT_API_KEY ?? '', stream: process.env.ZULIP_STREAM ?? 'cursor', } ``` ### Архитектура транспорта ZulipTransport должен **не наследовать** от BaseTelegramTransport (слишком много Telegram-специфики: форум топики, инлайн кнопки, typing indicators, activity messages). Правильнее — реализовать `Transport` интерфейс напрямую: ```typescript class ZulipTransport implements Transport { readonly name = 'zulip'; constructor( config: ZulipConfig, windowMonitor: WindowMonitor, stateManager: StateManager, commandExecutor: CommandExecutor, cdpBridge: CDPBridge, ) {} async start(): Promise { // 1. Подписаться на windowMonitor.on('window:update') // 2. Подписаться на stateManager.on('state:patch') } async stop(): Promise { // Отписаться } } ``` ### Детали WindowSnapshot (ключевые поля) | Поле | Тип | Для Zulip | |---|---|---| | `windowTitle` | `string` | Часть топика: `windowTitle/tabTitle` | | `chatTabs` | `ChatTab[]` | Брать активный (`t.isActive`) | | `messages` | `ChatElement[]` | Основной контент — постить в Zulip | | `pendingApprovals` | `Approval[]` | Отдельный пост с предупреждением | | `agentStatus` | `AgentStatus` | Можно игнорировать или слать в activity | | `agentActivityText` | `string\|null` | Текущая активность агента | | `activeComposerId` | `string` | Для дедупликации (один агент в разных окнах) | ### Управление состоянием Zulip не поддерживает редактирование сообщений так же гибко, как Telegram (может редактировать только контент существующего поста, не может менять топик/канал). Поэтому: 1. Новые `HumanMessage` → POST новый пост в топик 2. Новые `AssistantMessage` → POST новый пост 3. Изменяющиеся (streaming) → **edit_message** для последнего ai-сообщения 4. `ToolCallElement` (completed) → POST отдельным постом 5. `PlanBlock`, `RunCommand` → POST отдельным постом 6. `pendingApprovals` → POST с @**everyone** (если в конфиге) Топик: `cursor/{windowTitle}/{tabTitle}`, где `windowTitle` — имя проекта. ### Потенциальные сложности 1. **Streaming** — Cursor агент пишет сообщения постепенно, DOM меняется каждые 300-500ms. Нужно редактировать последний пост пока сообщение не зафиксировалось. Иначе каждое слово — отдельный пост. 2. **Rate лимиты Zulip API** — много мелких апдейтов могут вызвать 429. Нужен debounce на редактирование. 3. **Дубликаты при multi-window** — когда два окна Cursor показывают одного агента (через global rail), нужна дедупликация по `activeComposerId`. 4. **Прерывание работы** — при перезапуске сервера не постить уже отправленные сообщения заново. Нужен message-tracker. ### Команды для разработки ```bash cd ~/Developer/cursorremote # Запуск сервера (без extension) npm run dev # hot-reload, tsx watch npm start # Production (после npm run build) # Тесты (уже есть, свои добавить) npm test # Настройка через .env cp .env.example .env # Добавить: ZULIP_ENABLED=true, ZULIP_URL, ZULIP_BOT_EMAIL, ZULIP_BOT_API_KEY # CDP включение Cursor open -a Cursor --args --remote-debugging-port=9222 ``` ### Состояние проекта (26 июня 2026) - ✅ Репозиторий склонирован: `~/Developer/cursorremote/` - ✅ Код изучен, архитектура понятна - ✅ analysis.md создан в Obsidian - ❌ ZulipTransport не реализован - ❌ config.ts не расширен - ❌ .env не настроен ### Структура папок для Zulip транспорта (предлагаемая) ``` src/server/transports/zulip/ ├── index.ts # ZulipTransport — implements Transport ├── zulip-api.ts # REST клиент для Zulip API (send, edit, fetch) ├── topic-manager.ts # windowTitle+tabTitle → топик/тема └── message-tracker.ts # ChatElement.id → Zulip message_id ```