Агент отвечает на стикеры: переписка с Claude Code прямо на доске

Claude Code может отвечать вам прямо на доске: вы пишете стикер, ждёте 10 секунд — и рядом со стикером появляется ответ агента. Для этого рядом с проектом лежит небольшой скрипт-наблюдатель, а Claude Code запускает его в фоне и просыпается на каждый ваш стикер.
Как это работает
- Наблюдатель раз в 4 секунды спрашивает у доски, что изменилось.
- Новый или изменённый стикер (или текст) он запоминает и ждёт: если 10 секунд его никто не трогает, стикер считается дописанным.
- Наблюдатель проверяет, что стикер поставили вы, а не агент, и выводит одну строку: id стикера и его текст.
- Claude Code получает эту строку как событие, читает стикер и то, что вокруг него, делает то, о чём вы просите, и отвечает примечанием, прикреплённым к стикеру. Примечание едет вместе со стикером, если его передвинуть.
Что понадобится
- Claude Code, подключённый к доске. Если ещё не подключали — инструкция «Подключить агента по MCP».
- Python 3. На macOS проверьте командой
python3 --version; если система предложит установить инструменты разработчика — согласитесь. На Windows установите Python с python.org и используйтеpythonвместоpython3. - Ссылка на доску. Откройте доску и скопируйте адрес из строки браузера.
Как включить
-
Сохраните наблюдатель в папку проекта. Создайте файл
prona-watch.pyв той же папке, где запускаете Claude Code, и вставьте в него код ниже. Ничего менять не нужно: токен скрипт возьмёт из настроек Claude Code."""PRONA Board: стикер человека, который 10 с не правят, -> одна строка в stdout (событие для Claude Code).""" import json, os, sys, time, urllib.request URL = 'https://board-mcp.prona.space/mcp' QUIET_SEC, POLL_SEC = 10, 4 TEXT_TYPES = {'note', 'text'} def find_token(): """Токен из PRONA_BOARD_TOKEN или из настроек Claude Code (~/.claude.json). Не печатается.""" if os.environ.get('PRONA_BOARD_TOKEN'): return 'Bearer ' + os.environ['PRONA_BOARD_TOKEN'] cfg = json.load(open(os.path.expanduser('~/.claude.json'), encoding='utf-8')) scopes = [cfg.get('mcpServers', {})] + [p.get('mcpServers', {}) for p in cfg.get('projects', {}).values()] for servers in scopes: auth = servers.get('prona-board', {}).get('headers', {}).get('Authorization') if auth: return auth sys.exit('Не нашёл токен prona-board: подключите доску к Claude Code или задайте PRONA_BOARD_TOKEN') class Board: def __init__(self): self.auth, self.sid, self.n = find_token(), None, 0 self.rpc('initialize', {'protocolVersion': '2025-03-26', 'capabilities': {}, 'clientInfo': {'name': 'prona-watch', 'version': '1'}}) self.post({'jsonrpc': '2.0', 'method': 'notifications/initialized'}) def post(self, body): headers = {'Content-Type': 'application/json', 'Accept': 'application/json, text/event-stream', 'Authorization': self.auth} if self.sid: headers['Mcp-Session-Id'] = self.sid req = urllib.request.Request(URL, data=json.dumps(body).encode(), headers=headers, method='POST') with urllib.request.urlopen(req, timeout=30) as resp: self.sid = resp.headers.get('Mcp-Session-Id') or self.sid raw = resp.read().decode() if not raw.strip(): return None if raw.lstrip().startswith('{'): return json.loads(raw) data = [line[5:].strip() for line in raw.splitlines() if line.startswith('data:')] return json.loads(data[-1]) if data else None def rpc(self, method, params): self.n += 1 r = self.post({'jsonrpc': '2.0', 'id': self.n, 'method': method, 'params': params}) if r and 'error' in r: raise RuntimeError(r['error']) return r['result'] if r else None def read(self, request): res = self.rpc('tools/call', {'name': 'board_read', 'arguments': {'request': request}}) text = ''.join(c.get('text', '') for c in res.get('content', [])) if res.get('isError'): # истёкший токен, чужая доска — сервер объясняет словами raise RuntimeError(text) return json.loads(text) if len(sys.argv) < 2: sys.exit('Использование: python3 -u prona-watch.py <ссылка на доску или её id>') BOARD = sys.argv[1].rstrip('/').split('/')[-1].split('?')[0] here = os.path.dirname(os.path.abspath(__file__)) CURSOR_FILE = os.path.join(here, f'.prona-watch-{BOARD[:8]}.cursor') SEEN_FILE = os.path.join(here, f'.prona-watch-{BOARD[:8]}.seen.json') try: board = Board() cursor = open(CURSOR_FILE).read().strip() if os.path.exists(CURSOR_FILE) else None if not cursor: cursor = board.read({'op': 'changes', 'boardId': BOARD})['cursor'] except Exception as err: sys.exit(f'Доска не ответила: {err}') seen = json.load(open(SEEN_FILE, encoding='utf-8')) if os.path.exists(SEEN_FILE) else {} pending, fails = {}, 0 while True: try: ch = board.read({'op': 'changes', 'boardId': BOARD, 'since': cursor}) cursor = ch.get('cursor', cursor) open(CURSOR_FILE, 'w').write(cursor) now = time.time() for e in ch.get('changed', []): if e.get('type') in TEXT_TYPES and e.get('change') in ('created', 'updated'): pending[e['id']] = {'text': e.get('text', ''), 'last': now} ready = [i for i, p in pending.items() if now - p['last'] >= QUIET_SEC] if ready: human = {h.get('id') for h in board.read({'op': 'diff', 'boardId': BOARD}).get('human', [])} for i in ready: p = pending.pop(i) if i in human and p['text'].strip() and seen.get(i) != p['text']: seen[i] = p['text'] json.dump(seen, open(SEEN_FILE, 'w', encoding='utf-8'), ensure_ascii=False) print(json.dumps({'event': 'sticker', 'id': i, 'text': p['text']}, ensure_ascii=False), flush=True) fails = 0 except Exception as err: # сеть или сервер: не роняем наблюдение с первого сбоя fails += 1 if fails in (5, 50): print(json.dumps({'event': 'error', 'fails': fails, 'error': str(err)[:200]}, ensure_ascii=False), flush=True) if fails >= 200: sys.exit(1) time.sleep(POLL_SEC) -
Дайте Claude Code задачу следить за доской. Откройте Claude Code в этой папке и отправьте сообщение ниже, заменив ссылку на свою. Агент запустит наблюдатель в фоне и будет отвечать на каждый ваш стикер.
Следи за доской https://prona.space/board/ВАШ_ID и отвечай мне на ней. Запусти через Monitor команду `python3 -u prona-watch.py https://prona.space/board/ВАШ_ID` с timeout_ms 1800000; когда монитор истечёт — запускай его заново, пока я не скажу «хватит». Каждая строка вывода — мой стикер: прочитай его текст и то, что рядом на доске, сделай, о чём я прошу, и ответь примечанием (annotation) с attachTo = id стикера и position right. Длинный результат положи на доску документом рядом, а в чат пиши только коротко, что сделано. -
Проверьте. Поставьте на доске стикер «Привет, ты здесь?» и подождите 10–15 секунд. Рядом со стикером появится примечание с ответом агента.
Как пользоваться
- Пишите стикер как сообщение в чат. «Сведи эти три стикера в таблицу», «Почему этот вывод так оценён?», «Добавь этап про риски» — агент увидит, рядом с чем стоит стикер, и поймёт, о чём речь.
- Дописывайте спокойно. Пока вы правите стикер, агент ждёт; отвечает через 10 секунд после последней правки.
- Исправили вопрос — получите новый ответ. Если изменить текст уже отвеченного стикера, агент ответит снова. Простое перемещение стикера ответа не вызывает.
- Остановить — напишите агенту в чат «хватит следить за доской».
Ограничения
- Агент отвечает, только пока открыта сессия Claude Code, в которой он следит за доской. Закрыли терминал — ответы прекратятся; при следующем запуске повторите шаг 2, пропущенные стикеры наблюдатель не потеряет.
- Фоновое наблюдение живёт до 30 минут, после чего агент перезапускает его сам — это предусмотрено в задаче из шага 2.
- Пока агент выполняет одну просьбу, следующие стикеры ждут в очереди и будут обработаны по порядку.
Если не заработало
| Что видите | Причина | Что сделать |
|---|---|---|
| «Не нашёл токен prona-board» | Claude Code не подключён к доске или подключён под другим именем | Подключите по инструкции или задайте токен в переменной PRONA_BOARD_TOKEN |
python3: command not found | Не установлен Python 3 | Установите Python 3; на Windows запускайте через python |
| Агент говорит, что у него нет Monitor | Старая версия Claude Code | Обновите Claude Code и повторите шаг 2 |
| Стикер стоит, ответа нет | Агент занят предыдущей просьбой или сессия закрыта | Посмотрите в терминал: если сессия закрыта, повторите шаг 2 |
| «Токен доски не принят» | Истёк срок токена | Выпустите новый токен в окне «Подключить агента», выполните команду подключения заново и повторите шаг 2 |
Событие error в чате агента | Сбой сети | Наблюдатель повторит запросы сам; если ошибка не уходит — перезапустите шаг 2 |
Что дальше
Дайте агенту большую задачу на доске — как её поставить, рассказано в инструкции «Первая задача агенту». Стикерами удобно уточнять ход работы по этапам, не переключаясь в терминал.
Читайте также
ИнструкцияПодключить агента по MCP к доске за 2 минутыПошаговая инструкция подключения ИИ-агента к PRONA Board по MCP — Claude Code одной командой, Cursor и Codex строкой в конфиге, проверка и разбор частых ошибок.
ИнструкцияПервая задача агентуКак дать ИИ-агенту первую задачу на доске PRONA: своими словами, с итогом и данными; что агент делает дальше — трек, этапы, вопросы — и где следить за ходом.